一:企微实现和企业间的微信客服消息接收和事件原理
新版企微主要通过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