给用户发送通知消息是小程序以及微信公众号中用户运营的一种重要的手段。在小程序中使用订阅消息给用户发送通知。在订阅消息之前给用户发送通知是通过模板消息来实现的,模板消息对用户来说是被动发送的,贸然推送的消息很有可能对用户构成骚扰,目前微信平台已经下线了小程序模板消息的功能。
在小程序中使用订阅消息给用户发送通知,不同于公众号的模板消息,订阅消息将选择权交到用户手中,由用户来决定是否订阅。只有当用户订阅时,才会收到小程序的推送信息。订阅消息分为两种类型:
登录微信小程序管理后台,在【功能】->【订阅消息】->【我的模板】页面中查看已经选用模板消息,如果【我的模板】中没有模板,您可以在【公共模板】页面中搜索并选用合适的模板。如果没有合适的模板消息,也可以申请添加新模板,新添加的模板需审核通过后方可使用。
客户端访问wx.requestSubscribeMessage(Object object)函数显示小程序消息订阅界面,并返回用户订阅的操作结果。当用户勾选了订阅面板中的【总是保持以上选择,不再询问】时,订阅消息会被添加到用户的小程序设置页。
服务端调用接口subscribeMessage.send向指定OpenID的用户发送指定的模板ID的订阅消息。
关于订阅消息的使用的一点建议:
客户端访问:wx.requestSubscribeMessage(Object object)显示小程序消息订阅界面,返回用户订阅的操作结果。wx.requestSubscribeMessage的调用参数如下:
wx.requestSubscribeMessage({
tmplIds: [
'EteT4AMkuCazMpHQefLNr3nBVmI58KTRvTlLM3fUGe8',
'IYsLAX80LFhclmxNdFUbGkuAliSsQyxv9CR9caq_Qdk'
],
success (res) { },
fail(err){
console.log('err',err)
}
})
access_token 是小程序后台接口调用凭据,大多数后台接口调用时都要用到access_token 。开发者应在后端服务器使用getAccessToken获取 access_token,并进行妥善保存。
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
正常情况下,微信会返回下述JSON数据包:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):
{"errcode":40013,"errmsg":"invalid appid"}
//获取Access token的返回结果
type WxApiTokenReturn struct {
ErrCode int64 `json:"errcode"`
ErrMsg string `json:"errmsg"`
AccessToken string `json:"access_token"`
ExpiresIn int64 `json:"expires_in"`
}
//获取Access token
func GetWxTokenFromWx(appid string, secret string) (string, error) {
wx_api_url := "https://api.weixin.qq.com/cgi-bin/token"
wx_api_url = fmt.Sprintf("%s?grant_type=client_credential&appid=%s&secret=%s", wx_api_url, appid, secret)
resp, err := http.Get(wx_api_url)
if err != nil {
return "", err
}
defer resp.Body.Close()
ret_user, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
var ent WxApiTokenReturn
if err := json.Unmarshal(ret_user, &ent); err != nil {
return "", err
}
return ent.AccessToken, nil
}
access_token 的有效期目前为 2个小时,需定时刷新。另外重复获取将导致上次获取的access_token失效。建议开发者使用中控服务器统一获取和刷新access_token,服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务。以下代码是中控服务中的access_token管理相关的代码:
var tm_token int64 = 0
var wx_token string = ""
var lock_token sync.RWMutex
func GetWxToken() string {
lock_token.Lock()
defer lock_token.Unlock()
tm := time.Now().Unix()
if tm -tm_token > 3600 {
app_id:= "xxx"//替换为您的小程序APPID
app_secret:= "xxx"//替换为您的小程序密钥
wx_token, _ = GetWxTokenFromWx(app_id, app_secret)
tm_token = time.Now().Unix()
}
return wx_token
}
在获取access_token的函数中使用了Go语言的读写锁,防止并发请求造成access_token的失效。
补充说明:
服务端调用subscribeMessage.send接口向指定用户(OpenID)发送订阅消息。subscribeMessage.send的调用参数如下:
type TmplMsgReq struct {
Touser string `json:"touser"`
TmplId string `json:"template_id"`
VState string `json:"miniprogramState"`
PageUri string `json:"page"`
MsgData map[string] TmplMsgField `json:"data"`
}
type TmplMsgField struct {
Value string `json:"value"`
}
//发送微信订阅消息
func SendTmplMsgCall(token string, data []byte) (bool, error) {
wx_addr := "https://api.weixin.qq.com/cgi-bin/message/subscribe/send"
wx_addr += "?access_token=" + token
postReq, err := http.NewRequest("POST", wx_addr, bytes.NewReader(data))
if err != nil {
return false, err
}
postReq.Header.Set("Content-Type", "application/json; encoding=utf-8")
client := &http.Client{}
resp, err := client.Do(postReq)
defer resp.Body.Close()
if err != nil {
return false, err
}
_, err = ioutil.ReadAll(resp.Body)
if err != nil {
return false, err
}
return true, nil
}
应该为每个模板编写独立的消息发送函数,主要时因为每个模板的变量名称、变量的数量基本上都不相同。下面是一个模板消息发送的示例,首先从小程序管理后台获取模板的定义:
模板ID:xxxxxx
标题:问诊提醒
问诊医生:{{name3.DATA}}
提醒内容:{{thing2.DATA}}
然后实现该模板的发送函数:
//发送咨询提醒模板消息
func SendTmplMsg(name string, openid string, page_uri string) (bool, error) {
var data_req TmplMsgReq
data_req.Touser = openid
data_req.PageUri = page_uri
data_req.TmplId = "xxxxxx"//替换为您的模板ID
data_req.MsgData = make(map[string]TmplMsgField)
data_req.MsgData["thing1"] = TmplMsgField{"咨询提醒"}
data_req.MsgData["thing2"] = TmplMsgField{fmt.Sprintf("您的客户:%s提交了一个咨询", name)}
data_json, _ := json.Marshal(data_req)
token := GetWxToken()
return SendTmplMsgCall(token, data_json)
}
代码中的name是发送用户的姓名,openid是该用户的OpenID,page_uri是点击模板卡片后的跳转的目标页面。