快速接入微信小程序的訂閱消息
官方文檔: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、運行結果
注意未發布體驗版無法接收到消息
