注册企业微信,开发企业微信服务商管理后台,应用管理-网页应用,建立一个应用。具体如下:
数据回调和指令回调,可以是一个接口,也可以是分开,在这里我使用的是一个接口。
数据回调是直接调用,查看是否接口可以调通。指令回调是给后端的接口传送推一些数据,比如suit_token等。
整体逻辑:
1、从request中拿到签名(signature)、时间戳(timestamp)、随机字符串(nonce) 和验证回调的URL的有效性传入的字符串(echostr).
/**url中的签名**/
String signature = request.getParameter("msg_signature");
/**url中的时间戳*/
String timestamp = request.getParameter("timestamp");
/** url中的随机字符串 **/
String nonce = request.getParameter("nonce");
/** 创建套件时验证回调url有效性时传入**/
String echostr = request.getParameter("echostr");
2、首先是一个不做任何操作,仅仅验证该url是否可以调用的操作。需要对echostr进行解密操作,然后再将解密后的值返回给企业微信,这样就证明这个URL可以对密文进行解密。是可以调通的。注意:这个时间在实例化WXBizMsgCrypt用的是corpId。 WXBizMsgCrypt是企业微信提供的工具类。
/**
* 企业回调的url-----该url不做任何的业务逻辑,仅仅微信查看是否可以调通
*/
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Token, EncodingAESKey, CorpID);
sEchoStr = wxcpt.verifyURL(signature, timestamp, nonce, echostr);
//必须要返回解密之后的明文
response.getWriter().write(sEchoStr);
WXBizMsgCrypt方法:
/**
* 构造函数
* @param token 公众平台上,开发者设置的token
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
* @param receiveId企业的corpid/suiteID,
*
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public WXBizMsgCrypt(String token, String encodingAesKey, String receiveId) throws AesException {
if (encodingAesKey.length() != 43) {
throw new AesException(AesException.IllegalAesKey);
}
this.token = token;
this.receiveId= receiveId;
aesKey = Base64.decodeBase64(encodingAesKey + "=");
}
3、指令回调需要传递一些数据到后端接口,这时候需要将这些数据(request.getInputStream)转化为string,然后再将这个string进行解密,转化成xml文件,然后根据xml文件中的类型,进行操作。注意:这个在实例化的时候用的是suiteID。
首先是将流转换为string的方法:
然后是将流转换为string,然后方法得到的string解密,然后再解析xml,根据xml中的 InfoType得到不同类型的回调数据
/**
* 数据回调URL,用于接收托管企业微信应用的用户消息和用户事件 系统将会把此应用的授权变更事件以及ticket参数等推送给此URL
* */
String postData = getStringInputstream(request);
QywxCallBackInfo callBackInfo = QywxUtil.getDecryptCallBackInfo(postData, signature, timestamp, nonce, QywxBase.sSuiteID);
String infoType = callBackInfo.getInfoType();
logger.warn("infoType"+infoType);
if (!StringUtils.isBlank(infoType)) {
switch (infoType) {
/**
* 开始推送---票据
*/
case "suite_ticket":
if (!StringUtils.isEmpty(callBackInfo.getSuiteTicket())) {
//得到suite_ticket
logger.warn("suiteId"+callBackInfo.getSuiteId()+"ticket"+ callBackInfo.getSuiteTicket());
}
break;
case "create_auth":
/**
* 开始授权----从企业微信应用市场发起授权时,企业微信后台会推送授权成功通知。
* 服务商的响应必须在1000ms内完成,以保证用户安装应用的体验。建议在接收到此事件时,先记录下AuthCode,并立即回 应企业微信,之后再做相关业务的处理。
*/
break;
case "change_auth":
break;
case "cancel_auth":
/**
* 取消授权
*/
break;
case "contact_sync":
break;
default:
break;
}
}
//返回值,必须是success
response.getWriter().write("success");
上面的代码用到的相关方法如下:
/**
* 根据不同的类型得到不同的回调
*/
public static QywxCallBackInfo getDecryptCallBackInfo(String encryptPostData, String msgSignature, String timestamp, String nonce, String receiveId) throws Exception {
QywxCallBackInfo callBackInfo = new QywxCallBackInfo();
/**
* 备注:在企业内部的工具类中有!from_corpid.equals(corpId)的校验,
* 但是在第三方应用的时候,由于postDate解密得到的是安装该应用的fromCorpID,所以不能进行比较
*/
WXBizMsgCrypt wxBizMsgCrypt = new WXBizMsgCrypt(QywxBase.sToken, QywxBase.sEncodingAESKey, receiveId);
String decryptPostData = wxBizMsgCrypt.DecryptMsg(msgSignature, timestamp, nonce, encryptPostData);
System.out.println("decryptPostData" + decryptPostData);
//开始解析xml
Element root = getXMLCDATA(decryptPostData);
System.out.println("root" + root);
Node infoTypeNode = root.getElementsByTagName("InfoType").item(0);
Node suiteIdNode = root.getElementsByTagName("SuiteId").item(0);
Node timestampNode = root.getElementsByTagName("TimeStamp").item(0);
Node authCorpIdNode = root.getElementsByTagName("AuthCorpId").item(0);
Node authCodeNode = root.getElementsByTagName("AuthCode").item(0);
Node suiteTicketNode = root.getElementsByTagName("SuiteTicket").item(0);
Node seqNode = root.getElementsByTagName("Seq").item(0);
if (infoTypeNode != null) {
callBackInfo.setInfoType(infoTypeNode.getTextContent());
}
if (suiteIdNode != null) {
callBackInfo.setSuiteId(suiteIdNode.getTextContent());
}
if (timestampNode != null) {
callBackInfo.setTimeStamp(timestampNode.getTextContent());
}
if (authCorpIdNode != null) {
callBackInfo.setAuthCorpId(authCorpIdNode.getTextContent());
}
if (authCodeNode != null) {
callBackInfo.setAuthCode(authCodeNode.getTextContent());
}
if (suiteTicketNode != null) {
callBackInfo.setSuiteTicket(suiteTicketNode.getTextContent());
}
if (seqNode != null) {
callBackInfo.setSeq(seqNode.getTextContent());
}
return callBackInfo;
}
public static Element getXMLCDATA(String requestStr) {
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
StringReader sr = new StringReader(requestStr);
InputSource is = new InputSource(sr);
Document document = db.parse(is);
Element root = document.getDocumentElement();
return root;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String getStringInputstream(HttpServletRequest request) {
StringBuffer strb = new StringBuffer();
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()));
String str = null;
while (null != (str = reader.readLine())) {
strb.append(str);
}
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
return strb.toString();
}
QywxCallBackInfo
是一个实体类,里面就是几个字段。
至此,已经完成了回调,如有不对之处,请指正。