【微信小程序】订阅消息
概述
微信小程序订阅消息允许开发者在用户主动触发订阅后,向用户发送服务通知。这有助于提升用户体验和业务转化率。订阅消息分为一次性订阅和长期订阅两种类型,开发者需根据业务需求合理选择。
微信小程序的订阅消息分“一次性订阅”和“长期订阅”两种方式:
1、一次性订阅就是要用户点击同意一次消息订阅,服务端才能发送一次信息。点击多少次就能发送多少次。
2、长期订阅。服务端可以无限,但是教育、交通、医疗等行业才有长期订阅。
流程实现
1、申请消息模版
- 登录微信公众平台,选择小程序登录,进入“设置”-“基础功能”-“订阅消息”-“订阅消息模板管理”。
- 点击“添加模板”,选择合适的模板并添加,获取模板ID。
2、获取用户订阅权限
- 在小程序端,引导用户订阅消息。可以通过
wx.requestSubscribeMessage
API请求用户授权订阅特定模板消息。
3、后台订阅微信消息(可选)
配置服务器地址:
找到“开发与服务”-“开发管理”-“消息推送”,开启消息推送,配置业务服务器地址。
配置的地址需要支持外网访问,并同时需要GET和POST两种方式的API
GET方式API被微信服务器回调,验证上面配置的签名,Token等信息:
public boolean checkSignature(String signature, String timestamp, String nonce) {
String[] array = new String[]{ token, timestamp, nonce};
//先对这三个字符串字典排序,然后拼接
Arrays.sort(array);
StringBuilder sb = new StringBuilder();
for (String s : array) {
sb.append(s);
}
//使用SHA-1算法对拼接字符串加密
MessageDigest messageDigest;
String hexStr = null;
try {
messageDigest = MessageDigest.getInstance("SHA-1");
byte[] digest = messageDigest.digest(sb.toString().getBytes());
//将加密后的字符串转换成16进制字符串
hexStr = CommonUtils.bytesToHexStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
//返回校验结果
return signature.equalsIgnoreCase(hexStr);
}
POST方法被微信回调,可以处理以下相关事件(微信文档):
- 当用户触发订阅消息弹框后,用户的相关行为事件结果会推送至开发者所配置的服务器地址
- 当用户在手机端服务通知里消息卡片右上角“...”管理消息时,相应的行为事件会推送至开发者所配置的服务器地址
- 调用订阅消息接口发送消息给用户的最终结果,会推送下发结果事件至开发者所配置的服务器地址
业务服务器可以将用户订阅消息的状态做记录,后期发送消息时,可以过滤掉没有接受订阅的用户,或通过其他手段对该部分用户单独处理。
@PostMapping("/subscribeMessageCallback")
public R<String> handleSubscribeMessageCallback(HttpServletRequest request, HttpServletRequest httpRequest) {
BufferedReader reader;
StringBuilder requestContent = new StringBuilder();
//消息类型
try {
reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
requestContent.append(line);
}
reader.close();
String jsonStr = requestContent.toString();
log.info("wechat callback message=[{}]", jsonStr);
JSONObject rootObject = JSONUtil.parseObj(jsonStr);
// 在这里处理你的订阅消息事件,例如获取OpenID、模板ID和事件数据等
String openId = rootObject.getStr("FromUserName");
// 注意:这里的MsgType可能需要根据实际情况调整,因为订阅消息的回调中可能没有直接的MsgType字段,而是其他表示事件类型的字段
String eventType = rootObject.getStr("MsgType");
if("event".equals(eventType)){
String event = rootObject.getStr("Event");
String list = rootObject.getStr("List");
// 小程序用户接受,或改变订阅状态事件
if("subscribe_msg_popup_event".equals(event)
|| "subscribe_msg_change_event".equals(event)){
//小程序用户是否确定接受订阅消息事件
if(StrUtil.isNotBlank(list)){
JSONArray listArray = JSONUtil.parseArray(list);
listArray.forEach(item -> {
JSONObject jsonObject = JSONUtil.parseObj(item);
String subscribeStatusString = jsonObject.getStr("SubscribeStatusString");
String templateId = jsonObject.getStr("TemplateId");
// 判断用户是否接受订阅消消息
boolean accept = "accept".equals(subscribeStatusString);
MiniSubscribeUser plaMiniSubscribeUser = new MiniSubscribeUser();
plaMiniSubscribeUser.setOpenId(openId);
plaMiniSubscribeUser.setTemplateId(templateId);
plaMiniSubscribeUser.setAcceptState(accept ? "1" : "2");
plaMiniSubscribeUserService.saveOrUpdateSubscribeUser(plaMiniSubscribeUser);
});
}
} else if ("subscribe_msg_sent_event".equals(event)) {
//小程序订阅消息是否发送成功事件
/**
* "List": {
* "TemplateId": "BEwX0BO-T3MqK3Uc5oTU3CGBqzjpndk2jzUf7VfExd8",
* "MsgID": "1864323726461255680",
* "ErrorCode": "0",
* "ErrorStatus": "success"
* }
*/
JSONArray listArray = JSONUtil.parseArray(list);
listArray.forEach(item -> {
JSONObject jsonObject = JSONUtil.parseObj(item);
String msgId = jsonObject.getStr("MsgID");
String templateId = jsonObject.getStr("TemplateId");
String errorCode = jsonObject.getStr("ErrorCode");
String errorStatus = jsonObject.getStr("ErrorStatus");
log.info("小程序用户{},发送{}消息结果{}", openId, templateId, errorStatus);
});
}
}
// 返回给微信服务器的响应(通常是一个简单的成功响应)
return R.ok("success");
} catch (Exception e) {
e.printStackTrace();
return R.fail(HttpStatus.BAD_REQUEST, e.getMessage());
}
}
4、实现发送订阅消息的接口
在需要发送订阅消息的地方,调用微信提供的发送订阅消息接口。
POST https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN
这通常涉及到调用微信的API,传递必要的参数如模板ID、用户OpenID、消息内容等。封装一个消息发送的对象:
public class WeChatSubscribeMsg {
/**
* GQ7x0v7gelxp7C8AmT2EcJMmop8evA8P2qSiIr7QEyw
* {
* "touser": "OPENID",
* "template_id": "TEMPLATE_ID",
* "page": "index",
* "data": {
* "name01": {
* "value": "某某"
* },
* "amount01": {
* "value": "¥100"
* },
* "thing01": {
* "value": "广州至北京"
* } ,
* "date01": {
* "value": "2018-01-01"
* }
* }
* }
*/
private String touser;
private String userId;
private String template_id;
private String page;
private TreeMap<String,DataValue> data;
@Data
public static class DataValue{
public String value;
}
根据小程序appid和secret 获取小程序token 发送消息:
public Boolean sendSubscribeMessage(WeChatSubscribeMsg weChatSubscribeMsg) {
String touser = weChatSubscribeMsg.getTouser();
String templateId = weChatSubscribeMsg.getTemplate_id();
MiniSubscribeUser miniSubscribeUser = getMiniSubscribeUser(templateId, touser);
if (miniSubscribeUser == null ) {
log.info("用户未关注小程序");
return false;
}
if(miniSubscribeUser.getAcceptState().equals("2")){
log.info("用户{}已拒绝接收订阅消息", touser);
return false;
}
String postData = JSONUtil.toJsonStr(weChatSubscribeMsg);
val accessToken = wechatService.getMiniAccessToken();
String uniformSend = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN";
uniformSend = uniformSend.replace("ACCESS_TOKEN", accessToken);
log.info("send Wechat subscribe msg Url :{} postData:{} ", uniformSend, postData);
if (null != postData) {
//http 调用第三方接口
String result = HttpUtil.post(uniformSend, postData);
log.info("send wechat subscribe msg result:" + result);
JSONObject jsonObject = JSONUtil.parseObj(result);
if (jsonObject.getInt("errcode") == 0) {
return true;
}
}
return false;
}