• 微信小程序开发实战7 小程序订阅消息


    订阅消息介绍

    给用户发送通知消息是小程序以及微信公众号中用户运营的一种重要的手段。在小程序中使用订阅消息给用户发送通知。在订阅消息之前给用户发送通知是通过模板消息来实现的,模板消息对用户来说是被动发送的,贸然推送的消息很有可能对用户构成骚扰,目前微信平台已经下线了小程序模板消息的功能。
    在小程序中使用订阅消息给用户发送通知,不同于公众号的模板消息,订阅消息将选择权交到用户手中,由用户来决定是否订阅。只有当用户订阅时,才会收到小程序的推送信息。订阅消息分为两种类型:

    • 一次性消息推送
      用户订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。
    • 长期订阅消息
      一次性订阅消息可满足大部分小程序的需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态多次发送消息提醒。为便于服务用户,微信平台提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。
      相对于原来的模板消息,订阅消息有以下特点和优势:
    • 选择权在用户手中
      在小程序里,订阅消息像是一个开关,需要用户主动点击授权之后,开发者才能向其推送服务通知。
    • 时长不受限制
      订阅消息没有7天内推送消息的时间限制,只要用户没有主动拒收消息推送,就可以随时推送服务通知。
    • 安全系数更高
      原来的模板消息,用户被动接收,更容易被投诉、举报,封禁功能的风险系数极高。而订阅消息恰恰相反,订阅消息由用户主动订阅,即用户愿意接收由小程序发送的消息,更符合微信的绿规。

    订阅消息使用流程说明

    步骤一:为小程序配置模板

    登录微信小程序管理后台,在【功能】->【订阅消息】->【我的模板】页面中查看已经选用模板消息,如果【我的模板】中没有模板,您可以在【公共模板】页面中搜索并选用合适的模板。如果没有合适的模板消息,也可以申请添加新模板,新添加的模板需审核通过后方可使用。
    在这里插入图片描述

    步骤二:获取下发权限

    客户端访问wx.requestSubscribeMessage(Object object)函数显示小程序消息订阅界面,并返回用户订阅的操作结果。当用户勾选了订阅面板中的【总是保持以上选择,不再询问】时,订阅消息会被添加到用户的小程序设置页。
    在这里插入图片描述

    步骤三:调用接口下发订阅消息
    服务端调用接口subscribeMessage.send向指定OpenID的用户发送指定的模板ID的订阅消息。
    
    • 1

    关于订阅消息的使用的一点建议:

    • 关于订阅时机
      让用户在需要用到消息的时候,触发订阅机制,而不要让用户一打开小程序就进行订阅。
    • 关于订阅内容
      引导用户订阅跟用户当前的服务相关的订阅消息,其他暂时用不到的模板等用户用到之后再订阅,以免用户产生误解而取消订阅。

    消息订阅

    客户端访问:wx.requestSubscribeMessage(Object object)显示小程序消息订阅界面,返回用户订阅的操作结果。wx.requestSubscribeMessage的调用参数如下:

    • tmplIds:需要订阅的模板的id的集合,一次调用最多可订阅3条消息。
    • success:接口调用成功的回调函数
    • fail:接口调用失败的回调函数
    • complete:接口调用结束的回调函数(调用成功、失败都会执行)
      示例代码:
    wx.requestSubscribeMessage({
          tmplIds: [
            'EteT4AMkuCazMpHQefLNr3nBVmI58KTRvTlLM3fUGe8',
            'IYsLAX80LFhclmxNdFUbGkuAliSsQyxv9CR9caq_Qdk'
          ],
          success (res) { },
          fail(err){
            console.log('err',err)
          }
     })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    获取access_token

    access_token 是小程序后台接口调用凭据,大多数后台接口调用时都要用到access_token 。开发者应在后端服务器使用getAccessToken获取 access_token,并进行妥善保存。

    接口访问地址

    https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

    请求参数
    • grant_type:填写 client_credential
    • appid:小程序唯一凭证,即 AppID,可在【微信公众平台】 -【设置】 -【开发设置】页中获得。
    • secret:小程序唯一凭证密钥,即 AppSecret,获取方式同 appid。
    • access_token:获取到的凭证
    • expires_in:凭证有效时间,单位:秒。目前是7200秒之内的值。
    • errcode:错误码
    • errmsg:错误信息
    返回说明

    正常情况下,微信会返回下述JSON数据包:

    {"access_token":"ACCESS_TOKEN","expires_in":7200}
    
    • 1

    错误时微信会返回错误码等信息,JSON数据包示例如下(该示例为AppID无效错误):

    {"errcode":40013,"errmsg":"invalid appid"}
    
    • 1
    获取access_token的示例代码
    //获取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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    access_token的存储与更新

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在获取access_token的函数中使用了Go语言的读写锁,防止并发请求造成access_token的失效。
    补充说明:

    • access_token 的有效期通过返回的 expires_in 来传达,目前是7200秒之内的值,中控服务器需要根据这个有效时间提前去刷新。在刷新过程中,中控服务器可对外继续输出的老 access_token,此时公众平台后台会保证在5分钟内,新老 access_token 都可用,这保证了第三方业务的平滑过渡;
    • access_token 的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新 access_token 的接口,这样便于业务服务器在API调用获知 access_token 已超时的情况下,可以触发 access_token 的刷新流程。

    发送订阅消息

    服务端调用subscribeMessage.send接口向指定用户(OpenID)发送订阅消息。subscribeMessage.send的调用参数如下:

    • access_token:接口调用凭证
    • touser:接收者(用户)的OpenID
    • template_id:所需下发的订阅模板ID
    • page:点击模板卡片后的跳转页面,仅限本小程序内的页面。支持带参数,(示例index?foo=bar)。该字段不填则模板无跳转。
    • data:模板内容,格式形如 { “key1”: { “value”: any }, “key2”: { “value”: any } }
    • miniprogram_state:跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
    • lang:进入小程序查看”的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN
      以下是调用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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    应该为每个模板编写独立的消息发送函数,主要时因为每个模板的变量名称、变量的数量基本上都不相同。下面是一个模板消息发送的示例,首先从小程序管理后台获取模板的定义:

    模板ID:xxxxxx
    标题:问诊提醒
    问诊医生:{{name3.DATA}}
    提醒内容:{{thing2.DATA}}
    
    • 1
    • 2
    • 3
    • 4

    然后实现该模板的发送函数:

    //发送咨询提醒模板消息
    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    代码中的name是发送用户的姓名,openid是该用户的OpenID,page_uri是点击模板卡片后的跳转的目标页面。

  • 相关阅读:
    【Rust 笔记】15-字符串与文本(上)
    06JVM_类加载器
    统信浏览器kerberos配置方案
    带你一起理解什么是数据库分片?
    MyBatis Mapper映射器
    我开发了一个下载器 带宽拉满
    子序列宽度之和
    图形学-向量基础与应用
    前端工作小结80-title写活
    【快速入门】JVM之类加载机制与Native
  • 原文地址:https://blog.csdn.net/gz_hm/article/details/127138418