我们在开发企业内部应用时,需要实现内部应用和企业微信的双向同步,即互联互通。 举个例子 同步一:企业内部OA系统在修改内部通讯录时,可以同步企业微信(直接调API接口即可) 同步二:在企业微信后台修改通讯录时,反馈给内部OA系统(本文讲解)
总的来说,实现通讯录回调的流程分为四步:
第一步:管理员在企业微信在后台修改通讯录信息(除了API接口修改之外的都算)
第二步:企业微信修改通讯录后,以XML的方式,向企业内部系统发送修改详情
第三步:企业内部系统收到XML信息后,解密信息
第四步:对于解密后的信息,修改企业内部系统的通讯录
具体实现
一、导包
解密工具下载地址
下载企业微信提供的解密工具,放到自己的项目内。
注意com包不能改名字
如果1.9版本的包无法通过编译,可以使用1.4的包
代码语言:javascript复制<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.4</version>
</dependency>
二、验证URL
在企业微信管理后台,配置URL
test方法为验证URL的模板,拿来即用
ParameterSettings是我放固定字段的类,相应参数替换即可
代码语言:javascript复制import com.qq.weixin.mp.aes.AesException;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
/**
* 验证URL
* @param request
* @param response
* @throws Exception
*/
public void test(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 微信加密签名
String msg_signature = request.getParameter("msg_signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");
System.out.println("request=" request.getRequestURL());
PrintWriter out = response.getWriter();
// 通过检验msg_signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
String result = null;
try {
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(ParameterSettings.YHHD_TOKEN, ParameterSettings.YHHD_EAK, ParameterSettings.AS_CORPID);
result = wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr);
} catch (AesException e) {
e.printStackTrace();
}
if (result == null) {
result = ParameterSettings.YHHD_TOKEN;
}
out.print(result);
out.close();
out = null;
}
原文CSDN链接:https://cloud.tencent.com/developer/article/2306868
三、解密
验证URL完成后,将该URL的接口方法替代成下方callBack方法的代码
当企业微信发送回调通知时,该方法会实现接收
该模板会将XML格式的数据转换为标准JSON,方便后续处理
JSON用了阿里的fastjson,maven依赖如下:
代码语言:javascript复制<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.9</version>
</dependency>
代码语言:javascript复制import com.alibaba.fastjson.JSONObject;
import com.qq.weixin.mp.aes.AesException;
import com.qq.weixin.mp.aes.WXBizMsgCrypt;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
/**
* 企业微信发送XML到这个方法
* @param request
* @param response
* @throws Exception
*/
@RequestMapping(value = "/callBack", method = RequestMethod.POST)
public void callBack(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
String respMessage = "";
respMessage=getDecryptMsg(request);
//进行回调处理
dealCallBackEvent(respMessage);
PrintWriter out = response.getWriter();
out.print(respMessage);
out.close();
}
/**
* 解密XML数据转换JSON
* @param request
* @return
*/
public String getDecryptMsg(HttpServletRequest request) {
String postData=""; // 密文,对应POST请求的数据
String result=""; // 明文,解密之后的结果
String msg_signature = request.getParameter("msg_signature"); // 微信加密签名
String timestamp = request.getParameter("timestamp"); // 时间戳
String nonce = request.getParameter("nonce"); // 随机数
try {
//1.获取加密的请求消息:使用输入流获得加密请求消息postData
ServletInputStream in = request.getInputStream();
BufferedReader reader =new BufferedReader(new InputStreamReader(in));
String tempStr=""; //作为输出字符串的临时串,用于判断是否读取完毕
while(null!=(tempStr=reader.readLine())){
postData =tempStr;
}
//2.获取消息明文:对加密的请求消息进行解密获得明文
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(ParameterSettings.YHHD_TOKEN, ParameterSettings.YHHD_EAK, ParameterSettings.YH_CORPID);
result = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, postData);
} catch (IOException e) {
e.printStackTrace();
} catch (AesException e) {
e.printStackTrace();
}
return result;
}
/**
* 对解密后的数据进行业务逻辑处理
* @param text
*/
private void dealCallBackEvent(String text) {
JSONObject json = XmlTool.documentToJSONObject(text);
String event = json.getString("Event");
String changeType = json.getString("ChangeType");
if (event.equals("change_contact")) {
if (changeType.equals("create_user")) { // 创建用户回调
String name = json.getString("Name");
String userID = json.getString("UserID");
String departmentStr = json.getString("Department"); // 部门为逗号分开的字符串
String[] departmentList = departmentStr.split(",");
String corpID = json.getString("ToUserName");
String mobile = json.getString("Mobile");
if (corpID.equals(ParameterSettings.YH_CORPID)) { // 替换企业ID
// 业务逻辑代码
}
}
}
}
核心代码到此结束,以下是其他所有代码
代码语言:javascript复制import java.util.List;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
public class XmlTool {
/**
* String 转 org.dom4j.Document
* @param xml
* @return
* @throws DocumentException
*/
public static Document strToDocument(String xml){
try {
//加上xml标签是为了获取最外层的标签,如果不需要可以去掉
return DocumentHelper.parseText(xml);
} catch (DocumentException e) {
return null;
}
}
/**
* org.dom4j.Document 转 com.alibaba.fastjson.JSONObject
* @param xml
* @return
* @throws DocumentException
*/
public static JSONObject documentToJSONObject(String xml){
return elementToJSONObject(strToDocument(xml).getRootElement());
}
/**
* org.dom4j.Element 转 com.alibaba.fastjson.JSONObject
* @param node
* @return
*/
public static JSONObject elementToJSONObject(Element node) {
JSONObject result = new JSONObject();
// 当前节点的名称、文本内容和属性
List<Attribute> listAttr = node.attributes();// 当前节点的所有属性的list
for (Attribute attr : listAttr) {// 遍历当前节点的所有属性
result.put(attr.getName(), attr.getValue());
}
// 递归遍历当前节点所有的子节点
List<Element> listElement = node.elements();// 所有一级子节点的list
if (!listElement.isEmpty()) {
for (Element e : listElement) {// 遍历所有一级子节点
if (e.attributes().isEmpty() && e.elements().isEmpty()) // 判断一级节点是否有属性和子节点
result.put(e.getName(), e.getTextTrim());// 沒有则将当前节点作为上级节点的属性对待
else {
if (!result.containsKey(e.getName())) // 判断父节点是否存在该一级节点名称的属性
result.put(e.getName(), new JSONArray());// 没有则创建
((JSONArray) result.get(e.getName())).add(elementToJSONObject(e));// 将该一级节点放入该节点名称的属性对应的值中
}
}
}
return result;
}
}
代码语言:javascript复制import lombok.Data;
import java.util.List;
@Data
public class Items {
List<Item> item;
}
代码语言:javascript复制import lombok.Data;
import java.util.List;
@Data
public class Item {
private String type;
private List<TextValue> text;
private String name;
private List<WebValue> web;
}
代码语言:javascript复制import lombok.Data;
import java.util.List;
@Data
public class Member {
private String toUserName;
private String fromUserName;
private String createTime;
private String msgType;
private String event;
private String changeType;
private String userID;
private String newUserID;
private String name;
private String department;
private String isLeaderInDept;
private String position;
private String mobile;
private String gender;
private String email;
private String status;
private String avatar;
private String alias;
private String telephone;
private String address;
private List<Items> extAttr;
}
代码语言:javascript复制import lombok.Data;
@Data
public class TextValue {
private String Value;
}
代码语言:javascript复制import lombok.Data;
@Data
public class WebValue {
private String Title;
private String Url;
}