洛塔服务号回复005获取代码。
功能说明
用户订阅公众号、取消订阅、扫码公众号、开启地理位置、自定义菜单事件等,都会有收到微信发送的事件推送。本篇测试的事件推送
- 订阅公众号:含扫码关注和其他关注方式
- 取消订阅
- 扫描带参数二维码
- 上报地理位置:公众号后台需开启
- 自定义菜单事件
- 自定义菜单点击链接
方式选择
公众号后台的消息加解密方式选择安全模式。如果用的明文模式,需要对应调整。
- URL:和上一篇的一模一样,本篇使用http://test.lootaa.com/lootaa-wechat/wx3
- Token:任意填写,和代码中的一致
- EncodingAESKey:随机生成即可,代码中要用
- 消息加解密方式:安全模式(推荐)
其中,URL对应代码部署方式为,后台使用springboot来开发,nginx做端口转发。
nginx配置:
location /lootaa-wechat/ {
proxy_pass http://127.0.0.1:2022/lootaa-wechat/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
}
application.properties配置
server.port=2022
server.servlet.context-path=/lootaa-wechat
get方法访问配置(路径使用的http://test.lootaa.com/lootaa-wechat/wx3)
@RestController
public class Test005 {
@GetMapping("wx3")
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("");
}
}
打包部署到服务器后,将此路径和token填写到后台中,点击保存即可。
辅助类
为了方便解析,需要两个辅助类,分别是将接收到的request转化为Document(dom4j下的)、将Document转化为map。
- 将request转化为Document
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;
}
- 将Document转化为map
public static Map<String, String> docToMap(Document doc) {
Map<String, String> map = new HashMap<String, String>();
Element root = doc.getRootElement();
@SuppressWarnings("unchecked")
List<Element> list = root.elements();
for (Element element : list) {
map.put(element.getName(), element.getText());
}
return map;
}
微信提供的辅助类
微信提供了多种语言版本的辅助类,可以点击这里直接下载。使用过程中,需要maven引入包。其中codec是必须引入的,dom4j是我测试代码解析使用的。
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
微信提供的辅助类包括
- AesException.java
- ByteGroup.java
- PKCS7Encoder.java
- SHA1.java
- WXBizMsgCrypt.java
- XMLParse.java
提供测试的二维码
为了方便测试带参数的二维码关注和扫描事件,需要先生成对应的二维码。本篇使用了微信提供的工具网站:https://mp.weixin.qq.com/debug/cgi-bin/apiinfo
-
先获取token
-
用token获取到ticket
-
拼接得到二维码
最终二维码为https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=【上一步得到的ticket】
接收普通消息解密
用到了上面提供的辅助类。得到map后,处理方式就和明文的基本一致了(除了被动回复需要加密)
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);
Map<String, String> map = docToMap(DocumentHelper.parseText(result2));
被动回复加密
result就是明文情况下要返回的消息,直接调用下encryptMsg即可(pc是加密时候的WXBizMsgCrypt、timestamp和nonce是从request中接收到的原样信息)。
result = pc.encryptMsg(result, timestamp, nonce);
公众号订阅事件
如果是普通关注公众号,并不会有参数。如果是通过扫描带参数二维码关注,则会出现字段EventKey,去掉开头的qrscene_就是二维码中的参数。
if(Objects.equals(event, "subscribe")) { //公众号订阅
String eventKey = map.get("EventKey"); //如果是扫码关注,同时二维码有参数,会有这个值,qrscene_开头
if(eventKey != null) {
System.out.println("二维码参数" + eventKey.replaceFirst("qrscene_", ""));
}
String ticket = map.get("Ticket"); //用来生成二维码
if(ticket != null) {
String code = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;
System.out.println("二维码:" + code);
}
String result = "<xml>" + "<ToUserName><![CDATA[" + map.get("FromUserName") + "]]></ToUserName>"
+ "<FromUserName><![CDATA[" + map.get("ToUserName") + "]]></FromUserName>" + "<CreateTime>"
+ System.currentTimeMillis() + "</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>"
+ "<Content><![CDATA[欢迎关注洛塔!]]></Content></xml>";
result = pc.encryptMsg(result, timestamp, nonce);
pw.write(result);
}
公众号取消订阅
if(Objects.equals(event, "unsubscribe")) { //公众号订取消阅
System.out.println("取消订阅");
pw.write(nonce);
}
已关注用户扫码事件
这里有个坑,收到推送消息后,返回给微信的如果是nonce,公众号则会提示异常。必须返回空字符串,或者success。
if(Objects.equals(event, "SCAN")) { //已关注的用户扫码
String eventKey = map.get("EventKey"); //qrscene_开头
if(eventKey != null && eventKey.length() > 0) {
System.out.println("二维码参数" + eventKey.replaceFirst("qrscene_", ""));
}
String ticket = map.get("Ticket"); //用来生成二维码
if(ticket != null) {
String code = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;
System.out.println("二维码:" + code);
}
pw.write("");
}
上报地理位置事件
必须在公众号后台开启地理位置获取,开启的时候会让选择模式。
开启后,打开公众号的时候就有位置信息了文章来源:https://www.toymoban.com/news/detail-483860.html
if(Objects.equals(event, "LOCATION")) { //上报地理位置事件
String latitude = map.get("Latitude"); //纬度
String longitude = map.get("Longitude"); //经度
String precision = map.get("Precision"); //精度,感觉没啥用
System.out.println(latitude + "," + longitude + "," + precision);
pw.write(nonce);
}
点击菜单事件
实现方式可以参照之前写的自定义菜单部分。文章来源地址https://www.toymoban.com/news/detail-483860.html
if(Objects.equals(event, "CLICK")) { //点击菜单事件
String eventKey = map.get("EventKey"); //事件 KEY 值
String result = "<xml>" + "<ToUserName><![CDATA[" + map.get("FromUserName") + "]]></ToUserName>"
+ "<FromUserName><![CDATA[" + map.get("ToUserName") + "]]></FromUserName>" + "<CreateTime>"
+ System.currentTimeMillis() + "</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>"
+ "<Content><![CDATA[" + eventKey + "]]></Content></xml>";
result = pc.encryptMsg(result, timestamp, nonce);
pw.write(result);
}
点击菜单跳转链接
if(Objects.equals(event, "VIEW")) { //点击菜单跳转链接
String eventKey = map.get("EventKey"); //链接地址
System.out.println(eventKey);
pw.write(nonce);
}
完整代码
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.codec.digest.DigestUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import com.lootaa.wechat.util.WXBizMsgCrypt;
/**
* 前置条件:基本配置中开启了服务器配置
* 完整项目源码可关注公众号"lootaayun"(洛塔),回复005获取
*/
@RestController
public class Test005 {
@GetMapping("wx3")
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 Map<String, String> docToMap(Document doc) {
Map<String, String> map = new HashMap<String, String>();
Element root = doc.getRootElement();
@SuppressWarnings("unchecked")
List<Element> list = root.elements();
for (Element element : list) {
map.put(element.getName(), element.getText());
}
return map;
}
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;
}
/**
* 如果不能确保5秒钟之内回复消息,微信会发起重试。对于此种情况,两种解决方法 1. 所有消息都有MsgId字段,用来做判断避免重复处理 2.
* 先返回一条固定消息,然后再启线程单独处理(比如处理完成后在用客服消息接口发送给用户)
*/
@PostMapping("wx3")
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);
Map<String, String> map = docToMap(DocumentHelper.parseText(result2));
String messageType = map.get("MsgType"); //这个值如果是event表示是事件推送;如果是其他字符,参照Test004
if(Objects.equals("event", messageType)) {
String event = map.get("Event"); //这个是事件的具体类型
if(Objects.equals(event, "subscribe")) { //公众号订阅
String eventKey = map.get("EventKey"); //如果是扫码关注,同时二维码有参数,会有这个值,qrscene_开头
if(eventKey != null) {
System.out.println("二维码参数" + eventKey.replaceFirst("qrscene_", ""));
}
String ticket = map.get("Ticket"); //用来生成二维码
if(ticket != null) {
String code = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;
System.out.println("二维码:" + code);
}
String result = "<xml>" + "<ToUserName><![CDATA[" + map.get("FromUserName") + "]]></ToUserName>"
+ "<FromUserName><![CDATA[" + map.get("ToUserName") + "]]></FromUserName>" + "<CreateTime>"
+ System.currentTimeMillis() + "</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>"
+ "<Content><![CDATA[欢迎关注洛塔!]]></Content></xml>";
result = pc.encryptMsg(result, timestamp, nonce);
pw.write(result);
}
if(Objects.equals(event, "unsubscribe")) { //公众号订取消阅
System.out.println("取消订阅");
pw.write(nonce);
}
if(Objects.equals(event, "SCAN")) { //已关注的用户扫码
String eventKey = map.get("EventKey"); //qrscene_开头
if(eventKey != null && eventKey.length() > 0) {
System.out.println("二维码参数" + eventKey.replaceFirst("qrscene_", ""));
}
String ticket = map.get("Ticket"); //用来生成二维码
if(ticket != null) {
String code = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;
System.out.println("二维码:" + code);
}
pw.write("");
}
if(Objects.equals(event, "LOCATION")) { //上报地理位置事件
String latitude = map.get("Latitude"); //纬度
String longitude = map.get("Longitude"); //经度
String precision = map.get("Precision"); //精度,感觉没啥用
System.out.println(latitude + "," + longitude + "," + precision);
pw.write(nonce);
}
if(Objects.equals(event, "CLICK")) { //点击菜单事件
String eventKey = map.get("EventKey"); //事件 KEY 值
String result = "<xml>" + "<ToUserName><![CDATA[" + map.get("FromUserName") + "]]></ToUserName>"
+ "<FromUserName><![CDATA[" + map.get("ToUserName") + "]]></FromUserName>" + "<CreateTime>"
+ System.currentTimeMillis() + "</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>"
+ "<Content><![CDATA[" + eventKey + "]]></Content></xml>";
result = pc.encryptMsg(result, timestamp, nonce);
pw.write(result);
}
if(Objects.equals(event, "VIEW")) { //点击菜单跳转链接
String eventKey = map.get("EventKey"); //链接地址
System.out.println(eventKey);
pw.write(nonce);
}
}
}
}
到了这里,关于公众号接收事件推送的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!