快速接入微信小程序的订阅消息
官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/subscribe-message.html
2020年11月17日15:59:14 以下内容如有变更,请以官方文档为准。
小程序订阅消息
消息能力是小程序能力中的重要组成,我们为开发者提供了订阅消息能力,以便实现服务的闭环和更优的体验。
订阅消息推送位置:服务通知
订阅消息下发条件:用户自主订阅
订阅消息卡片跳转能力:点击查看详情可跳转至该小程序的页面
订阅消息包括两种:
一次性订阅消息
一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。
长期订阅消息
一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。
目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。
使用说明
步骤一:获取模板 ID
在微信公众平台手动配置获取模板 ID:
登录 https://mp.weixin.qq.com 获取模板,如果没有合适的模板,可以申请添加新模板,审核通过后可使用。
步骤二:获取下发权限
详见小程序端消息订阅接口 wx.requestSubscribeMessage
步骤三:调用接口下发订阅消息
详见服务端消息发送接口 subscribeMessage.send
注意事项
用户勾选 “总是保持以上选择,不再询问” 之后,下次订阅调用 wx.requestSubscribeMessage 不会弹窗,保持之前的选择,修改选择需要打开小程序设置进行修改。
快速接入
请先按照官文文档写的步骤开通好订阅消息模板。
1、小程序代码
<!--index.wxml--> <view class="container"> <text class="user-motto">{{openId}}</text> <button class="btn green" bindtap="onSubscribe" hover-class="btn-hover"> 订阅 </button> </view>
//index.js //获取应用实例 const app = getApp() Page({ data: { openId: \'\', userInfo: {}, hasUserInfo: false, canIUse: wx.canIUse(\'button.open-type.getUserInfo\') }, //事件处理函数 bindViewTap: function() { wx.navigateTo({ url: \'../logs/logs\' }) }, //订阅按钮事件 onSubscribe: function(e) { let that = this //申请订阅消息 模板IDXXX wx.requestSubscribeMessage({ tmplIds: [\'模板IDXXX\'], success(res) { let status = res[\'模板IDXXX\'] if(status === "reject"){ console.log("拒绝") //提示: 您拒绝了消息提示,后续将接收不到消息通知,打开小程序设置进行修改。 } console.log(that.openId) //发送测试消息 wx.request({ url: \'http://localhost:8080/api/wxmini/sendSubscribeMessage\', data: { openId: that.data.openId }, success(res){ console.log(res.data) } }) }}); }, onLoad: function () { let that =this //获取微信登录openId wx.login({ success (res) { //console.log(res.code) if (res.code) { //发起网络请求 wx.request({ url: \'http://localhost:8080/api/wxmini/jscode2session\', data: { code: res.code }, success(res){ console.log(res.data.openId) that.setData({ openId: res.data.openId }) } }) } else { console.log(\'登录失败!\' + res.errMsg) } } }) } })
package com.zhaojie.wechat.demo.controller; import com.zhaojie.wechat.demo.service.IWxService; import com.zhaojie.wechat.demo.vo.Jscode2sessionVo; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Api(tags = {"微信小程序"}) @Slf4j @Validated @RestController @RequestMapping("/api/wxmini") public class WxMiniController { @Autowired private IWxService wxService; @ApiOperation(value = "获取小程序用户的openid") @GetMapping("/jscode2session") @ApiImplicitParams({ @ApiImplicitParam(name = "code", value = "wx.login返回的code", required = true, dataType = "String") }) public Object jscode2session(@RequestParam String code) { Jscode2sessionVo jscode2sessionVo = wxService.jscode2session(code); return jscode2sessionVo; } @ApiOperation(value = "小程序订阅消息发送") @ApiImplicitParams({ @ApiImplicitParam(name = "openId", value = "接收者(用户)的 openid", required = true, dataType = "String") }) @GetMapping("/sendSubscribeMessage") public Object sendSubscribeMessage(@RequestParam String openId) { return "小程序订阅消息" + wxService.sendSubscribeMessage(openId); } }
package com.zhaojie.wechat.demo.service.impl; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.zhaojie.wechat.demo.common.Constant; import com.zhaojie.wechat.demo.service.IWxService; import com.zhaojie.wechat.demo.vo.Jscode2sessionVo; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; @Slf4j @Service public class WxServiceImpl implements IWxService { @Autowired private RestTemplate restTemplate; @Override public Jscode2sessionVo jscode2session(String code) { //登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。 String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + Constant.WX_APP_ID + "&secret=" + Constant.WX_SECRET + "&js_code=" + code + "&grant_type=authorization_code"; String forObject = restTemplate.getForObject(url, String.class); //返回的 JSON 数据包 //属性 类型 说明 //openid string 用户唯一标识 //session_key string 会话密钥 //unionid string 用户在开放平台的唯一标识符,在满足 UnionID 下发条件的情况下会返回,详见 UnionID 机制说明。 //errcode number 错误码 //errmsg string 错误信息 JSONObject result = JSONUtil.parseObj(forObject); Jscode2sessionVo jscode2sessionVo = new Jscode2sessionVo(); jscode2sessionVo.setOpenId(result.getStr("openid")); jscode2sessionVo.setErrmsg(result.getStr("errmsg")); jscode2sessionVo.setErrcode(result.getStr("errcode")); return jscode2sessionVo; } @Override public String getAccessToken() { try { // 线上不需要每次都去获取access_token,需要把它缓存起来。我这里仅测试就不缓存了!!! /*String redisAccessToken = redisSeConstantrvice.get(Constant.WX_ACCESS_TOKEN); if (StrUtil.isNotEmpty(redisAccessToken)) { return redisAccessToken; }*/ //获取小程序全局唯一后台接口调用凭据(access_token)。调用绝大多数后台接口时都需使用 access_token,开发者需要进行妥善保存。 String url = "https://api.weixin.qq.com/cgi-bin/token?appid=" + Constant.WX_APP_ID + "&secret=" + Constant.WX_SECRET + "&grant_type=client_credential"; String forObject = restTemplate.getForObject(url, String.class); //返回的 JSON 数据包 //属性 类型 说明 //access_token string 获取到的凭证 //expires_in number 凭证有效时间,单位:秒。目前是7200秒之内的值。 //errcode number 错误码 //errmsg string 错误信息 log.error("获取小程序全局唯一后台接口调用凭据,msg{}", forObject); JSONObject result = JSONUtil.parseObj(forObject); String accessToken = result.getStr("access_token"); /*if (StrUtil.isNotEmpty(accessToken)) { //存入Redis redisService.set(Constant.WX_ACCESS_TOKEN, accessToken, 7200 * 1000L); }*/ return accessToken; } catch (Exception e) { e.printStackTrace(); } return null; } @Override public String sendSubscribeMessage(String openId) { String accessToken = this.getAccessToken(); log.info("accessToken {}", accessToken); HttpHeaders httpHeaders = new HttpHeaders(); //注意这里要是json格式的提交 httpHeaders.setContentType(MediaType.APPLICATION_JSON); //我这里测试直接拼接的,请按照你自己模板的配置去改下面的json字符串 //官方文档:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html StringBuffer jsonParam = new StringBuffer(); jsonParam.append("{"); jsonParam.append("\"touser\": \"" + openId + "\","); jsonParam.append("\"template_id\": \"" + Constant.WX_TMPL_ID + "\","); jsonParam.append("\"page\": \"" + Constant.WX_TMPL_PAGE + "\","); jsonParam.append("\"data\": {"); jsonParam.append("\"name1\": { \"value\": \"zhaojie\"},"); jsonParam.append("\"thing13\": { \"value\": \"预约产品\"},"); jsonParam.append("\"date3\": { \"value\": \"2020年11月17日 16:59\"},"); jsonParam.append("\"phrase9\": { \"value\": \"预约中\"},"); jsonParam.append("\"thing7\": { \"value\": \"如有疑问请联系客服人员\"}"); jsonParam.append("}"); jsonParam.append("}"); HttpEntity<String> httpEntity = new HttpEntity<>(jsonParam.toString(), httpHeaders); ResponseEntity<String> entity = restTemplate.exchange("https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=" + accessToken, HttpMethod.POST, httpEntity, String.class); String result = entity.getBody(); log.info("result {}", result); return result; } }
package com.zhaojie.wechat.demo.common; /** * 常量 */ public class Constant { // 系统前缀 public static final String SYS_PREFIX = "WECHAT-DEMO:"; // 小程序全局唯一后台接口调用凭据(access_token)。调用绝大多数后台接口时都需使用 access_token,开发者需要进行妥善保存。 public static final String WX_ACCESS_TOKEN = SYS_PREFIX + "WX_ACCESS_TOKEN"; // 小程序订阅消息模板 改成自己的参数 public static final String WX_TMPL_ID = "XXXXX; public static final String WX_TMPL_PAGE = "pages/index/index"; // 小程序配置信息 改成自己的 public static final String WX_APP_ID = "XXXX"; public static final String WX_SECRET = "XXXX"; }
3、运行结果
注意未发布体验版无法接收到消息