Java监控微信公众号关注事件
监控微信公众号时间需要先完善服务器配置
微信官方文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
这里的URL填写自己的服务器地址,需要80或者443端口,没有服务器的可以使用内网渗透工具,我使用的是cpolar,Token跟EncodingAESKey是可以随机的。
验证消息的确来自微信服务器
开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上。
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
检验signature的Java示例代码:
@GetMapping(value = "/check-token", produces = "application/json; charset=utf-8") @ApiOperationSupport(order = 7) @ApiOperation(value = "验证消息的确来自微信服务器,签名验证", notes = "验证消息的确来自微信服务器,签名验证", code = 200, produces = "application/json") @ApiImplicitParams({ @ApiImplicitParam(paramType = "query", dataType = "string", name = "signature",required = true ,value = "微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。"), @ApiImplicitParam(paramType = "query", dataType = "string", name = "timestamp",required = true ,value = "时间戳"), @ApiImplicitParam(paramType = "query", dataType = "string", name = "nonce",required = true ,value = "随机数"), @ApiImplicitParam(paramType = "query", dataType = "string", name = "echostr",required = true ,value = "随机字符串")}) public String checkToken(@RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr) { return service.checkSignature(signature, timestamp, nonce,echostr); } public String checkSignature(String signature, String timestamp, String nonce, String echostr) { log.info("signature:{},token:{},timestamp:{},nonce:{}",signature,token,timestamp,nonce); // 1.将token、timestamp、nonce三个参数进行字典序排序 String tmpStr = ShaUtil.getSHA1(token, timestamp, nonce); log.info("随机字符串echostr:{}",echostr); log.info("tmpStr:{}",tmpStr); if (tmpStr.equals(signature.toUpperCase())) { return echostr; } return null; }
ShaUtil工具类
@Slf4j public class ShaUtil { /** * @param token url相关的token * @param timestamp 时间戳 * @param nonce 随机数 * @return java.lang.String * @Description 用SHA1算法验证Token */ public static String getSHA1(String token, String timestamp, String nonce) { String[] arr = new String[]{token, timestamp, nonce}; Arrays.sort(arr); StringBuilder content = new StringBuilder(); for (int i = 0; i < arr.length; i++) { content.append(arr[i]); } MessageDigest md = null; String tmpStr = null; try { md = MessageDigest.getInstance("SHA-1"); // 将三个参数字符串拼接成一个字符串进行sha1加密 byte[] digest = md.digest(content.toString().getBytes()); tmpStr = byteToStr(digest); } catch (NoSuchAlgorithmException e) { log.info("错误信息:{}", e.getMessage()); } return tmpStr; } /** * @param byteArray * @return java.lang.String * @Description 将字节数组转换为十六进制字符串 */ private static String byteToStr(byte[] byteArray) { StringBuilder strDigest = new StringBuilder(); for (int i = 0; i < byteArray.length; i++) { strDigest.append(byteToHexStr(byteArray[i])); } return strDigest.toString(); } /** * @param mByte * @return java.lang.String * @Description 将字节转换为十六进制字符串 */ 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; } }
关注/取消关注事件
微信官方文档:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html
用户在关注与取消关注公众号时,微信会把这个事件推送到开发者填写的URL。方便开发者给用户下发欢迎消息或者做账号的解绑。为保护用户数据隐私,开发者收到用户取消关注事件时需要删除该用户的所有信息。
微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。
关于重试的消息排重,推荐使用FromUserName + CreateTime 排重。
假如服务器无法保证在五秒内处理并回复,可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。
@PostMapping("/check-token") @ApiOperation(value = "关注公众号微信回调接口") public R<String> responseEvent(HttpServletRequest req, HttpServletResponse resp){ return R.data(weChatService.responseEvent(req,resp)); } public String responseEvent(HttpServletRequest req, HttpServletResponse resp) { String message = ""; try { req.setCharacterEncoding("UTF-8"); resp.setCharacterEncoding("UTF-8"); Map<String, String> requestMap = WxMessageUtil.parseXml(req); log.info("解析====>{}", req); // 消息类型,event String messageType = requestMap.get("MsgType"); // 事件类型,subscribe String eventType = requestMap.get("Event"); // 发送方帐号(open_id) String openid = requestMap.get("FromUserName"); // 开发者微信号 String toUserName = requestMap.get("ToUserName"); if ("event".equals(messageType)) { //判断消息类型是否是事件消息类型 log.info("公众号====>事件消息"); log.info("openid:{}, Event:", openid, eventType); if (eventType.equals("subscribe")) { message = "新用户关注公众号" + toUserName; log.info(message); // 具体业务 } else if (eventType.equals("unsubscribe")) { message = "用户取消关注公众号" + toUserName; log.info(message); // 具体业务 } } } catch (Exception e) { log.error(e.getMessage()); } return message; }
微信有多种消息类型和事件类型,可以根据具体需求去监控不同的消息事件。
WxMessageUtil工具类
public class WxMessageUtil { public static Map<String, String> parseXml(HttpServletRequest request) throws Exception { // 将解析结果存储在HashMap中 Map<String, String> map = new HashMap<String, String>(); // 从request中取得输入流 InputStream inputStream = request.getInputStream(); // 读取输入流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); // 得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子节点 List<Element> elementList = root.elements(); // 遍历所有子节点 for (Element e : elementList) { map.put(e.getName(), e.getText()); } // 释放资源 inputStream.close(); return map; } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)