企业微信-消息推送之微信客服-接收消息和事件

一:企微实现和企业间的微信客服消息接收和事件原理

        新版企微主要通过2个阶段实,第一个:消息推送 概述 - 文档 - 企业微信开发者中心 ,第二个:微信客服 接收消息和事件 - 文档 - 企业微信开发者中心

二:代码逻辑分析

        1、消息推送   概述 - 文档 - 企业微信开发者中心 

企微所有的消息都是通过这个主体接收,然后将消息内容放在Encrypt节点里面,如下图

返回对象log打印结果实例:

后端日志控制台返回:
<xml><ToUserName><![CDATA[wwaf9f23ec4871e]]></ToUserName><Encrypt><![CDATA[LJzYB4kTu5qFbSDoW1beSl7ZlMBMsf8rvFPO7aaO/UNUipytMzGer6oQBxGcdvtd4zE6o7OPliGvUqxdOaiTIUeUAbqvSld92guwwUXhlsaipIfPo7BU2SJ2Lg5xClCXmTSRA2PZ6NVEk6n9WLjHieXzNp/1pLVjadNueXVZjQLCEL2yQQmZmSMqGlz4KfKy9XfkyuyKCTEmBIcwmOxMEvWtslQOtlSDnZhCV6KNkV4gK+dlBa0gJQthpTYt7UGyB5PagFQhDDdpFRonyE746BiNkNNyqxyCQBCp50ldWItuixEUM40LL95V0Z/wKaQDkPMV1H1WRrr+9eqUM3/NAz0HojMtTjrqIlDimwiEkMgOlMY6PLwKP9JdDrjJU3VfDS5SxYOpnQxmBU6JLk7yAsd1ssb1LaHwReCciW4vVUoTKHhQ5BTkRpHfHQ7TuTQ1bhPZKE5yXK7hkXKG8XSxnbCiMmnp+Szm41K4XpOr8QOmmKsrX646hGyja489IrKMaZ3LzCT2vHKloBozsurvydrqkUhVoU5my/r7yE3ctMLxua7mvXX3AbzIXt7lt4OfhNIyo1Am5oGAA==]]></Encrypt><AgentID><![CDATA[1060109]]></AgentID></xml>

客服消息也会在Encrypt里面

        2、微信客服   接收消息和事件 - 文档 - 企业微信开发者中心

获取客服消息,需要解析主体中的Encrypt,拿到里面的数据。

解密后,你会发现,Encrypt里面的内容就是如下格式数据:

<xml>
   <ToUserName><![CDATA[ww12345678910]]></ToUserName>
   <CreateTime>1348831860</CreateTime>
   <MsgType><![CDATA[event]]></MsgType>
   <Event><![CDATA[kf_msg_or_event]]></Event>
   <Token><![CDATA[ENCApHxnGDNAVNY4AaSJKj4Tb5mwsEMzxhFmHVGcra996NR]]></Token>
   <OpenKfId><![CDATA[wkxxxxxxx]]></OpenKfId>
</xml>

到此,逻辑就基本出来了!

很重要的一点要理解,这个消息的使用逻辑,看企微api文档你发现就是这一块那一块的。实际就是做的一件事,实际就是一个包含关系—— 微【信客服-接收消息和事件】都是放在 【企业微信-消息推送】这个大的主体里面的!

企微后台自建应用绑定事件url:

理解到这里,基本就很简单了。

下面是一个java Servlet的实现!

package org.jeecg.modules.ww.kf.servlet;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.modules.sys.qiwei.service.QiWeiEventStrategy;
import org.jeecg.modules.ww.kf.util.UtilHelp;
import org.jeecg.modules.ww.sys.json_callback.com.qq.weixin.mp.aes.AesException;
import org.jeecg.modules.ww.sys.json_callback.com.qq.weixin.mp.aes.WXBizJsonMsgCrypt;
import org.jeecg.modules.ww.wechatkfmsgrecord.service.ITblWechatKfMsgRecordService;
import org.jeewx.api.mp.aes.WXBizMsgCrypt;
import org.springframework.beans.factory.annotation.Autowired;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Map;


@Slf4j
@WebServlet(name = "kfCallbackServlet",urlPatterns = "/qw/kf-callback")
public class WeiXinKfCallbackServlet extends HttpServlet {

    @Autowired
    private ITblWechatKfMsgRecordService kfMsgRecordService;

    @Autowired
    private Map<String, QiWeiEventStrategy> qiWeiEventStrategyMap;


    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public WeiXinKfCallbackServlet() {
        super();
    }

    @Override
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 处理Servlet请求逻辑
        log.info("WeiXinCheckUrl 处理Servlet请求逻辑");
        String method = request.getMethod();
        if ("GET".equalsIgnoreCase(method)) {
            // 处理GET请求
            doGet(request,response);
        } else if ("POST".equalsIgnoreCase(method)) {
            // 处理POST请求
            doPost(request,response);
        }
    }

    /**
     * get方法主要用来做验证企微事件的url
     * msg_signature	是	企业微信加密签名,msg_signature结合了企业填写的token、请求中的timestamp、nonce参数、加密的消息体
     * timestamp	是	时间戳
     * nonce	是	随机数
     * echostr	是	加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、CorpID四个字段,其中msg即为消息内容明文
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 获取企业参数
        String sVerifyMsg_signature = request.getParameter("msg_signature");
        String sVerifyTimestamp = request.getParameter("timestamp");
        String sVerifyNonce = request.getParameter("nonce");
        String sVerifyEchostr = request.getParameter("echostr");
        log.info("WeiXinCheckUrl doGet params msg_signature:{},timestamp:{},nonce:{},echostr:{}",sVerifyMsg_signature,sVerifyTimestamp,sVerifyNonce,sVerifyEchostr);
        String sToken= "Hid7AO1DdL3FRQMJeonqURipY9";
        String sEncodingAESKey = "H6MitIu236jCO4uS8j2nVH4iGzkKf1oKGnbeHGnOb5o";
        String sCorpID = "wwaf9f523ec40871ee";
        WXBizJsonMsgCrypt wxcpt = null;
        try {
            wxcpt = new WXBizJsonMsgCrypt(sToken, sEncodingAESKey, sCorpID);
        } catch (AesException e) {
            e.printStackTrace();
        }
        String sEchoStr = null; //需要返回的明文
        try {
            sEchoStr = wxcpt.VerifyURL(sVerifyMsg_signature, sVerifyTimestamp,
                    sVerifyNonce, sVerifyEchostr);
            log.info("WeiXinCheckUrl doGet verifyurl echostr:{}",sEchoStr);
            // 验证URL成功,将sEchoStr返回 验证url这里可以不用管
            // HttpUtils.SetResponse(sEchoStr);
        } catch (Exception e) {
            //验证URL失败,错误原因请查看异常
            e.printStackTrace();
        }
        UtilHelp.PrintWriter(sEchoStr, "json", request, response);
    }

    /**
     * doPost是用来接收企微发送post数据,如微信端给企微发消息后,企微把消息推送给我们后端服务的时候!
     * msgSignature 签名串,对应URL参数的msg_signature
     * timeStamp 时间戳,对应URL参数的timestamp
     * nonce 随机串,对应URL参数的nonce
     * postData 密文,对应POST请求的数据
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String result = "";
        String msgSignature = request.getParameter("msg_signature");
        String timestamp = request.getParameter("timestamp");
        String nonce = request.getParameter("nonce");
        BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
        String lines;
        StringBuffer bodyXml = new StringBuffer("");
        while ((lines = reader.readLine()) != null) {
            lines = new String(lines.getBytes(), "utf-8");
            bodyXml.append(lines);
        }
        log.info("qwKfEvent params timestamp:{},nonce:{},msg_signature:{},data:{}",timestamp,nonce,msgSignature,bodyXml);
        try {
            WXBizMsgCrypt wxcpt = new WXBizMsgCrypt("Hid7AODdL3FRQMJeqURip9","H6MitIu236CO4uS8j2H4iGzkKfKGnbHnOb5o","wwa9f523ec4081e");
            result = wxcpt.decryptMsg(msgSignature,timestamp,nonce,String.valueOf(bodyXml));
        } catch (org.jeewx.api.mp.aes.AesException e) {
            log.info("qwKfEvent 解密失败 Exeception:{}",e.getMessage());
        }
        cn.hutool.json.JSONObject eventObj = JSONUtil.xmlToJson(result);
        log.info("[qwKfEvent] 解密后内容={}", eventObj.toString());

        // 这里解密后就是主体里面的微信消息了,可以参考微信消息事件的响应xml内容去做对应的解析
        String event = eventObj.getByPath("xml.Event", String.class);
        // 获取事件处理实现类 (这里处理你自己的业务,或者搞个实现类去实现如下,就不贴代码了)
        QiWeiEventStrategy eventStrategy = qiWeiEventStrategyMap.get(String.format("%s_service", event));
        if (eventStrategy != null) {
            eventStrategy.handleEvent(eventObj);
            log.info("[qwKfEvent] 事件处理完成.  eventName={}", event);
        } else {
            log.info("[qwKfEvent] 未找到合适对应的事件实现类.  eventName={}", event);
        }
    }


}

里面使用到的企微api:weworkapi_java-master.zip 去csdn下载即可,也可以使用jar包

servlet的话,自己创建个配置注入到spring资源管理就行:

import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ServletConfig {
    @Bean
    public ServletRegistrationBean<WeiXinKfCallbackServlet> myServletRegistration() {
        ServletRegistrationBean<WeiXinKfCallbackServlet> registration = new ServletRegistrationBean<>(new WeiXinKfCallbackServlet(), "/qw/kf-callback");
        return registration;
    }

}

本人QQ: 93775988

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/912144.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ascend Extension for PyTorch是个what?

1 Ascend Extension for PyTorch Ascend Extension for PyTorch 插件是基于昇腾的深度学习适配框架&#xff0c;使昇腾NPU可以支持PyTorch框架&#xff0c;为PyTorch框架的使用者提供昇腾AI处理器的超强算力。 项目源码地址请参见Ascend/Pytorch。 昇腾为基于昇腾处理器和软…

【React】React 生命周期完全指南

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 React 生命周期完全指南一、生命周期概述二、生命周期的三个阶段2.1 挂载阶段&a…

开源模型应用落地-glm模型小试-glm-4-9b-chat-压力测试(六)

一、前言 GLM-4是智谱AI团队于2024年1月16日发布的基座大模型&#xff0c;旨在自动理解和规划用户的复杂指令&#xff0c;并能调用网页浏览器。其功能包括数据分析、图表创建、PPT生成等&#xff0c;支持128K的上下文窗口&#xff0c;使其在长文本处理和精度召回方面表现优异&a…

程序开发时单数复数及前缀的命名规范(目录名、文件名、函数名、变量名、数据库字段等)

在程序开发中&#xff0c;我总是被单复数搞得头疼&#xff0c;以前采用了最舒服的方法&#xff0c;一刀切&#xff1a;全部单数&#xff0c;因为理由也很简单&#xff0c;单数都可以作为定语解释&#xff0c;比如/util&#xff0c;可以认为真正的名称是/util files或者/util di…

Spring Boot原理全解析:如何让开发更轻松高效(二)-起步依赖、自动装配

通过这篇博客&#xff0c;读者将能够掌握 Spring Boot 中的配置优先级和 Bean 管理的核心原理&#xff0c;为开发更加高效、可维护的 Spring Boot 应用打下坚实的基础。 目录 前言 起步依赖 自动配置 概述 常见方案 概述 方案一 方案二 总结 前言 通过这篇博客&#xf…

力扣动态规划基础版(矩阵型)

62.不同路径&#xff08;唯一路径问题&#xff09; 62. 不同路径https://leetcode.cn/problems/unique-paths/ 方法一&#xff1a;动态规划 找状态转移方程&#xff0c;也就是说它从左上角走到右下角&#xff0c;只能往右或者往下走&#xff0c;那么设置一个位置为&#xff…

Ubuntu 22 安装 Apache Doris 3.0.3 笔记

Ubuntu 22 安装 Apache Doris 3.0.3 笔记 1. 环境准备 Doris 需要 Java 17 作为运行环境&#xff0c;所以首先需要安装 Java 17。 sudo apt-get install openjdk-17-jdk -y sudo update-alternatives --config java在安装 Java 17 后&#xff0c;可以通过 sudo update-alter…

141/142题环形链表

本题返回环入口的位置。使用快慢指针&#xff0c;快指针每次移动两个&#xff0c;慢指针每次移动一个。设前一段距离是a,进入环内到slow和fast相遇的地点距离是b&#xff0c;环内剩下的距离是c&#xff0c;如图所示。 环的长度是bc 慢指针移动距离是ab 快指针移动距离是abk(bc…

leetcode25:k个一组链表反转

给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点内部的值…

《今日制造与升级》是什么级别的期刊?是正规期刊吗?能评职称吗?

​问题解答 问&#xff1a;《今日制造与升级》是不是核心期刊&#xff1f; 答&#xff1a;不是&#xff0c;是知网收录的正规学术期刊。 问&#xff1a;《今日制造与升级》级别&#xff1f; 答&#xff1a;国家级。主管单位&#xff1a;中国机械工业联合会 …

【Linux第八课-进程间通信】管道、共享内存、消息队列、信号量、信号、可重入函数、volatile

目录 进程间通信为什么&#xff1f;是什么&#xff1f;怎么办&#xff1f;一般规律具体做法 匿名管道原理代码 命名管道原理代码 system V共享内存消息队列信号量信号量的接口 信号概念为什么&#xff1f;怎么办&#xff1f;准备信号的产生信号的保存概念三张表匹配的操作和系统…

C++builder中的人工智能(18):神经网络中的SoftMax函数

在这篇文章中&#xff0c;我们将探讨SoftMax函数在神经网络中的作用&#xff0c;如何在人工神经网络&#xff08;ANN&#xff09;中使用SoftMax函数&#xff0c;以及在AI技术中SoftMax的应用场景。让我们来详细解释这些概念。 SoftMax函数是什么&#xff1f; SoftMax函数是逻辑…

证件照尺寸168宽240高,如何手机自拍更换蓝底

在提供学籍照片及一些社会化考试报名时&#xff0c;会要求我们提供尺寸为168*240像素的电子版证件照&#xff0c;本文将介绍如何使用“报名电子照助手”&#xff0c;借助手机拍照功能完成证件照的拍摄和背景更换&#xff0c;特别是如何将照片尺寸调整为168像素宽和240像素高&am…

pytest+request+allure接口自动化框架搭建分享

介绍分享一个接口自动化框架搭建方法 (pytestrequestallure)&#xff0c;这个方案是由 xpcs 同学在TesterHome社区网站的分享。 写在前面 去年11月被裁&#xff0c;到现在还没上岸&#xff0c;gap 半年了。上岸无望&#xff0c;专业技能不能落下&#xff0c;花了两三天时间&…

大数据新视界 -- 大数据大厂之 Impala 性能优化:融合机器学习的未来之路(上 (2-2))(11/30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

Unity 实现数字垂直滚动效果

Unity 实现数字垂直滚动效果 前言项目场景布置Shader代码编写材质球设置代码编写数字图片 前言 遇到一个需要数字垂直滚动模拟老虎机的效果&#xff0c;记录一下。 项目 场景布置 3个Image换上带有RollNumberShader的材质 在RollNumberScript脚本中引用即可 Shader代码编…

[linux]docker基础

常见命令 Docker最常见的命令就是操作镜像、容器的命令&#xff0c;详见官方文档: Docker Docs 案例: 查看DockerHub&#xff0c;拉取Nginx镜像&#xff0c;创建并运行Nginx容器 在DockerHub中搜索Nginx镜像 拉取Nginx镜像 查看本地镜像列表 把镜像保持到本地 查看保持命令的…

纯C++信号槽使用Demo (sigslot 库使用)

sigslot 库与QT的信号槽一样&#xff0c;通过发送信号&#xff0c;触发槽函数&#xff0c;信号槽不是QT的专利&#xff0c;早在2002年国外的一小哥用C写了sigslot 库&#xff0c;简单易用&#xff1b; 该库的官网&#xff08;喜欢阅读的小伙伴可以仔细研究&#xff09;&#xf…

(Go语言)Go里面的指针如何?函数与方法怎么不一样?带你了解Go不同于其他高级语言的语法

0. 序言 从这章开始&#xff0c;在Go基础语法里难度就开始上来了 在学习函数与方法前&#xff0c;先弄明白指针是很重要的。 1. 指针 在没学指针前&#xff0c;相信很多人就已经大概知道指针是个什么东西了。因为它太有名了&#xff0c;当然是与 C和C 的出名有关。 1.1 指针…

基于redis实现API接口访问次数限制

一&#xff0c;概述 日常开发中会有一个常见的需求&#xff0c;需要限制接口在单位时间内的访问次数&#xff0c;比如说某个免费的接口限制单个IP一分钟内只能访问5次。该怎么实现呢&#xff0c;通常大家都会想到用redis&#xff0c;确实通过redis可以实现这个功能&#xff0c…