golang 订阅消息提醒
这里不只于 微信平台, 还有QQ平台.
这里说一下大致的思路吧, 按着平台的文档就差不错可以了,
- 获取
access_token
- 获取模板id 和模板的字段名字
- 装配结构体
- 发送请求
下面是代码实现例子
基础代码
// 对内置的 http的 Post 和 Get 做了一个简单的封装
func HttpPost(url, params string, contentType string, retMap interface{}) error {
if contentType == "" {
contentType = "application/json"
// "application/x-www-form-urlencoded"
}
resp, err := http.Post(url,
contentType,
strings.NewReader(params))
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
err = json.Unmarshal(body, retMap)
if err != nil {
return err
}
return nil
}
func HttpGet(url string, retMap interface{}) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
err = json.Unmarshal(body, retMap)
if err != nil {
return err
}
return nil
}
微信
var _tokenMap sync.Map = sync.Map{}
type storeTokenKV struct {
Token string
Expires int64
CreateTime int64
}
type WechatTokenRetMap struct {
Token string `json:"access_token"`
Expires int64 `json:"expires_in"`
Errcode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
func getTokenWechat(appid, appSecret string, retMap *WechatTokenRetMap) error {
url := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
getUrl := fmt.Sprintf(url, appid, appSecret)
err := HttpGet(getUrl, retMap)
if err != nil {
return err
}
if retMap.Errcode != 0 {
return errors.New(fmt.Sprintf("code:%d,errmsg:%s", retMap.Errcode, retMap.ErrMsg))
}
return nil
}
func checkAndGetTokenWechat(key, appid, appSecret string) (error, *storeTokenKV) {
if val, ok := _tokenMap.Load(key); !ok {
retToken := &WechatTokenRetMap{}
err := getTokenWechat(appid, appSecret, retToken)
if err != nil {
return err, nil
}
d := &storeTokenKV{
Token: retToken.Token,
Expires: retToken.Expires,
CreateTime: time.Now().Unix(),
}
_tokenMap.Store(key, d)
return nil, d
} else {
d := val.(*storeTokenKV)
t := time.Now().Unix() - d.CreateTime + 60
if t >= d.Expires {
retToken := &WechatTokenRetMap{}
err := getTokenWechat(appid, appSecret, retToken)
if err != nil {
return err, nil
}
d := &storeTokenKV{
Token: retToken.Token,
Expires: retToken.Expires,
CreateTime: time.Now().Unix(),
}
_tokenMap.Store(key, d)
return nil, d
} else {
return nil, d
}
}
}
type WechatSubscribeRetMap struct {
Token string `json:"access_token"`
Expires int `json:"expires_in"`
Errcode int `json:"errcode"`
ErrMsg string `json:"errmsg"`
}
func SendSubscribeWechat(appid, appSecret, projName, platform string, data interface{}, retMap *WechatSubscribeRetMap) error {
err, retToken := checkAndGetTokenWechat(fmt.Sprintf("%s-%s", projName, platform), appid, appSecret)
if err != nil {
return nil
}
fmt.Println("token ==>", retToken)
url := fmt.Sprintf("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=%s", retToken.Token)
dJson, _ := json.Marshal(data)
sJson := string(dJson)
err = HttpPost(url, sJson, "", retMap)
if err != nil {
return err
}
return nil
}
调用列子
我这里使用的结构体转配的 你可以使用 map 这种的更方便.
type ValS struct {
Value string `json:"value"`
}
type SignInTT struct {
V1 ValS `json:"phrase1"` // 签到状态
V2 ValS `json:"date4"` // 通知日期
V3 ValS `json:"thing7"` // 功能名称
V4 ValS `json:"thing8"` // 功能描述
}
type SubScribeSendS struct {
Touser string `json:"touser"`
TemplateId string `json:"template_id"`
Page string `json:"page"`
Data SignInTT `json:"data"`
}
func test() {
d := &common.WechatSubscribeRetMap{}
sTime := time.Now()
hLog.Debug("======================", item.TemplateName, item.OpenId)
err := common.SendSubscribeWechat("appid", "秘钥", "根据的项目定义这个字段", "根据的项目定义这个字段", &SubScribeSendS{
Touser: "<用户的Openid>",
TemplateId: "<微信开发平台的模板id>",
Page: "<跳转路径>", // 这里配置了跳转路径会点击消息模板直接进入游戏, 建议配置 // 例如: index
Data: SignInTT{
V1: ValS{
Value: "未签到",
},
V2: ValS{
Value: sTime.Format("2006年01月02日"),
},
V3: ValS{
Value: "七日签到",
},
V4: ValS{
Value: "累计签到获取奖励",
},
},
}, d)
// 用map 模拟那个data结构体
d := map[string]interface{} {
"Touser": "<用户的Openid>",
"TemplateId": "<微信开发平台的模板id>",
"Page": "<跳转路径>", // 这里配置了跳转路径会点击消息模板直接进入游戏, 建议配置 // 例如: index
"Data": map[string]interface{} {
"phrase1": map[string]interface{} {
"value": 未签到,
},
// 其他的字段类似
},
}
我这里更新一下QQ平台的订阅消息
这里说明一下 QQ 平台的订阅消息和 微信的 调用方式基本保持一致, 这里唯一不同有两点
1.请求的地址不一样
2.请求订阅消息传参的格式不一样
上面的第一点我就不多说了. 主要说一下第二点
因为第二点 QQ小游戏
官方文档说的不清晰需要调试, 而且一个账号一天只能发送一个订阅消息.
QQ 订阅消息传递的结构体如下.
在传递Data这个字段的时候 微信是指定 对象里面的 key, QQ 是直接使用 订阅消息模板里面的关键字作为key
d:= map[string]interface{}{
"touser": "3B77F77EC13BA7243DD53BD4BF632CAD",
"template_id": "8de968e05b257800a0833f36d16ca5f4",
"page": "index?env=s&type=signIn",
"data": map[string]interface{}{
"离线收益": map[string]interface{}{
"value": "未签到",
},
"通知日期": map[string]interface{}{
"value": sTime.Format("2006年01月02日"),
},
"通知时间": map[string]interface{}{
"value": "七日签到",
},
"温馨提示": map[string]interface{}{
"value": "累计签到获取奖励",
},
},
}