cuilei
2025-07-02 cd8f73819900dd41e1a92b0393e67da57684f9a3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package org.jeecg.modules.qywx.message.controller;
 
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.jeecg.modules.qywx.message.utils.WXBizMsgCrypt;
import org.springframework.web.bind.annotation.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
 
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
 
/**
 * 企业微信消息回调接口(支持 URL 验证 + 消息接收)
 */
@RestController
@RequestMapping("/qywx/message/callback")
@Slf4j
public class WeComCallbackController {
 
    // 替换为你的应用配置
    private static final String TOKEN = "bPGpQimYTtjAfAj2uTE4fb";
    private static final String AES_KEY = "hkbscaEtxWAydMIoWe8EW4drpHXaYroQPKBMoKCs2bf"; // EncodingAESKey
    private static final String CORP_ID = "ww5999b2643c95fa75"; // 企业 ID 或 AppId
 
    private static WXBizMsgCrypt wxBizMsgCrypt;
 
    static {
        try {
            wxBizMsgCrypt = new WXBizMsgCrypt(TOKEN, AES_KEY, CORP_ID);
        } catch (Exception e) {
            throw new RuntimeException("初始化 WXBizMsgCrypt 失败,请检查 Token/AESKey/AppId 是否正确", e);
        }
    }
 
    /**
     * GET 请求:用于 URL 验证(Token 校验)
     */
    @GetMapping(produces = "text/plain;charset=utf-8")
    public String verifyUrl(
            @RequestParam("msg_signature") String msgSignature,
            @RequestParam("timestamp") String timestamp,
            @RequestParam("nonce") String nonce,
            @RequestParam("echostr") String echostr) {
        try {
            log.info("收到企业微信 URL 验证请求:msg_signature={}, timestamp={}, nonce={}, echostr={}", msgSignature, timestamp, nonce, echostr);
 
            // 调用官方 SDK 验证并解密
            String decryptedEchoStr = wxBizMsgCrypt.VerifyURL(msgSignature, timestamp, nonce, echostr);
            log.info("解密后的 echostr: {}", decryptedEchoStr);
 
            // 返回解密后的内容(企业微信要求必须原样返回)
            return decryptedEchoStr;
        } catch (Exception e) {
            log.error("URL 验证失败", e);
            return "fail";
        }
    }
 
    /**
     * POST 请求:接收企业微信推送的消息
     */
    @PostMapping(produces = "text/xml;charset=utf-8")
    public String handleWeComCallback(@RequestBody String xmlData,
                                      @RequestParam("msg_signature") String msgSignature,
                                      @RequestParam("timestamp") String timestamp,
                                      @RequestParam("nonce") String nonce) {
        try {
            log.info("收到企业微信消息推送:msg_signature={}, timestamp={}, nonce={}", msgSignature, timestamp, nonce);
            log.info("原始 XML 数据: {}", xmlData);
 
            // Step 1: 解密消息
            String plainXml = wxBizMsgCrypt.DecryptMsg(msgSignature, timestamp, nonce, xmlData);
            log.info("解密后的明文消息: {}", plainXml);
 
            // Step 2: 解析 XML 并处理业务逻辑
            Map<String, String> msgMap = parseXmlToMap(plainXml);
            String eventType = msgMap.get("Event");
            String content = msgMap.get("Content");
 
            log.info("事件类型: {}, 消息内容: {}", eventType, content);
 
            // Step 3: 业务处理(示例:回复 success)
            String responseXml = "<xml><returncode>0</returncode><returndata>success</returndata></xml>";
            return responseXml;
        } catch (Exception e) {
            log.error("处理企业微信消息失败", e);
            return "<xml><returncode>1</returncode><returndata>fail</returndata></xml>";
        }
    }
 
    /**
     * 将 XML 转为 Map
     */
    private Map<String, String> parseXmlToMap(String xml) throws Exception {
        Map<String, String> map = new HashMap<>();
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        StringReader reader = new StringReader(xml);
        InputSource source = new InputSource(reader);
        Document doc = builder.parse(source);
        Element root = doc.getDocumentElement();
        NodeList list = root.getChildNodes();
 
        for (int i = 0; i < list.getLength(); i++) {
            if (list.item(i).getNodeType() == Document.ELEMENT_NODE) {
                Element element = (Element) list.item(i);
                map.put(element.getNodeName(), element.getTextContent());
            }
        }
        return map;
    }
 
    //public static void main(String[] args) throws UnsupportedEncodingException {
    //    String msg_signature = "eae930cbae5d988ed29f8aac6cf221016f81669e";
    //    String timestamp = "1751356163";
    //    String nonce = "16ljuxd4gd8";
    //    String echostr = "Ax30h%2BharWss%2FeNNQl8x4KdzggrpmLFH8i%2F0mHqVGbcKcq1NeascovZV%2B08ooq5o0ng8RJ2WPyo69A8oGhKpxA%3D%3D";
    //
    //    String decode = URLDecoder.decode(echostr, "UTF-8");
    //    //System.out.println(decode);
    //    WeComCallbackController weComCallbackController = new WeComCallbackController();
    //    String decryptedEchoStr = weComCallbackController.verifyUrl(msg_signature, timestamp, nonce, decode);
    //    System.out.println(decryptedEchoStr);
    //}
}