關鍵區別說明(指令回調 vs 數據回調)
特性 | 指令回調 | 數據回調 |
---|---|---|
觸發場景 | 授權/取消授權等管理事件 | 通訊錄變更、應用菜單點擊等業務事件 |
關鍵字段 | InfoType | Event ?+?ChangeType |
典型事件 | suite_auth, cancel_auth | change_contact, suite_ticket |
響應要求 | 必須返回加密的"success" | 必須返回加密的"success" |
xml:?
<dependencies><!-- Spring Boot Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 企業微信官方加解密庫 --><dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-cp</artifactId><version>4.5.0</version></dependency><!-- XML處理 --><dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</version></dependency>
</dependencies>
controller
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.example.testchat.aes.WXBizMsgCrypt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;@RestController
@RequestMapping("/callback")
public class WxWorkCallbackController {private static final Logger logger = LoggerFactory.getLogger(WxWorkCallbackController.class);@Value("${qiyewx.token}")private String token;@Value("${qiyewx.encodingAESKey}")private String encodingAESKey;@Value("${qiyewx.corpid}")private String corpid;@Value("${qiyewx.suiteId}")private String suiteId;/*** 數據回調驗證接口 (GET請求)*/@GetMapping("/data")public String validateDataCallback(@RequestParam("msg_signature") String msgSignature,@RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce,@RequestParam("echostr") String echostr) {logger.info("收到數據回調驗證請求: signature={}, timestamp={}, nonce={}, echostr={}",msgSignature, timestamp, nonce, echostr);try {WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpid);String plainText = wxcpt.VerifyURL(msgSignature, timestamp, nonce, echostr);logger.info("驗證成功,明文: {}", plainText);return plainText;} catch (Exception e) {logger.error("驗證失敗", e);return "fail";}}/*** 專門處理suite_ticket推送(數據回調)*/@PostMapping(value = "/data", produces = "text/plain;charset=UTF-8")public String handleDataCallback(@RequestParam("msg_signature") String signature,@RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce,@RequestBody String encryptedMsg) {try {// 1. 解密消息WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpid);String plainText = wxcpt.DecryptMsg(signature, timestamp, nonce, encryptedMsg);// 2. 解析XMLMap<String, String> message = parseXml(plainText);if ("suite_ticket".equals(message.get("Event"))) {String suiteTicket = message.get("SuiteTicket");String suiteId = message.get("SuiteId");// 3. 保存ticket(示例代碼)saveSuiteTicket(suiteId, suiteTicket);logger.info("成功更新suite_ticket: {}", suiteTicket);}// 4. 關鍵點:返回加密的success!!!String encryptedSuccess = wxcpt.EncryptMsg("success", timestamp, nonce);return encryptedSuccess;} catch (Exception e) {logger.error("處理suite_ticket失敗", e);return "fail";}}private void saveSuiteTicket(String suiteId, String suiteTicket) {// 實現你的存儲邏輯,例如:// redisTemplate.opsForValue().set("wxwork:ticket:"+suiteId, suiteTicket, 20*60);System.out.println("suiteId: " + suiteId + "suiteTicket:" + suiteTicket);}/*** 指令回調驗證接口(GET請求)* 企業微信首次配置時會觸發此驗證*/@GetMapping("/cmd")public String validateCmdCallback(@RequestParam("msg_signature") String msgSignature,@RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce,@RequestParam("echostr") String echostr) {logger.info("[指令回調] 驗證請求 - signature:{}, timestamp:{}, nonce:{}, echostr:{}",msgSignature, timestamp, nonce, echostr);try {WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, suiteId);String plainText = wxcpt.VerifyURL(msgSignature, timestamp, nonce, echostr);logger.info("[指令回調] 驗證成功,明文: {}", plainText);
// return plainText; // 必須返回解密后的明文return "success";} catch (Exception e) {logger.error("[指令回調] 驗證失敗", e);return "fail";}}/*** 指令回調處理接口(POST請求)* 接收:授權成功、取消授權、變更授權等指令*/@PostMapping(value = "/cmd", produces = "application/xml;charset=UTF-8")public String handleCmdCallback(@RequestParam("msg_signature") String msgSignature,@RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce,@RequestBody String encryptedMsg) {logger.info("[指令回調] 收到消息 - signature:{}, timestamp:{}, nonce:{}",msgSignature, timestamp, nonce);try {// 1. 解密消息WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, suiteId);String plainText = wxcpt.DecryptMsg(msgSignature, timestamp, nonce, encryptedMsg);logger.info("[指令回調] 解密后消息: {}", plainText);// 2. 解析XML(復用數據回調的解析方法)Map<String, String> message = parseXml(plainText);String infoType = message.get("InfoType");String authCorpId = message.get("AuthCorpId");String SuiteTicket = message.get("SuiteTicket");saveSuiteTicket(authCorpId, SuiteTicket);// 3. 處理不同類型的指令switch (infoType) {case "suite_auth":// 授權成功事件(含臨時授權碼)String authCode = message.get("AuthCode");logger.info("[指令回調] 企業授權成功: corpId={}, authCode={}", authCorpId, authCode);// TODO: 調用企業微信API換取永久授權碼break;case "change_auth":// 授權變更事件(如權限集變更)String state = message.get("State");logger.info("[指令回調] 授權變更: corpId={}, state={}", authCorpId, state);break;case "cancel_auth":// 取消授權事件logger.info("[指令回調] 取消授權: corpId={}", authCorpId);// TODO: 清理該企業相關數據break;default:logger.warn("[指令回調] 未知指令類型: {}", infoType);}// 4. 必須返回加密的success
// return wxcpt.EncryptMsg("success", timestamp, nonce);return "success";} catch (Exception e) {logger.error("[指令回調] 處理失敗", e);return "fail";}}/*** 解析XML到Map*/private Map<String, String> parseXml(String xml) throws DocumentException {Map<String, String> result = new HashMap<>();Document document = DocumentHelper.parseText(xml);Element root = document.getRootElement();for (Iterator<Element> it = root.elementIterator(); it.hasNext(); ) {Element element = it.next();result.put(element.getName(), element.getText());}return result;}
}
yml
server:port: 8080servlet:context-path: /wxwork:token: 你的Token # 在企業微信后臺設置的回調TokenencodingAESKey: 你的EncodingAESKey # 在企業微信后臺設置的EncodingAESKeycorpId: 你的CorpID # 企業微信服務商的CorpIDsuiteId: 你的suiteId # 第三方應用id
踩坑:企業微信文檔寫的太爛了,而且坑也特別多,企業微信指令回調用的不是corpid,而是?
suiteId!!!!!!!!!!!