需求背景:小程序下单完成之后,通过微信公众号给关注的用户推送信息、小程序和公众号的同一ID为unionId
前置条件
(1)在公众号开放平台绑定小程序和公众号是同一主体【公众账号和小程序】
(2)开通服务号模版消息接口授权使用(一般需要两到三天)
(3)公众号配置通知地址
提交配置的时候,可能会提示token无效,那是因为接口验证没有通过、代码验签实现如下:
【因为接收事件推送消息的数据包是XML格式,所以需要倒入解析的jar包】
- 接收的数据格式
<xml>
<ToUserName>< ![CDATA[toUser] ]></ToUserName>
<FromUserName>< ![CDATA[FromUser] ]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType>< ![CDATA[event] ]></MsgType>
<Event>< ![CDATA[subscribe] ]></Event>
</xml>
- 导入的jar包
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
代码实现
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.DocumentHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
/**
* @version 1.0
* @description: 公众号订阅通知
*/
@RestController
public class SubscribeNoticeController {
private static final Logger log = LoggerFactory.getLogger(SubscribeNoticeController.class);
/**
* @GetMapping 和 @PostMapping 路径名称是一样的,一个用来验证token、一个用来接收数据
*/
/**
* token 验签
*/
@GetMapping("/noticeInfo")
Long checkParams(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr){
String token = "和你在公众号配置中的token保持一致";
String[] arr = {token, timestamp, nonce};
Arrays.sort(arr);
StringBuilder content = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
content.append(arr[i]);
}
//sha1Hex 加密
MessageDigest md = null;
String temp = null;
try {
md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(content.toString().getBytes());
temp = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
if ((temp.toLowerCase()).equals(signature)){
/**
* 重点:要把接收的这个随机码返回
*/
return Long.valueOf(echostr);
}
return null;
}
/**
* 接收推送的数据
*/
@PostMapping("/noticeInfo")
@ResponseBody
public String subscribeProcessor(HttpServletRequest request) throws Exception {
BufferedReader br = request.getReader();
String str="";
String paramInfo = "";
while((str = br.readLine()) != null){
paramInfo += str;
}
if (StringUtils.isNotBlank(paramInfo)){
JSONObject jsonObject = JSON.parseObject(paramInfo);
String xmlInfo = jsonObject.getString("xmlInfo");
paramInfo = xmlInfo;
}
log.info("微信公众号接收信息:{}",paramInfo);
paramInfo = paramInfo.replaceAll(" ", "");
Document document = DocumentHelper.parseText(paramInfo);
Element root = document.getRootElement();
String toUserName = root.elementText("ToUserName");
String fromUserName = root.elementText("FromUserName");
String createTime = root.elementText("CreateTime");
String msgType = root.elementText("MsgType");
String event = root.elementText("Event");
log.info("微信公众号接收信息:{},{},{},{},{}",toUserName,fromUserName,createTime,msgType,event);
/**
* 处理后续的逻辑信息...
*/
return null;
}
private static String byteToStr(byte[] byteArray){
String strDigest = "";
for (int i = 0; i < byteArray.length; i++) {
strDigest += byteToHexStr(byteArray[i]);
}
return strDigest;
}
private static String byteToHexStr(byte mByte){
char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A','B', 'C', 'D', 'E', 'F' };
char[] tempArr = new char[2];
tempArr[0] = Digit[(mByte >>> 4)& 0X0F];
tempArr[1] = Digit[mByte & 0X0F];
String s = new String(tempArr);
return s;
}
}
至此就可以接收公众号推送的信息了、比如用户的订阅、取关事件都能有通知。
实现推送消息给用户
前面代码已经可以监听到用户的订阅、取关的事件了,因为可以通过用户订阅的事件拿到用户的FromUserName(用户的openId)信息、通过这个字段,就能查到用户的unionId、然后把openId和unionId保存到数据库、相关实现方法:
/**
* 获取AccessToken
* @return
*/
public static String obtainAccessToken(){
String G_APPID = “公众号的APPID”;
String G_SECRET = “公众号的SECRET”;
String tokenData = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+G_APPID+"&secret="+G_SECRET;
// 返回的用户信息json字符串
JSONObject jsonObject = httpRequestUtil(tokenData);
return String.valueOf(jsonObject.get("access_token"));
}
/**
* 获取订阅用户的openid和unionid
* @param accessToken
* @param openId
* @return
*/
public static OpenUnionIdRelationDO obtainUserDetail(String accessToken,String openId){
String openInfoUrl = "https://api.weixin.qq.com/cgi-bin/user/info?access_token="+accessToken+"&openid="+openId+"&lang=zh_CN";
JSONObject userDetail = httpRequestUtil(openInfoUrl);
String openid = userDetail.getString("openid");
Integer subscribeTime = userDetail.getInteger("subscribe_time");
String unionid = userDetail.getString("unionid");
OpenUnionIdRelationDO openDO = new OpenUnionIdRelationDO();
openDO.setOpenId(openid);
openDO.setUnionId(unionid);
// 时间类型转换 10位时间戳转 yyyy-MM-dd HH:mm:ss
String st = "";
if (null != subscribeTime){
st = DateUtils.timestampToString(subscribeTime);
}
openDO.setSubscribeTime(st);
openDO.setCreateTime(new Date());
return openDO;
}
给用户推送信息
前面已经维护好了用户的公众号ID和公众号小程序的一致的unionid,小程序就可以通过unionid查询数据库中的openid给公众号用户推送信息了
/**
* 给关注该公众好的用户发送信息
* @throws Exception
*/
public static void sendTemplateInfo() throws Exception{
//获取小程序accessToken
String accessToken = obtainAccessToken();
//消息推送接口
String path = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + accessToken;
//封装参数
JSONObject jsonData = new JSONObject();
jsonData.put("touser", "公众号openid");
jsonData.put("template_id", "公众号消息模板id");
JSONObject miniprogram = new JSONObject();
miniprogram.put("appid","小程序ID");
miniprogram.put("pagepath","小程序调转路径");
jsonData.put("miniprogram",miniprogram);
JSONObject data = new JSONObject();
JSONObject first = new JSONObject();
first.put("value","测试购票支付详情!");
first.put("color","#173177");
JSONObject keyword1 = new JSONObject();
keyword1.put("value","2112323832748239");
keyword1.put("color","#173177");
JSONObject keyword2 = new JSONObject();
keyword2.put("value","2022-12-22 00:00:00");
keyword2.put("color","#173177");
JSONObject keyword3 = new JSONObject();
keyword3.put("value","199.56元");
keyword3.put("color","#173177");
JSONObject keyword4 = new JSONObject();
keyword4.put("value","微信支付");
keyword4.put("color","#173177");
JSONObject remark = new JSONObject();
remark.put("value","温馨提示:请不要爽约哦!");
remark.put("color","#173177");
data.put("first",first);
data.put("keyword1",keyword1);
data.put("keyword2",keyword2);
data.put("keyword3",keyword3);
data.put("keyword4",keyword4);
data.put("remark",remark);
jsonData.put("data",data);
HttpUtil.post(path, jsonData.toJSONString());
}
效果展示: