洛塔服务号回复010获取代码。
公众号订阅通知这个功能,微信本来打算替代掉模板消息和一次性订阅的,最后也没替代掉,成为单独的一个功能。
个人感觉和一次性订阅是没有太大区别的,只不过增加了一个长期订阅,但这个不是一般账号能申请下来的,所以整体来说使用也没有太大区别。
/**
* 完整项目源码可关注公众号"lootaayun"(洛塔),回复010获取
*/
@GetMapping("wx10")
public void wxGet(HttpServletRequest request, PrintWriter pw) {
// 微信加密签名,需要使用本地计算出来的和这个对比,确认是微信发送的消息
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp"); // 时间戳
String nonce = request.getParameter("nonce"); // 随机数
String echostr = request.getParameter("echostr"); // 随机字符串
// 将token、timestamp、nonce三个参数进行字典序排序
List<String> list = new ArrayList<String>();
list.add("lootaa"); // 公众号后台设置的token
list.add(timestamp);
list.add(nonce);
Collections.sort(list);
// 将三个参数字符串拼接成一个字符串进行sha1加密
String tokenStr = "";
for (int i = 0; i < list.size(); i++) {
tokenStr += list.get(i);
}
String signatureStr = DigestUtils.sha1Hex(tokenStr);
// 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if (signature.equals(signatureStr)) {
pw.write(echostr); // 原样返回echostr参数内容
} else {
pw.write("");
}
}
后台订阅通知部分可以选择对应的模板,是否选择参数以及参数顺序均可变更。有时候会有小坑,比如下图这个模板示例,如果原样写测试代码会失败,原因是thing的长度不能超过20个字符,但是demo样例里面显然已经超过了。
按照官方文档的说明,订阅有两种方式,一种是公众号图文消息直接插入订阅通知组件,一种是使用开放标签能力,在自己的网页中添加。开放标签能力这个需要熟悉前端开发,这个我不擅长,后面会单独写一篇关于开放标签的简单实现。图文消息插入的订阅通知组件在编辑页面的最上方位置。
和之前写的公众号接收事件推送的一致,只不过对应的Event值不同。再安全模式下,接收的代码为
@PostMapping("wx10")
public void wxPost(HttpServletRequest request, HttpServletResponse response, PrintWriter pw) throws Exception {
String token = "lootaa";
String encodingAesKey = "FpKEYJDuwK92k2juU2z0sUvTmc3hB4W5wGLJEKay8oK";
String appid = "wx276049d6a7551dca";
WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appid);
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String msgSignature = request.getParameter("msg_signature");
Document doc = getDocument(request);
String result2 = pc.decryptMsg(msgSignature, timestamp, nonce, doc.asXML());
System.out.println("解密后明文: " + result2);
JSONObject resultJson = documentToJSONObject(result2);
String messageType = resultJson.getString("MsgType"); //这个值如果是event表示是事件推送;如果是其他字符,参照Test004
if(Objects.equals("event", messageType)) {
String event = resultJson.getString("Event"); //这个是事件的具体类型
if(Objects.equals(event, "subscribe_msg_popup_event")) { // 订阅消息
System.out.println("用户订阅后进入这里,后面会完善这一行代码");
pw.write(nonce);
} else if(Objects.equals(event, "subscribe_msg_popup_event")) { // 发送订阅通知
System.out.println("发送订阅通知的事件");
pw.write(nonce);
} else if(Objects.equals(event, "subscribe_msg_change_event")) { // 用户管理订阅通知
System.out.println("如果用户之前订阅了,然后管理里面点击了拒绝,会到这里。根据具体业务调整推送逻辑");
pw.write(nonce);
}
}
}
发送订阅通知的时候,需要几个参数
为了方便测试,我将发送订阅通知直接放到了收到事件推送里面,也就是用户订阅–>收到事件推送–>直接将通知发送出去,只是为了测试,真实场景不会这样。
发送部分的代码为
// SubscribeMsgPopupEvent 列表中可以单独管理各个模板推送,这里直接演示收到之后将消息发送出去
// 真实场景肯定是先保存,然后在合适的时机执行下面推送的代码
String openid = resultJson.getString("FromUserName");
// 先获取access_token,这部分正式环境需要配置定时获取,每天2000次调用限制
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + SECRET;
String result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
System.out.println(result);
String accessToken = JSON.parseObject(result).getString("access_token");
// 获取公众号的自动回复规则
url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/bizsend?access_token=" + accessToken;
JSONObject param = new JSONObject();
param.put("touser", openid); //接收用户的openid
param.put("template_id", "dnemOr1oZ7XLQApxzBaZFtwYJxfdYzvbVS5hjZyW4KI"); //可以为事件推送的模板id,这里测试直接使用后台配置好的
param.put("page", "https:///m0_58095675");
JSONObject miniprogram = new JSONObject();
miniprogram.put("appid", "wxa3b096d8546b270d");
miniprogram.put("pagepath", "pages/station/station");
param.put("miniprogram", miniprogram);
JSONObject data = new JSONObject();
JSONObject thing1 = new JSONObject();
thing1.put("value", "20个以内字符。");
data.put("thing1", thing1);
JSONObject thing2 = new JSONObject();
thing2.put("value", "超过了会报错47003。");
data.put("thing2", thing2);
param.put("data", data);
result = Jsoup.connect(url).ignoreContentType(true).method(Method.POST).requestBody(param.toString())
.timeout(60000).execute().body();
System.out.println(result);
发送订阅通知中请求参数data里面,各个参数内容都是有限制的,比如上面代码里面,thing就只能20个字符以内(即便给的示例超过20个字符也不行)。符号表示除中文、英文、数字外的常见符号,不能带有换行等控制字符。 时间格式支持HH:MM:SS或者HH:MM。 日期包含年月日,为 y 年m月 d 日,y年 m 月、m月 d 日格式,或者用‘-’、‘/’、‘.’符号连接,如2018-01-01,2018/01/01,2018.01.01,2018-01,01-01。各个参数限制如下:
参数类别 | 参数说明 | 参数值限制 | 说明 |
---|---|---|---|
thing.DATA | 事物 | 20个以内字符 | 可汉字、数字、字母或符号组合 |
number.DATA | 数字 | 32位以内数字 | 只能数字,可带小数 |
letter.DATA | 字母 | 32位以内字母 | 只能字母 |
symbol.DATA | 符号 | 5位以内符号 | 只能符号 |
character_string.DATA | 字符串 | 32位以内数字、字母或符号 | 可数字、字母或符号组合 |
time.DATA | 时间 | 24小时制时间格式(支持+年月日),支持填时间段,两个时间点之间用“~”符号连接 | 例如:15:01,或:2019年10月1日 15:01 |
date.DATA | 日期 | 年月日格式(支持+24小时制时间),支持填时间段,两个时间点之间用“~”符号连接 | 例如:2019年10月1日,或:2019年10月1日 15:01 |
amount.DATA | 金额 | 1个币种符号+10位以内纯数字,可带小数,结尾可带“元” | 可带小数 |
phone_number.DATA | 电话 | 17位以内,数字、符号 | 电话号码,例:+86-0766-66888866 |
car_number.DATA | 车牌 | 8位以内,第一位与最后一位可为汉字,其余为字母或数字 | 车牌号码:粤A8Z888挂 |
name.DATA | 姓名 | 10个以内纯汉字或20个以内纯字母或符号 | 中文名10个汉字内;纯英文名20个字母内;中文和字母混合按中文名算,10个字内 |
phrase.DATA | 汉字 | 5个以内汉字 | 5个以内纯汉字,例如:配送中 |
正常情况下是不会需要对接这些接口的,但是为了第三方调用,微信还是给开通了。按照正常测试顺序(官方文档里面的顺序得调整看,难受),测试流程如下。
先拿到所有类目,使用这个类目才能请求公共模板
// 先获取access_token,这部分正式环境需要配置定时获取,每天2000次调用限制
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + SECRET;
String result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
System.out.println(result);
String accessToken = JSON.parseObject(result).getString("access_token");
// 获取公众号类目
url = "https://api.weixin.qq.com/wxaapi/newtmpl/getcategory?access_token=" + accessToken;
result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
// {"data":[{"id":612,"name":"信息查询"},{"id":413,"name":"软件服务提供商"},{"id":1041,"name":"其他医学健康服务"},{"id":1085,"name":"货物运输"}],"errmsg":"ok","errcode":0}
System.out.println(result);
请求参数需要类目id,也就是获取公共类目接口得到的id,多个id可以使用英文逗号分隔。本例id使用上面得到的四个612,413,1041,1085。
url = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatetitles?ids=612,413,1041,1085&start=0&limit=30&access_token=" + accessToken;
result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
// {"count":2335,"data":[{"categoryId":"1041","tid":370,"title":"基因检测结果提醒","type":2},{"categoryId":"612","tid":414,"title":"开奖结果通知","type":2},{"categoryId":"612","tid":420,"title":"线路调整通知","type":2},{"categoryId":"413","tid":492,"title":"预约通知","type":2},{"categoryId":"612","tid":513,"title":"签到提醒","type":2},{"categoryId":"612","tid":522,"title":"每日推壁纸更新通知","type":2},{"categoryId":"612","tid":525,"title":"新作品推荐提醒","type":2},{"categoryId":"612","tid":526,"title":"停电通知","type":2},{"categoryId":"612","tid":563,"title":"图书到期提醒","type":2},{"categoryId":"612","tid":565,"title":"停电通知","type":2},{"categoryId":"612","tid":576,"title":"实时地震通知","type":2},{"categoryId":"612","tid":624,"title":"活动预约提醒","type":2},{"categoryId":"612","tid":638,"title":"摇号结果通知","type":2},{"categoryId":"612","tid":648,"title":"代码更新提醒","type":2},{"categoryId":"612","tid":660,"title":"留言审核通知","type":2},{"categoryId":"612","tid":720,"title":"故障告警通知","type":2},{"categoryId":"612","tid":725,"title":"题库更新提醒","type":2},{"categoryId":"612","tid":730,"title":"客户分配提醒","type":2},{"categoryId":"612","tid":738,"title":"开通会员成功通知","type":2},{"categoryId":"612","tid":739,"title":"会员到期提醒","type":2},{"categoryId":"612","tid":786,"title":"审核结果通知","type":2},{"categoryId":"612","tid":789,"title":"样本状态变更提醒","type":2},{"categoryId":"612","tid":800,"title":"指标配置结果通知","type":2},{"categoryId":"612","tid":802,"title":"任务接收通知","type":2},{"categoryId":"612","tid":843,"title":"名言语录推荐通知","type":2},{"categoryId":"612","tid":867,"title":"监控告警通知","type":2},{"categoryId":"612","tid":895,"title":"审核通过通知","type":2},{"categoryId":"612","tid":1071,"title":"发货确认通知","type":2},{"categoryId":"612","tid":1072,"title":"订单签收通知","type":2},{"categoryId":"612","tid":1077,"title":"行程提醒","type":2}],"errmsg":"ok","errcode":0}
System.out.println(result);
上面已经获取到了对应的模板,返回结果中有tid,可以作为本接口的请求参数。返回对应模板的关键词,后面选用模板的时候,可以选择任意关键词,同时顺序也可以调整。
获取模板关键词代码:
url = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatekeywords?tid=370&access_token=" + accessToken;
result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
// {"data":[{"kid":1,"name":"温馨提示","example":"您好,您的检测已出结果。","rule":"thing","enumValueList":[]},{"kid":2,"name":"检测项目","example":"基因检测","rule":"thing","enumValueList":[]},{"kid":3,"name":"检测类别","example":"精准医疗","rule":"thing","enumValueList":[]},{"kid":4,"name":"检测时间","example":"2017年02月22日","rule":"date","enumValueList":[]},{"kid":5,"name":"备注","example":"点击“详情”查看详细报告","rule":"thing","enumValueList":[]},{"kid":6,"name":"样本编号","example":"2004001071","rule":"number","enumValueList":[]}],"errmsg":"ok","errcode":0}
System.out.println(result);
从公共模板库中选用模板,到私有模板库中。发送消息的时候,就是从私有模板中选择的。
这个地方有个小坑,使用Jsoup请求,必须添加header中的content-type,值为application/json,否则请求会报错。其他地方的接口即便是用post,也不需要这个header,搞不懂都是公众号的接口为什么不能统一。
// 选用模板:从公共模板库中选用模板,到私有模板库中
url = "https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate?access_token=" + accessToken;
JSONObject param = new JSONObject();
param.put("tid", "370"); // 公共模板的tid
JSONArray kidList = new JSONArray();
kidList.add(1);
kidList.add(4);
kidList.add(2);
param.put("kidList", kidList); // 模板关键词列表,顺序可以调整
param.put("sceneDesc", "给用户看的场景描述");
result = Jsoup.connect(url).ignoreContentType(true).method(Method.POST)
.header("content-type", "application/json") //其他接口不需要,只有本篇的几个接口需要,不加content-type就报错
.requestBody(param.toString()).execute().body();
// {"priTmplId":"vyIiro57RIuW2b0XlZYFa4NOVOsMYIe1dnCwNWZAF1M","errmsg":"ok","errcode":0}
System.out.println(result);
就用上面已经选用的模板做删除测试。
和选用模板一样,删除模板也必须添加content-type为application/json,否则删除会报错。
// 删除模板:这个是真的删除(模板消息推送里面的删除是假的删除,就修改了下templateId)
url = "https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate?access_token=" + accessToken;
param = new JSONObject();
param.put("priTmplId", "vyIiro57RIuW2b0XlZYFa4NOVOsMYIe1dnCwNWZAF1M");
result = Jsoup.connect(url).ignoreContentType(true).method(Method.POST)
.header("content-type", "application/json") //其他接口不需要,只有本篇的几个接口需要,不加content-type就报错
.requestBody(param.toString()).execute().body();
System.out.println(result);
这里也有个小坑:必须从后台手动删除掉已删除的私人模板,不然这里还是能获取到,而且和正常的区分不出来。
// 获取私有模板列表:必须从后台手动删除掉已删除的私人模板,不然这里还是能获取到,而且和正常的区分不出来
url = "https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate?access_token=" + accessToken;
result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
System.out.println(result);
package com.lootaa.wechat;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.digest.DigestUtils;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.jsoup.Connection.Method;
import org.jsoup.Jsoup;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.lootaa.wechat.util.WXBizMsgCrypt;
/**
* 订阅通知
* 前置条件:公众号后台设置ip白名单(推送给用户需要),启用了服务器配置(接收订阅事件推送需要)
*/
@RestController
public class Test010 {
public static final String APPID = "wx276049d6a7551dca";
public static final String SECRET = "cbe109fdf6f399bd72ed3a4afafa21b1";
/**
* 完整项目源码可关注公众号"lootaayun"(洛塔),回复010获取
*/
public static void main(String[] args) throws Exception {
// 先获取access_token,这部分正式环境需要配置定时获取,每天2000次调用限制
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + SECRET;
String result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
System.out.println(result);
String accessToken = JSON.parseObject(result).getString("access_token");
// 获取公众号类目
url = "https://api.weixin.qq.com/wxaapi/newtmpl/getcategory?access_token=" + accessToken;
result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
// {"data":[{"id":612,"name":"信息查询"},{"id":413,"name":"软件服务提供商"},{"id":1041,"name":"其他医学健康服务"},{"id":1085,"name":"货物运输"}],"errmsg":"ok","errcode":0}
System.out.println(result);
// 获取类目下的公共模板
url = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatetitles?ids=612,413,1041,1085&start=0&limit=30&access_token=" + accessToken;
result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
// {"count":2335,"data":[{"categoryId":"1041","tid":370,"title":"基因检测结果提醒","type":2},{"categoryId":"612","tid":414,"title":"开奖结果通知","type":2},{"categoryId":"612","tid":420,"title":"线路调整通知","type":2},{"categoryId":"413","tid":492,"title":"预约通知","type":2},{"categoryId":"612","tid":513,"title":"签到提醒","type":2},{"categoryId":"612","tid":522,"title":"每日推壁纸更新通知","type":2},{"categoryId":"612","tid":525,"title":"新作品推荐提醒","type":2},{"categoryId":"612","tid":526,"title":"停电通知","type":2},{"categoryId":"612","tid":563,"title":"图书到期提醒","type":2},{"categoryId":"612","tid":565,"title":"停电通知","type":2},{"categoryId":"612","tid":576,"title":"实时地震通知","type":2},{"categoryId":"612","tid":624,"title":"活动预约提醒","type":2},{"categoryId":"612","tid":638,"title":"摇号结果通知","type":2},{"categoryId":"612","tid":648,"title":"代码更新提醒","type":2},{"categoryId":"612","tid":660,"title":"留言审核通知","type":2},{"categoryId":"612","tid":720,"title":"故障告警通知","type":2},{"categoryId":"612","tid":725,"title":"题库更新提醒","type":2},{"categoryId":"612","tid":730,"title":"客户分配提醒","type":2},{"categoryId":"612","tid":738,"title":"开通会员成功通知","type":2},{"categoryId":"612","tid":739,"title":"会员到期提醒","type":2},{"categoryId":"612","tid":786,"title":"审核结果通知","type":2},{"categoryId":"612","tid":789,"title":"样本状态变更提醒","type":2},{"categoryId":"612","tid":800,"title":"指标配置结果通知","type":2},{"categoryId":"612","tid":802,"title":"任务接收通知","type":2},{"categoryId":"612","tid":843,"title":"名言语录推荐通知","type":2},{"categoryId":"612","tid":867,"title":"监控告警通知","type":2},{"categoryId":"612","tid":895,"title":"审核通过通知","type":2},{"categoryId":"612","tid":1071,"title":"发货确认通知","type":2},{"categoryId":"612","tid":1072,"title":"订单签收通知","type":2},{"categoryId":"612","tid":1077,"title":"行程提醒","type":2}],"errmsg":"ok","errcode":0}
System.out.println(result);
// 获取模板中的关键词
url = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatekeywords?tid=370&access_token=" + accessToken;
result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
// {"data":[{"kid":1,"name":"温馨提示","example":"您好,您的检测已出结果。","rule":"thing","enumValueList":[]},{"kid":2,"name":"检测项目","example":"基因检测","rule":"thing","enumValueList":[]},{"kid":3,"name":"检测类别","example":"精准医疗","rule":"thing","enumValueList":[]},{"kid":4,"name":"检测时间","example":"2017年02月22日","rule":"date","enumValueList":[]},{"kid":5,"name":"备注","example":"点击“详情”查看详细报告","rule":"thing","enumValueList":[]},{"kid":6,"name":"样本编号","example":"2004001071","rule":"number","enumValueList":[]}],"errmsg":"ok","errcode":0}
System.out.println(result);
// 选用模板:从公共模板库中选用模板,到私有模板库中
url = "https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate?access_token=" + accessToken;
JSONObject param = new JSONObject();
param.put("tid", "370"); // 公共模板的tid
JSONArray kidList = new JSONArray();
kidList.add(1);
kidList.add(4);
kidList.add(2);
param.put("kidList", kidList); // 模板关键词列表,顺序可以调整
param.put("sceneDesc", "给用户看的场景描述");
result = Jsoup.connect(url).ignoreContentType(true).method(Method.POST)
.header("content-type", "application/json") //其他接口不需要,只有本篇的几个接口需要,不加content-type就报错
.requestBody(param.toString()).execute().body();
// {"priTmplId":"vyIiro57RIuW2b0XlZYFa4NOVOsMYIe1dnCwNWZAF1M","errmsg":"ok","errcode":0}
System.out.println(result);
// 删除模板:这个是真的删除(模板消息推送里面的删除是假的删除,就修改了下templateId)
url = "https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate?access_token=" + accessToken;
param = new JSONObject();
param.put("priTmplId", "vyIiro57RIuW2b0XlZYFa4NOVOsMYIe1dnCwNWZAF1M");
result = Jsoup.connect(url).ignoreContentType(true).method(Method.POST)
.header("content-type", "application/json") //其他接口不需要,只有本篇的几个接口需要,不加content-type就报错
.requestBody(param.toString()).execute().body();
System.out.println(result);
// 获取私有模板列表:必须从后台手动删除掉已删除的私人模板,不然这里还是能获取到,而且和正常的区分不出来
url = "https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate?access_token=" + accessToken;
result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
System.out.println(result);
}
/**
* 完整项目源码可关注公众号"lootaayun"(洛塔),回复010获取
*/
@GetMapping("wx10")
public void wxGet(HttpServletRequest request, PrintWriter pw) {
// 微信加密签名,需要使用本地计算出来的和这个对比,确认是微信发送的消息
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp"); // 时间戳
String nonce = request.getParameter("nonce"); // 随机数
String echostr = request.getParameter("echostr"); // 随机字符串
// 将token、timestamp、nonce三个参数进行字典序排序
List<String> list = new ArrayList<String>();
list.add("lootaa"); // 公众号后台设置的token
list.add(timestamp);
list.add(nonce);
Collections.sort(list);
// 将三个参数字符串拼接成一个字符串进行sha1加密
String tokenStr = "";
for (int i = 0; i < list.size(); i++) {
tokenStr += list.get(i);
}
String signatureStr = DigestUtils.sha1Hex(tokenStr);
// 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if (signature.equals(signatureStr)) {
pw.write(echostr); // 原样返回echostr参数内容
} else {
pw.write("");
}
}
public static Document getDocument(HttpServletRequest request) {
SAXReader reader = new SAXReader();
try {
InputStream ins = request.getInputStream();
Document doc = reader.read(ins);
return doc;
} catch (IOException e) {
e.printStackTrace();
} catch (DocumentException e) {
e.printStackTrace();
}
return null;
}
public static JSONObject documentToJSONObject(String xml) {
JSONObject jsonObject = null;
try {
jsonObject = elementToJSONObject(DocumentHelper.parseText(xml).getRootElement());
} catch (DocumentException e) {
e.printStackTrace();
}
return jsonObject;
}
@SuppressWarnings("unchecked")
public static JSONObject elementToJSONObject(Element node) {
JSONObject result = new JSONObject();
// 当前节点的名称、文本内容和属性
List<Attribute> listAttr = node.attributes();// 当前节点的所有属性的list
for (Attribute attr : listAttr) {// 遍历当前节点的所有属性
result.put(attr.getName(), attr.getValue());
}
// 递归遍历当前节点所有的子节点
List<Element> listElement = node.elements();// 所有一级子节点的list
if (!listElement.isEmpty()) {
for (Element e : listElement) {// 遍历所有一级子节点
if (e.attributes().isEmpty() && e.elements().isEmpty()) // 判断一级节点是否有属性和子节点
result.put(e.getName(), e.getTextTrim());// 沒有则将当前节点作为上级节点的属性对待
else {
if (!result.containsKey(e.getName())) // 判断父节点是否存在该一级节点名称的属性
result.put(e.getName(), new JSONArray());// 没有则创建
((JSONArray) result.get(e.getName())).add(elementToJSONObject(e));// 将该一级节点放入该节点名称的属性对应的值中
}
}
}
return result;
}
@PostMapping("wx10")
public void wxPost(HttpServletRequest request, HttpServletResponse response, PrintWriter pw) throws Exception {
String token = "lootaa";
String encodingAesKey = "FpKEYJDuwK92k2juU2z0sUvTmc3hB4W5wGLJEKay8oK";
String appid = "wx276049d6a7551dca";
WXBizMsgCrypt pc = new WXBizMsgCrypt(token, encodingAesKey, appid);
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String msgSignature = request.getParameter("msg_signature");
Document doc = getDocument(request);
String result2 = pc.decryptMsg(msgSignature, timestamp, nonce, doc.asXML());
System.out.println("解密后明文: " + result2);
JSONObject resultJson = documentToJSONObject(result2);
String messageType = resultJson.getString("MsgType"); //这个值如果是event表示是事件推送;如果是其他字符,参照Test004
if(Objects.equals("event", messageType)) {
String event = resultJson.getString("Event"); //这个是事件的具体类型
if(Objects.equals(event, "subscribe_msg_popup_event")) { // 订阅消息
// SubscribeMsgPopupEvent 列表中可以单独管理各个模板推送,这里直接演示收到之后将消息发送出去
// 真实场景肯定是先保存,然后在合适的时机执行下面推送的代码
String openid = resultJson.getString("FromUserName");
// 先获取access_token,这部分正式环境需要配置定时获取,每天2000次调用限制
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + SECRET;
String result = Jsoup.connect(url).ignoreContentType(true).method(Method.GET).execute().body();
System.out.println(result);
String accessToken = JSON.parseObject(result).getString("access_token");
// 获取公众号的自动回复规则
url = "https://api.weixin.qq.com/cgi-bin/message/subscribe/bizsend?access_token=" + accessToken;
JSONObject param = new JSONObject();
param.put("touser", openid); //接收用户的openid
param.put("template_id", "dnemOr1oZ7XLQApxzBaZFtwYJxfdYzvbVS5hjZyW4KI"); //可以为事件推送的模板id,这里测试直接使用后台配置好的
param.put("page", "https:///m0_58095675");
JSONObject miniprogram = new JSONObject();
miniprogram.put("appid", "wxa3b096d8546b270d");
miniprogram.put("pagepath", "pages/station/station");
param.put("miniprogram", miniprogram);
JSONObject data = new JSONObject();
JSONObject thing1 = new JSONObject();
thing1.put("value", "20个以内字符。");
data.put("thing1", thing1);
JSONObject thing2 = new JSONObject();
thing2.put("value", "超过了会报错47003。");
data.put("thing2", thing2);
param.put("data", data);
result = Jsoup.connect(url).ignoreContentType(true).method(Method.POST).requestBody(param.toString())
.timeout(60000).execute().body();
System.out.println(result);
pw.write(nonce);
} else if(Objects.equals(event, "subscribe_msg_popup_event")) { // 发送订阅通知
System.out.println("发送订阅通知的事件");
pw.write(nonce);
} else if(Objects.equals(event, "subscribe_msg_change_event")) { // 用户管理订阅通知
System.out.println("如果用户之前订阅了,然后管理里面点击了拒绝,会到这里。根据具体业务调整推送逻辑");
pw.write(nonce);
}
}
}
}