clazz) {
+ try {
+ return DEFAULT_MAPPER.treeToValue(node, clazz);
+ } catch (Exception e) {
+ throw new RuntimeException("JsonNode 转换对象失败", e);
+ }
+ }
+
+ public static JsonNode parseXml(String xmlText) {
+ try {
+ com.fasterxml.jackson.databind.JsonNode jsonNode = XML_MAPPER.readTree(xmlText);
+ return toJsonNode(jsonNode.toString());
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ throw new RuntimeException("XML转JSON失败:", e);
+ }
+ }
+}
diff --git a/src/main/java/com/f8q8/qywechatarchive/modules/api/action/CallbackController.java b/src/main/java/com/f8q8/qywechatarchive/modules/api/action/CallbackController.java
index 73b9dac..3012217 100644
--- a/src/main/java/com/f8q8/qywechatarchive/modules/api/action/CallbackController.java
+++ b/src/main/java/com/f8q8/qywechatarchive/modules/api/action/CallbackController.java
@@ -1,19 +1,35 @@
package com.f8q8.qywechatarchive.modules.api.action;
-import com.f8q8.qywechatarchive.core.utils.ResultUtils;
+import com.f8q8.qywechatarchive.common.utils.JsonTools;
+import com.f8q8.qywechatarchive.modules.api.client.WeComSdkClient;
+import com.f8q8.qywechatarchive.modules.api.service.WeComSyncService;
+import com.f8q8.qywechatarchive.modules.api.utils.AesException;
import com.f8q8.qywechatarchive.modules.api.utils.WXBizJsonMsgCrypt;
import com.f8q8.qywechatarchive.modules.api.vo.CallbackVO;
+import jakarta.annotation.Resource;
+import jakarta.servlet.ServletInputStream;
+import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.util.StringUtils;
+import org.springframework.util.StreamUtils;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
+import tools.jackson.databind.JsonNode;
+
+import java.nio.charset.StandardCharsets;
@Slf4j
@RestController
@RequestMapping(value = "api/v1/callback")
public class CallbackController {
+ @Resource
+ WeComSdkClient weComSdkClient;
+ @Resource
+ WeComSyncService weComSyncService;
+
@Value("${wecom.token}")
String token;
@Value("${wecom.encodingAESKey}")
@@ -21,24 +37,62 @@ public class CallbackController {
@Value("${wecom.corp-id}")
String corpId;
- @RequestMapping(value = "notify")
- public Object notify(CallbackVO callbackVO) {
- log.info("企业微信回调数据:{}", callbackVO);
- if (StringUtils.hasText(callbackVO.getEchostr())) {
- // 下面逻辑用于校验
- return verifyRequest(callbackVO);
- } else {
- // 下面逻辑用于消息解密
- return ResultUtils.success();
+ private static WXBizJsonMsgCrypt wxcpt;
+
+ private WXBizJsonMsgCrypt getWxCpt() {
+ if (wxcpt == null) {
+ try {
+ wxcpt = new WXBizJsonMsgCrypt(token, encodingAESKey, corpId);
+ } catch (AesException e) {
+ log.error(e.getMessage(), e);
+ throw new RuntimeException(e);
+ }
}
+ return wxcpt;
+ }
+
+
+ @GetMapping(value = "notify")
+ public Object verify(CallbackVO callbackVO) {
+ log.info("企业微信验证数据:{}", callbackVO);
+ return verifyRequest(callbackVO);
+ }
+
+ @PostMapping(value = "notify")
+ public Object notify(HttpServletRequest request) {
+ try {
+ // 1. 读取 URL 上的签名参数
+ String msgSignature = request.getParameter("msg_signature");
+ String timestamp = request.getParameter("timestamp");
+ String nonce = request.getParameter("nonce");
+
+ // 2. 读取请求体中的 XML
+ ServletInputStream inputStream = request.getInputStream();
+ String postData = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
+ // msg_signature=cb7349c730ce79a415d977e7de3fcee7da709597, timestamp=1779089871, nonce=1778976713, body=
+ log.info("收到回调消息: msg_signature={}, timestamp={}, nonce={}, body={}",
+ msgSignature, timestamp, nonce, postData);
+ String message = getWxCpt().DecryptMsg(msgSignature, timestamp, nonce, postData);
+ // 判断是否是消息通知,如果是,拉取数据
+ JsonNode jsonNode = JsonTools.parseXml(message);
+ if (jsonNode != null && jsonNode.has("Event") && "msgaudit_notify".equals(jsonNode.get("Event").asString())) {
+ // 是会话消息事件,拉取消息内容
+ int count = weComSyncService.syncMessages(corpId);
+ log.info("同步了{}条消息", count);
+ }
+ log.info(message);
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+
+ return null;
}
private String verifyRequest(CallbackVO callbackVO) {
String result = null;
if (verifyParam(callbackVO)) {
try {
- WXBizJsonMsgCrypt wxcpt = new WXBizJsonMsgCrypt(token, encodingAESKey, corpId);
- result = wxcpt.VerifyURL(callbackVO.getMsg_signature(), String.valueOf(callbackVO.getTimestamp()),
+ result = getWxCpt().VerifyURL(callbackVO.getMsg_signature(), String.valueOf(callbackVO.getTimestamp()),
callbackVO.getNonce(), callbackVO.getEchostr());
} catch (Exception e) {
log.error(e.getMessage(), e);
diff --git a/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/JsonParse.java b/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/JsonParse.java
index 96df378..18f0b7f 100644
--- a/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/JsonParse.java
+++ b/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/JsonParse.java
@@ -15,46 +15,51 @@ package com.f8q8.qywechatarchive.modules.api.utils;
*/
+import com.f8q8.qywechatarchive.common.utils.JsonTools;
+import lombok.extern.slf4j.Slf4j;
+import tools.jackson.databind.JsonNode;
+
/**
* JsonParse class
- *
+ *
* 提供提取消息格式中的密文及生成回复消息格式的接口.
*/
+@Slf4j
class JsonParse {
/**
* 提取出 JSON 包中的加密消息
+ *
* @param jsontext 待提取的json字符串
* @return 提取出的加密消息字符串
* @throws AesException
*/
- public static Object[] extract(String jsontext) throws AesException {
+ public static Object[] extract(String jsonText) throws AesException {
Object[] result = new Object[3];
- /*
- try {
- JSONObject json = new JSONObject(jsontext);
- String encrypt_msg = json.getString("encrypt");
- String tousername = json.getString("tousername");
- String agentid = json.getString("agentid");
- result[0] = tousername;
- result[1] = encrypt_msg;
- result[2] = agentid;
- return result;
- } catch (Exception e) {
- e.printStackTrace();
- throw new AesException(AesException.ParseJsonError);
- }
- */
- return null;
+ try {
+ JsonNode json = JsonTools.toJsonNode(jsonText);
+ String encrypt_msg = json.get("encrypt").asString();
+ String tousername = json.get("tousername").asString();
+ String agentid = json.get("agentid").asString();
+
+ result[0] = tousername;
+ result[1] = encrypt_msg;
+ result[2] = agentid;
+ return result;
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ throw new AesException(AesException.ParseJsonError);
+ }
}
/**
* 生成json消息
- * @param encrypt 加密后的消息密文
+ *
+ * @param encrypt 加密后的消息密文
* @param signature 安全签名
* @param timestamp 时间戳
- * @param nonce 随机字符串
+ * @param nonce 随机字符串
* @return 生成的json字符串
*/
public static String generate(String encrypt, String signature, String timestamp, String nonce) {
diff --git a/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/WXBizJsonMsgCrypt.java b/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/WXBizJsonMsgCrypt.java
index 38dbbec..75d7986 100644
--- a/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/WXBizJsonMsgCrypt.java
+++ b/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/WXBizJsonMsgCrypt.java
@@ -13,7 +13,9 @@
*/
package com.f8q8.qywechatarchive.modules.api.utils;
+import com.f8q8.qywechatarchive.common.utils.JsonTools;
import org.apache.commons.codec.binary.Base64;
+import tools.jackson.databind.JsonNode;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
@@ -46,10 +48,10 @@ public class WXBizJsonMsgCrypt {
/**
* 构造函数
- * @param token 企业微信后台,开发者设置的token
- * @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey
- * @param receiveid, 不同场景含义不同,详见文档
*
+ * @param token 企业微信后台,开发者设置的token
+ * @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey
+ * @param receiveid, 不同场景含义不同,详见文档
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
public WXBizJsonMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException {
@@ -201,10 +203,9 @@ public class WXBizJsonMsgCrypt {
*
将消息密文和安全签名打包成json格式
*
*
- * @param replyMsg 企业微信待回复用户的消息,json格式的字符串
+ * @param replyMsg 企业微信待回复用户的消息,json格式的字符串
* @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
- * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
- *
+ * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的json格式的字符串
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
@@ -234,10 +235,9 @@ public class WXBizJsonMsgCrypt {
*
*
* @param msgSignature 签名串,对应URL参数的msg_signature
- * @param timeStamp 时间戳,对应URL参数的timestamp
- * @param nonce 随机串,对应URL参数的nonce
- * @param postData 密文,对应POST请求的数据
- *
+ * @param timeStamp 时间戳,对应URL参数的timestamp
+ * @param nonce 随机串,对应URL参数的nonce
+ * @param postData 密文,对应POST请求的数据
* @return 解密后的原文
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
@@ -246,10 +246,10 @@ public class WXBizJsonMsgCrypt {
// 密钥,公众账号的app secret
// 提取密文
- Object[] encrypt = JsonParse.extract(postData);
+ JsonNode encrypt = JsonTools.parseXml(postData);
// 验证安全签名
- String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt[1].toString());
+ String signature = SHA1.getSHA1(token, timeStamp, nonce, encrypt.get("Encrypt").asString());
// 和URL中的签名比较是否相等
// System.out.println("第三方收到URL中的签名:" + msg_sign);
@@ -257,19 +257,17 @@ public class WXBizJsonMsgCrypt {
if (!signature.equals(msgSignature)) {
throw new AesException(AesException.ValidateSignatureError);
}
-
// 解密
- String result = decrypt(encrypt[1].toString());
- return result;
+ return decrypt(encrypt.get("Encrypt").asString());
}
/**
* 验证URL
- * @param msgSignature 签名串,对应URL参数的msg_signature
- * @param timeStamp 时间戳,对应URL参数的timestamp
- * @param nonce 随机串,对应URL参数的nonce
- * @param echoStr 随机串,对应URL参数的echostr
*
+ * @param msgSignature 签名串,对应URL参数的msg_signature
+ * @param timeStamp 时间戳,对应URL参数的timestamp
+ * @param nonce 随机串,对应URL参数的nonce
+ * @param echoStr 随机串,对应URL参数的echostr
* @return 解密之后的echostr
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
*/
@@ -281,8 +279,7 @@ public class WXBizJsonMsgCrypt {
throw new AesException(AesException.ValidateSignatureError);
}
- String result = decrypt(echoStr);
- return result;
+ return decrypt(echoStr);
}
}
\ No newline at end of file
diff --git a/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/XmlTool.java b/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/XmlTool.java
new file mode 100644
index 0000000..8ad2bbd
--- /dev/null
+++ b/src/main/java/com/f8q8/qywechatarchive/modules/api/utils/XmlTool.java
@@ -0,0 +1,30 @@
+package com.f8q8.qywechatarchive.modules.api.utils;
+
+import lombok.extern.slf4j.Slf4j;
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+
+import java.util.List;
+
+@Slf4j
+public class XmlTool {
+
+ public static Object[] extract(String xmlText) throws AesException {
+ Object[] result = null;
+ try {
+ Document document = DocumentHelper.parseText(xmlText);
+ Element root = document.getRootElement();
+ List elements = root.elements();
+ result = new Object[elements.size()];
+ for (int i = 0; i < elements.size(); i++) {
+ Element element = elements.get(i);
+ result[i] = element.attributeValue("name");
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+ return result;
+ }
+
+}
diff --git a/src/main/java/com/tencent/wework/Finance.java b/src/main/java/com/tencent/wework/Finance.java
index 004c0a8..2ef0da7 100644
--- a/src/main/java/com/tencent/wework/Finance.java
+++ b/src/main/java/com/tencent/wework/Finance.java
@@ -16,6 +16,9 @@ typedef struct MediaData {
*/
public class Finance {
+
+ private static final Object SDK_LOCK = new Object();
+
public native static long NewSdk();
/**
diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml
index cfbff71..1483b47 100644
--- a/src/main/resources/application-dev.yml
+++ b/src/main/resources/application-dev.yml
@@ -1,8 +1,8 @@
spring:
datasource:
- url: jdbc:mysql://frps.zxl78585.com:3306/qy_wechat_archive?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
- username: qyWechat
- password: qyWechat@2026Test
+ url: jdbc:mysql://frps.zxl78585.com:3306/qy_wechat_archive?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
+ username: wecom
+ password: wecom@2026Prod
hikari:
maximum-pool-size: 20
minimum-idle: 2
@@ -25,7 +25,7 @@ wecom:
# 同步间隔(秒)
interval: 300
# 每次拉取条数
- batch-size: 500
+ batch-size: 50
logging:
file:
name: d:/logs/wecomArchive/wecomArchive.log
diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml
index 415fcf9..d804d63 100644
--- a/src/main/resources/application-prod.yml
+++ b/src/main/resources/application-prod.yml
@@ -1,6 +1,6 @@
spring:
datasource:
- url: jdbc:mysql://127.0.0.1:3306/wecom?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
+ url: jdbc:mysql://127.0.0.1:3306/wecom?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: wecom
password: wecom@2026Prod
hikari:
diff --git a/src/main/resources/native/win/WeWorkFinanceSdk.dll b/src/main/resources/native/win/WeWorkFinanceSdk.dll
index 4ce7286..8bf13a1 100644
Binary files a/src/main/resources/native/win/WeWorkFinanceSdk.dll and b/src/main/resources/native/win/WeWorkFinanceSdk.dll differ
diff --git a/src/main/resources/native/win/libcrypto-1_1-x64.dll b/src/main/resources/native/win/libcrypto-1_1-x64.dll
new file mode 100644
index 0000000..809b633
Binary files /dev/null and b/src/main/resources/native/win/libcrypto-1_1-x64.dll differ
diff --git a/src/main/resources/native/win/libcrypto-3-x64.dll b/src/main/resources/native/win/libcrypto-3-x64.dll
deleted file mode 100644
index b1a6dbe..0000000
Binary files a/src/main/resources/native/win/libcrypto-3-x64.dll and /dev/null differ
diff --git a/src/main/resources/native/win/libcurl-x64.dll b/src/main/resources/native/win/libcurl-x64.dll
index 8b79e41..e5c54cd 100644
Binary files a/src/main/resources/native/win/libcurl-x64.dll and b/src/main/resources/native/win/libcurl-x64.dll differ
diff --git a/src/main/resources/native/win/libssl-1_1-x64.dll b/src/main/resources/native/win/libssl-1_1-x64.dll
new file mode 100644
index 0000000..832b725
Binary files /dev/null and b/src/main/resources/native/win/libssl-1_1-x64.dll differ
diff --git a/src/test/java/com/f8q8/qywechatarchive/XmlTest.java b/src/test/java/com/f8q8/qywechatarchive/XmlTest.java
new file mode 100644
index 0000000..9899a0c
--- /dev/null
+++ b/src/test/java/com/f8q8/qywechatarchive/XmlTest.java
@@ -0,0 +1,33 @@
+package com.f8q8.qywechatarchive;
+
+import com.f8q8.qywechatarchive.common.utils.JsonTools;
+import lombok.extern.slf4j.Slf4j;
+import org.dom4j.Document;
+import org.dom4j.DocumentHelper;
+import org.dom4j.Element;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import tools.jackson.databind.JsonNode;
+
+@Slf4j
+@SpringBootTest
+public class XmlTest {
+
+ @Test
+ public void testXml() {
+ String xml = "";
+ try {
+ JsonNode json = JsonTools.parseXml(xml);
+ log.info(json.toPrettyString());
+ Document document = DocumentHelper.parseText(xml);
+ Element root = document.getRootElement();
+ for (Element element : root.elements()) {
+ log.info(element.getName());
+ log.info(element.getTextTrim());
+ }
+ } catch (Exception e) {
+ log.error(e.getMessage(), e);
+ }
+
+ }
+}