最近在搞 SaaS 平台的测试,其中包括了企业微信的第三方应用一系列操作,我这里第三方应用主要去解决消息订阅和审核审批,真的是有源源不断的大坑等着你去跳,收获满满不禁吐槽企业微信的文档是真的对第一次使用的人不怎么友好,可能还是我技术不到家的原因吧。
事实证明确实是我找文档造成这一系列的问题,我都是边有问题边问度娘,如果没有搜索到我就会去看官网相应文档。文档没有必要从头看到尾,反正遇到什么问题就去看相应的部分内容这样效率更加高。业务开发没有必要去仔细研究人家那套技术,毕竟目前更加在意业务处理速度,当完全解决了业务问题感兴趣就可以去看或琢磨实现的逻辑的思路。
回调配置:https://developer.work.weixin.qq.com/document/path/91116
在企业微信里面可以通过企业微信接口调试工具来简单验证我们写的一些接口是否符合逻辑。
第一次接触的接口就是创建完第三方应用的接口回调测试,其中包括数据回调、指令回调两个接口测试。
我根据文档所描述的那样去操作写了个简单的数据回调接口,并且可以通过接口回调测试,但是数据回调 URL 申请校验一直通过不了。
com.example.wxutil.AesException: corpid校验失败
根据报错信息粗略地知道是 corpid 的问题,于是乎我开始找细节看看是不是落掉什么。果然在 【加解密方案说明 】 找到了怎么一句话:
附注:ReceiveId 含义
加解密库里,ReceiveId 在各个场景的含义不同:
企业应用的回调,表示corpid
第三方事件的回调,表示suiteid
个人主体的第三方应用的回调,ReceiveId是一个空字符串
在创建 WXBizMsgCrypt 实例化时,会将 receiveid 进行设置,这个就是 corpid 设置的对应参数,因为我的是个人主体的第三方应用的回调,所以直接将 receiveid 设置为空字符串就可以了,于是乎问题就解决了。
第三方回调协议:https://developer.work.weixin.qq.com/document/10982
加解密方案说明:https://developer.work.weixin.qq.com/document/path/90968
在回调协议中,显然易见的是大部分操作都会经过指令回调 URL 且是以 POST 请求方式来请求的,参数以 XML 格式进行展示。
以刷新 Ticket 为例,进行参数拆分解析。
/**
* 刷新 Ticket 会触发指令回调
* @param param 企业应用信息
* @param request 请求体
* @return
*/
@PostMapping("/commoncall")
public String postCommonCall(ParamForWeChat param, HttpServletRequest request) throws IOException {
// 获取请求包体的内容
String content = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
System.out.println(param + "\n" + request.getRequestURI() + "\n" + content);
return "success";
}
<xml>
<ToUserName>ToUserName>
<Encrypt>Encrypt>
<AgentID>AgentID>
xml>
ToUserName:产生事件的 SuiteId;
Encrypt:加密的 XML 内容,解密后可获得事件请求包体的XML内容
我一开始以为 DecryptMsg 加密函数的参数 postData 是直接传 Encrypt 的内容,这真的是我多此一举。因为我原有的 Java 代码是直接以对象的形式来解析 XML 的,其实大家大可不必这样直接将上面的 content 赋值给形参 postData,它底层代码会直接去解析 XML ,我这真的是画蛇添足。
错误代码
@PostMapping("/commoncall")
public String postCommonCall(ParamForWeChat param1, @RequestBody XmlForWeChat param2, HttpServletRequest request) throws AesException {
String sReplyEchoStr = param2.DecryptMsg(weCom, param1);
System.out.println(param1 + "\n" + request.getRequestURI() + "\n" + param2 + "\n" + sReplyEchoStr);
return "success";
}
@Data
@JacksonXmlRootElement(localName = "xml")
public class XmlForWeChat {
/**
* 产生事件的 SuiteId
*/
@JacksonXmlProperty(localName = "ToUserName")
private String toUserName;
/**
* 加密的 XML 内容,解密后可获得事件请求包体的 XML 内容
*/
@JacksonXmlProperty(localName = "Encrypt")
private String encrypt;
/**
* 默认为空
*/
@JacksonXmlProperty(localName = "AgentID")
private String agentId;
/**
* 事件回调处理
*
* - 对 msg_signature 进行校验
* - 解密 Encrypt 得到明文的消息结构体
* - 如果需要被动回复消息 构造被动响应包
* - 正确响应本次请求
*
*/
public String DecryptMsg(WeCom weCom, ParamForWeChat param) throws AesException {
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(weCom);
// 解密数据包
return wxcpt.DecryptMsg(param.getMsg_signature(), param.getTimestamp(), param.getNonce(), this.encrypt);
}
}
正确代码
/**
* 刷新 Ticket 会触发指令回调
* @param param 企业应用信息
* @param request 请求体
* @return
*/
@PostMapping("/commoncall")
public String postCommonCall(ParamForWeChat param, HttpServletRequest request) throws AesException, IOException {
String content = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
weCom.setSCorpID("wwc2564e5eb48af836");
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(weCom);
String sReplyEchoStr = wxcpt.DecryptMsg(param.getMsg_signature(), param.getTimestamp(), param.getNonce(), content);
System.out.println(param + "\n" + request.getRequestURI() + "\n" + sReplyEchoStr);
return "success";
}
事件 Encrypt 解密后,其标准节点有 SuiteId、InfoType、TimeStamp 这三个参数,还有一些非标准节点这就需要根据自己的业务来处理相关事件的参数了。