-# 企业微信-内部应用回调服务的应用与实现
第一章 域名与ip白名单
第二章 api的调用与实现
第三章 回调服务的应用与实现
前言
哈喽!之前我们聊了api的手动调用这章我们来聊聊回调服务。
首先什么是企业微信的回调服务?还是拿企业成员来聊吧(这个相对简单)。
比如有成员离职然后咱们内部系统中有资源属于他,是一定要做资源转交或者迁移的。
做迁移时系统需要知道迁移谁的数据,这里逻辑层面上有两种方式:
1、定时任务-每秒访问一次通讯录有成员变动就执行迁移。
2、回调模式-开一个接口让企业微信调用来告诉你成员变得信息。企微回调服务入口
由于企业微信有回调模式因此本人极力不推荐定时任务方案,但回调服务也有丢失消息的弊端。
下一章我将介绍如何使用利用RabiitMQ使其回调消息不丢失,有兴趣的朋友可以看一下。
(个人学习使用并不全面,如有错误欢迎指正。绝对听劝!)
一、配置回调服务
1.配置回调服务入口:
2.配置前:
上图中第一个空填后端服务中要接受企业微信消息的接口路径。(第二节重点讲解)
Token和EncodingAESKey随机获取即可,不过获取后要和后端的解析代码对应。
注意红框中的消息类型,如果不勾选这一类的消息不会做回调。
3.配置后
这里的回调服务器不一定要域名,用ip加端口也一样能回调成功,配置成功页面请看下图:
建议获取Token和EncodingAESKey后就立刻配置到后端的配置文件中。
当服务部署到服务器后在复制配置文件中的内容到该页面即可。
想要成功配置需要完成本章第三节,并且把服务部署到服务器上才行!!!!!!!!!!
二、解密工具
企业微信的回调需要AES加解密
可以看企业微信的官方接口加解密方案说明,当然不用看懂,会用就行了。
本章主要用java语言做后端服务的开发,因此主要看java的AES加解密,这是企业微信提供的Java版加解密工具 JavaAES加解密工具包
正式在项目中使用可以按我这个目录存放
加解密工具使用实现
主要是看WXBizMsgCrypt的VerifyURL或者DecryptMsg方法。
首先我们需要new一个WXBizMsgCrypt的实例,必须有三个入参:
参数 | 描述 |
---|---|
token | 配置消息服务器时随机生成的Token |
encodingAesKey | 配置消息服务器时随机生成的EncodingAESKey |
receiveid | 在内部应用开发中这里填corpId就可以了 |
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(callBackToken, callBackEncodingAESKey, corpId);
当我们有wxcpt 这个实例后可以调用VerifyURL方法或者DecryptMsg来解密
这个两个方法的公共参数如下:
参数 | 描述 |
---|---|
msgSignature | 加密签名由企业微信回调时提供 |
timestamp | 时间戳同上由企业微信回调时提供 |
nonce | 随机数戳同上由企业微信回调时提供 |
VerifyURL方法在上面三个参数的情况下还需要传入echoStr参数,代码如下:
//echoStr加密密文,只有在配置回调服务时企业微信会发一段密文让你解析,这个密文就是放到这个参数里的。
//一般解密成功后就直接将结果return就行了。
wxcpt.VerifyURL(msgSignature, timestamp, nonce, echoStr);
DecryptMsg方法将echoStr参数替换为xml解密后得到明文xml,代码如下:
//xml也是企业微信回调时穿过来的密文xml
String plainXml = wxcpt.DecryptMsg(msgSignature, timestamp, nonce, xml);
解密工具可以不用研究的很透彻,会用就行。深究伤脑!!!
三、回调接口编写
1.环境版本
JDK:17.0.12
mavem:3.6.3
spring-boot:3.3.3
2.引入库
使用lombok来简化实体类的代码
jaxb-api和jaxb-core以及jaxb-runtime用于解析企微回调的名为xml数据
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--xml解析工具-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0.1</version>
</dependency>
<!-- 添加 JAXB 运行时实现 -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.1</version>
</dependency>
3.配置文件
qywx:
url: https://qyapi.weixin.qq.com #企业微信api出口根路径
#如果嫌public-path麻烦可以舍弃这个配置url用:https://qyapi.weixin.qq.com/cgi-bin代替
public-path: cgi-bin #接口目录,最终会拼接在url后面,
debug-mode: false #是否开启api的debug-mode模式
corpId: 你的企业ID
secret: 你的应用密钥
call-back:
#最好用自己在企业微信上随机生成的
token: 自己生成
encodingAESKey: 自己生成
token和encodingAESKey看第一节的第二点随机获取就行了!
4.控制层
参数 | 名称 | 描述 |
---|---|---|
msgSignature | 企业微信加密签名 | 就是描述的那样该参数由企业微信回调时提供 |
timestamp | 时间戳 | 同上 |
nonce | 随机数 | 同上 |
echoStr | 加密字符 | 该字符串是由在配置回调服务器时有用,由企微传递 |
xml | 加密文档 | 所有企微的回调都会有该参数,通过解析它才能获取回调事件内容 |
该接口必须包含这五个参数并且是开放的(有安全框架的需忽略权限,其它自定义拦截逻辑请过滤该接口)
@RestController
public class CallBackController {
private final CallbackService callbackService;
public CallBackController(CallbackService callbackService) {
this.callbackService = callbackService;
}
/**
* 回调接口,由企业微信调用请开放
* 注意:
* echoStr,get
* xml
* @param msgSignature 企业微信加密签名
* @param timestamp 时间戳
* @param nonce 随机数
* @param echoStr 加密字符
* @param xml 加密文档
*/
@RequestMapping("/callback")
public String callback(@RequestParam(value = "msg_signature", required = false) String msgSignature,
@RequestParam(value = "timestamp", required = false) String timestamp,
@RequestParam(value = "nonce", required = false) String nonce,
@RequestParam(value = "echostr", required = false) String echoStr,
@RequestBody(required = false) String xml)
{
return callbackService.callback(msgSignature,timestamp,nonce,echoStr,xml);
}
}
5、服务层
public class CallbackService {
@Value("${qywx.corpId}")
private String corpId;
@Value("${qywx.call-back.token}")
private String callBackToken;
@Value("${qywx.call-back.encodingAESKey}")
private String callBackEncodingAESKey;
public String callback(String msgSignature, String timestamp, String nonce, String echoStr,String xml) {
if (echoStr==null&&xml==null){
return "解析内容为空,解析失败";
}
if (msgSignature==null||timestamp==null||nonce==null){
return "辅助解析参数为空,解析失败";
}
String result = "成功";
try {
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(callBackToken, callBackEncodingAESKey, corpId);
//这里注意在配置url时企业微信会调用一次该接口,此时echoStr是有值的
//为了通过企业微信的校验这里需要解密并且将明文返回
if (echoStr!=null&&!echoStr.isBlank()){
//调用工具包解密
return wxcpt.VerifyURL(msgSignature, timestamp, nonce, echoStr);
}else {
//如果echoStr没值,那肯定是企业的业务回调了
String plainXml = wxcpt.DecryptMsg(msgSignature, timestamp, nonce, xml);
//企业微信回调的明文是xml,下面我用javax.xml来做一个解析。
CommonEventData xmlObject = XMLUtil.convertXmlStrToObject(CommonEventData.class, plainXml);
//因为企业微信有各种各样的回调xml类型,因此需要做抽象类,并以对应子类返回数据
if ("text".equals(xmlObject.getMsgType())){
System.out.println("text");
//文本消息回调的业务逻辑
TextMsgEventData textMsgEventData = XMLUtil.convertXmlStrToObject(TextMsgEventData.class, xml);
System.out.println(textMsgEventData);
}
//如果你需要加其它业务逻辑需要自己建BaseEventData的子类来对应
//当然这只是我使用了javax.xml来解析xml的案例,有更好用的xml包可以替换
if ("event".equals(xmlObject.getMsgType())){
//事件消息回调的业务逻辑,审批和成员变动都是该类型
//该类型的消息还有很多不同内容的xml需要特别注意(用我这个方法就需要建很多的子类来对应)
System.out.println("event");
//这里我们忽略Event的类型直接用ChangeType来判断使用哪个类进行封装
switch(xmlObject.getChangeType()){
case "create_user":
case "update_user":
case "delete_user":
UserEventData userEventData = XMLUtil.convertXmlStrToObject(UserEventData.class, xml);
System.out.println(userEventData);
}
}
}
} catch (AesException e) {
throw new RuntimeException(e);
}
return result;
}
}
六、项目源码
总结
本章主要难点是编写正确的回调接口,只要注意配置文件的内容和企业微信上的一一对应(Token和EncodingAESKey)的,并且解密工具包的入参顺序没问题,就能成功。还有一个麻烦的点在于没办法本地测试只能部署到服务器后调用才能判断接口是否正确。