• 公众号接收事件推送


    洛塔服务号回复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 list = new ArrayList();
    		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 docToMap(Document doc) {
    		Map map = new HashMap();
    		Element root = doc.getRootElement();
    		@SuppressWarnings("unchecked")
    		List list = root.elements();
    		for (Element element : list) {
    			map.put(element.getName(), element.getText());
    		}
    		return map;
    	}
    

    微信提供的辅助类

    微信提供了多种语言版本的辅助类,可以点击这里直接下载。使用过程中,需要maven引入包。其中codec是必须引入的,dom4j是我测试代码解析使用的。

    		
    			commons-codec
    			commons-codec
    		
    		
    		
                dom4j
                dom4j
                1.6.1
            
    

    微信提供的辅助类包括

    • 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 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 = "" + ""
    						+ "" + ""
    						+ System.currentTimeMillis() + "" + ""
    						+ "";
    				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("");
    			}
    

    上报地理位置事件

    必须在公众号后台开启地理位置获取,开启的时候会让选择模式。
    在这里插入图片描述

    开启后,打开公众号的时候就有位置信息了

    			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 = "" + ""
    						+ "" + ""
    						+ System.currentTimeMillis() + "" + ""
    						+ "";
    				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 list = new ArrayList();
    		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 docToMap(Document doc) {
    		Map map = new HashMap();
    		Element root = doc.getRootElement();
    		@SuppressWarnings("unchecked")
    		List 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 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 = "" + ""
    						+ "" + ""
    						+ System.currentTimeMillis() + "" + ""
    						+ "";
    				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 = "" + ""
    						+ "" + ""
    						+ System.currentTimeMillis() + "" + ""
    						+ "";
    				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);
    			}
    			
    		}
    		
    	}
    
    }
    
    
  • 相关阅读:
    【Mycat2实战】三、Mycat实现读写分离
    数据分享|R语言武汉流动人口趋势预测:灰色模型GM(1,1)、ARIMA时间序列、logistic逻辑回归模型...
    分发饼干(贪心算法+图解)
    SpringBoot是如何集成了Tomcat呢?
    C语言 —— sizeof与strlen对比表 (持续更新中....)
    mybatis面试题
    AI是未来?——神经网络篇
    LeetCode 318. 最大单词长度乘积
    iOS 中,isa 指针
    超前进位加法器
  • 原文地址:https://blog.csdn.net/m0_58095675/article/details/127044391