Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: umeng broadcast message #153

Merged
merged 2 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ upyuns:
token-timeout: 1800
path: "/statistic/html/"

umeng:
app_key: ""
message_secret: ""
app_master_secret: ""
package_name: ""

elasticsearch:
addr: 127.0.0.1:9200
host: 127.0.0.1
Expand Down
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
Elasticsearch *elasticsearch
Kafka *kafka
UpYun *upyun
Umeng *umeng
VersionUploadService *url
runtimeViper = viper.New()
)
Expand Down Expand Up @@ -103,6 +104,7 @@
Kafka = &c.Kafka
DefaultUser = &c.DefaultUser
VersionUploadService = &c.Url
Umeng = &c.Umeng

Check warning on line 107 in config/config.go

View check run for this annotation

Codecov / codecov/patch

config/config.go#L107

Added line #L107 was not covered by tests
if upy, ok := c.UpYuns[srv]; ok {
UpYun = &upy
}
Expand Down
8 changes: 8 additions & 0 deletions config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ type upyun struct {
Path string
}

type umeng struct {
AppKey string `mapstructure:"app_key"`
MessageSecret string `mapstructure:"message_secret"`
AppMasterSecret string `mapstructure:"app_master_secret"`
PackageName string `mapstructure:"package_name"`
}

type config struct {
Server server
Snowflake snowflake
Expand All @@ -133,5 +140,6 @@ type config struct {
Kafka kafka
DefaultUser defaultUser
UpYuns map[string]upyun
Umeng umeng
Url url
}
2 changes: 2 additions & 0 deletions pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ const (

// ValidateCodeURL 获取验证码结果的本地python服务url,需要保证 login-verify 和 api 处于同一个 dokcer 网络中
ValidateCodeURL = "http://login-verify:8081/api/v1/jwch/user/validateCode"
// UmengURL 友盟推送 API
UmengURL = "https://msgapi.umeng.com/api/send"
)

const (
Expand Down
129 changes: 129 additions & 0 deletions pkg/umeng/broadcast.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
Copyright 2024 The west2-online Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package umeng

import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/west2-online/fzuhelper-server/pkg/constants"
"github.com/west2-online/fzuhelper-server/pkg/errno"
"github.com/west2-online/fzuhelper-server/pkg/logger"
)

// Android广播函数
func SendAndroidBroadcast(appKey, appMasterSecret, ticker, title, text, expireTime string) error {
message := AndroidBroadcastMessage{
AppKey: appKey,
Timestamp: fmt.Sprintf("%d", time.Now().Unix()),
Type: "broadcast",
Payload: AndroidPayload{
DisplayType: "notification",
Body: AndroidBody{
Ticker: ticker,
Title: title,
Text: text,
AfterOpen: "go_app",
},
},
Policy: Policy{
ExpireTime: expireTime,
},
ChannelProperties: map[string]string{
"channel_activity": "xxx",
},
Description: "测试广播通知-Android",
}

return sendBroadcast(appMasterSecret, message)

Check warning on line 57 in pkg/umeng/broadcast.go

View check run for this annotation

Codecov / codecov/patch

pkg/umeng/broadcast.go#L34-L57

Added lines #L34 - L57 were not covered by tests
}

// iOS广播函数
func SendIOSBroadcast(appKey, appMasterSecret, title, subtitle, body, expireTime string) error {
message := IOSBroadcastMessage{
AppKey: appKey,
Timestamp: fmt.Sprintf("%d", time.Now().Unix()),
Type: "broadcast",
Payload: IOSPayload{
Aps: IOSAps{
Alert: IOSAlert{
Title: title,
Subtitle: subtitle,
Body: body,
},
},
},
Policy: Policy{
ExpireTime: expireTime,
},
Description: "测试广播通知-iOS",
}

return sendBroadcast(appMasterSecret, message)

Check warning on line 81 in pkg/umeng/broadcast.go

View check run for this annotation

Codecov / codecov/patch

pkg/umeng/broadcast.go#L61-L81

Added lines #L61 - L81 were not covered by tests
}

// 通用广播发送逻辑
func sendBroadcast(appMasterSecret string, message interface{}) error {
postBody, err := json.Marshal(message)
if err != nil {
return errno.Errorf(errno.InternalServiceErrorCode, "umeng.sendBroadcast : failed to marshal JSON: %v", err)
}

Check warning on line 89 in pkg/umeng/broadcast.go

View check run for this annotation

Codecov / codecov/patch

pkg/umeng/broadcast.go#L85-L89

Added lines #L85 - L89 were not covered by tests

sign := generateSign("POST", constants.UmengURL, string(postBody), appMasterSecret)

req, err := http.NewRequest("POST", constants.UmengURL+"?sign="+sign, bytes.NewBuffer(postBody))
if err != nil {
return errno.Errorf(errno.InternalServiceErrorCode, "umeng.sendBroadcast : failed to create request: %v", err)
}
req.Header.Set("Content-Type", "application/json")

client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return errno.Errorf(errno.InternalServiceErrorCode, "umeng.sendBroadcast : failed to send request: %v", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return errno.Errorf(errno.InternalServiceErrorCode, "umeng.sendBroadcast : unexpected response code: %v", resp.StatusCode)
}

Check warning on line 108 in pkg/umeng/broadcast.go

View check run for this annotation

Codecov / codecov/patch

pkg/umeng/broadcast.go#L91-L108

Added lines #L91 - L108 were not covered by tests

var response UmengResponse
err = json.NewDecoder(resp.Body).Decode(&response)
if err != nil {
return errno.Errorf(errno.InternalServiceErrorCode, "umeng.sendBroadcast : failed to decode response: %v", err)
}

Check warning on line 114 in pkg/umeng/broadcast.go

View check run for this annotation

Codecov / codecov/patch

pkg/umeng/broadcast.go#L110-L114

Added lines #L110 - L114 were not covered by tests

if response.Ret != "SUCCESS" {
return errno.Errorf(errno.InternalServiceErrorCode, "umeng.sendBroadcast : broadcast failed: %s (%s)", response.Data.ErrorMsg, response.Data.ErrorCode)
}

Check warning on line 118 in pkg/umeng/broadcast.go

View check run for this annotation

Codecov / codecov/patch

pkg/umeng/broadcast.go#L116-L118

Added lines #L116 - L118 were not covered by tests

logger.Infof("Broadcast sent successfully! MsgID: %s\n", response.Data.MsgID)
return nil

Check warning on line 121 in pkg/umeng/broadcast.go

View check run for this annotation

Codecov / codecov/patch

pkg/umeng/broadcast.go#L120-L121

Added lines #L120 - L121 were not covered by tests
}

// 生成MD5签名
func generateSign(method, url, postBody, appMasterSecret string) string {
data := fmt.Sprintf("%s%s%s%s", method, url, postBody, appMasterSecret)
hash := md5.Sum([]byte(data))
return hex.EncodeToString(hash[:])

Check warning on line 128 in pkg/umeng/broadcast.go

View check run for this annotation

Codecov / codecov/patch

pkg/umeng/broadcast.go#L125-L128

Added lines #L125 - L128 were not covered by tests
}
79 changes: 79 additions & 0 deletions pkg/umeng/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
Copyright 2024 The west2-online Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package umeng

// 公共返回结构
type UmengResponse struct {
Ret string `json:"ret"`
Data struct {
MsgID string `json:"msg_id,omitempty"`
ErrorCode string `json:"error_code,omitempty"`
ErrorMsg string `json:"error_msg,omitempty"`
} `json:"data"`
}

// Android广播消息结构
type AndroidBroadcastMessage struct {
AppKey string `json:"appkey"`
Timestamp string `json:"timestamp"`
Type string `json:"type"`
Payload AndroidPayload `json:"payload"`
Policy Policy `json:"policy"`
ChannelProperties map[string]string `json:"channel_properties"`
Description string `json:"description"`
}

type AndroidPayload struct {
DisplayType string `json:"display_type"`
Body AndroidBody `json:"body"`
}

type AndroidBody struct {
Ticker string `json:"ticker"`
Title string `json:"title"`
Text string `json:"text"`
AfterOpen string `json:"after_open"`
}

// iOS广播消息结构
type IOSBroadcastMessage struct {
AppKey string `json:"appkey"`
Timestamp string `json:"timestamp"`
Type string `json:"type"`
Payload IOSPayload `json:"payload"`
Policy Policy `json:"policy"`
Description string `json:"description"`
}

type IOSPayload struct {
Aps IOSAps `json:"aps"`
}

type IOSAps struct {
Alert IOSAlert `json:"alert"`
}

type IOSAlert struct {
Title string `json:"title"`
Subtitle string `json:"subtitle"`
Body string `json:"body"`
}

// 公共策略结构
type Policy struct {
ExpireTime string `json:"expire_time"`
}
Loading