1 parent
8191a2dbc9
commit
5e2fc9348c
15 files changed
+313
-39
No files matched your search
@@ -70,6 +70,15 @@
|
|||||||
<groupId>commons-codec</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>commons-codec</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.dom4j</groupId>
|
||||||
|
<artifactId>dom4j</artifactId>
|
||||||
|
<version>2.1.4</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
|
<artifactId>jackson-dataformat-xml</artifactId>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -0,0 +1,143 @@
|
|||||||
|
package com.f8q8.qywechatarchive.common.utils;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import tools.jackson.core.type.TypeReference;
|
||||||
|
import tools.jackson.databind.DeserializationFeature;
|
||||||
|
import tools.jackson.databind.JavaType;
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
import tools.jackson.databind.SerializationFeature;
|
||||||
|
import tools.jackson.databind.json.JsonMapper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class JsonTools {
|
||||||
|
|
||||||
|
private static final JsonMapper DEFAULT_MAPPER = buildDefaultMapper();
|
||||||
|
|
||||||
|
private static final JsonMapper PRETTY_MAPPER = buildPrettyMapper();
|
||||||
|
|
||||||
|
private static final XmlMapper XML_MAPPER = buildXmlMapper();
|
||||||
|
|
||||||
|
private JsonTools() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonMapper getDefaultMapper() {
|
||||||
|
return DEFAULT_MAPPER;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonMapper getPrettyMapper() {
|
||||||
|
return PRETTY_MAPPER;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonMapper buildDefaultMapper() {
|
||||||
|
return JsonMapper.builder().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||||
|
.defaultDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
|
||||||
|
.defaultTimeZone(TimeZone.getDefault()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonMapper buildPrettyMapper() {
|
||||||
|
return JsonMapper.builder().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
|
||||||
|
.defaultDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"))
|
||||||
|
.defaultTimeZone(TimeZone.getDefault()).enable(SerializationFeature.INDENT_OUTPUT).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static XmlMapper buildXmlMapper() {
|
||||||
|
return XmlMapper.builder().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列化
|
||||||
|
public static String toJson(Object Obj) {
|
||||||
|
try {
|
||||||
|
return DEFAULT_MAPPER.writeValueAsString(Obj);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
throw new RuntimeException("json序列化失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toPrettyJson(Object obj) {
|
||||||
|
try {
|
||||||
|
return PRETTY_MAPPER.writeValueAsString(obj);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("JSON序列化失败:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void writeToStream(Object obj, OutputStream outputStream) {
|
||||||
|
DEFAULT_MAPPER.writeValue(outputStream, obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 反序列化
|
||||||
|
public static <T> T fromJson(String json, Class<T> claszz) {
|
||||||
|
try {
|
||||||
|
return DEFAULT_MAPPER.readValue(json, claszz);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
throw new RuntimeException("JSON反序列化失败:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T fromJson(String json, TypeReference<T> typeRef) {
|
||||||
|
try {
|
||||||
|
return DEFAULT_MAPPER.readValue(json, typeRef);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("JSON 反序列化失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 泛型集合的辅助方法
|
||||||
|
public static <T> List<T> fromJsonToList(String json, Class<T> elementClass) {
|
||||||
|
try {
|
||||||
|
JavaType type = DEFAULT_MAPPER.getTypeFactory().constructCollectionType(List.class, elementClass);
|
||||||
|
return DEFAULT_MAPPER.readValue(json, type);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("JSON 反序列化为List失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T fromStream(InputStream in, Class<T> clazz) throws IOException {
|
||||||
|
return DEFAULT_MAPPER.readValue(in, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T fromStream(InputStream in, TypeReference<T> typeRef) throws IOException {
|
||||||
|
return DEFAULT_MAPPER.readValue(in, typeRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- JsonNode 操作 ----------
|
||||||
|
public static JsonNode toJsonNode(String json) {
|
||||||
|
try {
|
||||||
|
return DEFAULT_MAPPER.readTree(json);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("转换为 JsonNode 失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonNode valueToTree(Object obj) {
|
||||||
|
return DEFAULT_MAPPER.valueToTree(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T treeToValue(JsonNode node, Class<T> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,35 @@
|
|||||||
package com.f8q8.qywechatarchive.modules.api.action;
|
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.utils.WXBizJsonMsgCrypt;
|
||||||
import com.f8q8.qywechatarchive.modules.api.vo.CallbackVO;
|
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 lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
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.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(value = "api/v1/callback")
|
@RequestMapping(value = "api/v1/callback")
|
||||||
public class CallbackController {
|
public class CallbackController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
WeComSdkClient weComSdkClient;
|
||||||
|
@Resource
|
||||||
|
WeComSyncService weComSyncService;
|
||||||
|
|
||||||
@Value("${wecom.token}")
|
@Value("${wecom.token}")
|
||||||
String token;
|
String token;
|
||||||
@Value("${wecom.encodingAESKey}")
|
@Value("${wecom.encodingAESKey}")
|
||||||
@@ -21,24 +37,62 @@ public class CallbackController {
|
|||||||
@Value("${wecom.corp-id}")
|
@Value("${wecom.corp-id}")
|
||||||
String corpId;
|
String corpId;
|
||||||
|
|
||||||
@RequestMapping(value = "notify")
|
private static WXBizJsonMsgCrypt wxcpt;
|
||||||
public Object notify(CallbackVO callbackVO) {
|
|
||||||
log.info("企业微信回调数据:{}", callbackVO);
|
private WXBizJsonMsgCrypt getWxCpt() {
|
||||||
if (StringUtils.hasText(callbackVO.getEchostr())) {
|
if (wxcpt == null) {
|
||||||
// 下面逻辑用于校验
|
try {
|
||||||
return verifyRequest(callbackVO);
|
wxcpt = new WXBizJsonMsgCrypt(token, encodingAESKey, corpId);
|
||||||
} else {
|
} catch (AesException e) {
|
||||||
// 下面逻辑用于消息解密
|
log.error(e.getMessage(), e);
|
||||||
return ResultUtils.success();
|
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=<xml><ToUserName><![CDATA[wwb42d275eae6910cc]]></ToUserName><Encrypt><![CDATA[GrVgKnXnMS6/7T/PVitnID+W/1Ak9GHV0BkPaznk21Hg+01yZmIdfKhUPIcWPFEXd2ccyMTeO0i/ug6RyDe88vnrt2w5WZpZ2/rJqYBh9eAi+WBbHn/0Th3MoIER8/dChnGDbLn9T1oV+rrEI8wqlZre8oaYiDdfGqhNkQZ7xesaVJZhqHFswXdjSu239fbtEojKX9YS22ss34O6ZK9VEcO3mjV2rI1pQETLf4d7auWsKz1Sm2zkodALOZrrdEk/bn5md1QxmHXRZ2gEc4Pl0UHUji2HrQRiiCFYQ9lW1kMJs1Ib3ElSwNcLEuSaZJV/bW8I+TqfLWy0nPvawhYw0BsVCs5LofsVYtpYSuWca7wo6b3Ug9nzduoiHWBbO0ni]]></Encrypt><AgentID><![CDATA[2000004]]></AgentID></xml>
|
||||||
|
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) {
|
private String verifyRequest(CallbackVO callbackVO) {
|
||||||
String result = null;
|
String result = null;
|
||||||
if (verifyParam(callbackVO)) {
|
if (verifyParam(callbackVO)) {
|
||||||
try {
|
try {
|
||||||
WXBizJsonMsgCrypt wxcpt = new WXBizJsonMsgCrypt(token, encodingAESKey, corpId);
|
result = getWxCpt().VerifyURL(callbackVO.getMsg_signature(), String.valueOf(callbackVO.getTimestamp()),
|
||||||
result = wxcpt.VerifyURL(callbackVO.getMsg_signature(), String.valueOf(callbackVO.getTimestamp()),
|
|
||||||
callbackVO.getNonce(), callbackVO.getEchostr());
|
callbackVO.getNonce(), callbackVO.getEchostr());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage(), e);
|
log.error(e.getMessage(), e);
|
||||||
|
|||||||
@@ -15,42 +15,47 @@ 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
|
* JsonParse class
|
||||||
*
|
* <p>
|
||||||
* 提供提取消息格式中的密文及生成回复消息格式的接口.
|
* 提供提取消息格式中的密文及生成回复消息格式的接口.
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
class JsonParse {
|
class JsonParse {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 提取出 JSON 包中的加密消息
|
* 提取出 JSON 包中的加密消息
|
||||||
|
*
|
||||||
* @param jsontext 待提取的json字符串
|
* @param jsontext 待提取的json字符串
|
||||||
* @return 提取出的加密消息字符串
|
* @return 提取出的加密消息字符串
|
||||||
* @throws AesException
|
* @throws AesException
|
||||||
*/
|
*/
|
||||||
public static Object[] extract(String jsontext) throws AesException {
|
public static Object[] extract(String jsonText) throws AesException {
|
||||||
Object[] result = new Object[3];
|
Object[] result = new Object[3];
|
||||||
/*
|
|
||||||
try {
|
try {
|
||||||
JSONObject json = new JSONObject(jsontext);
|
JsonNode json = JsonTools.toJsonNode(jsonText);
|
||||||
String encrypt_msg = json.getString("encrypt");
|
String encrypt_msg = json.get("encrypt").asString();
|
||||||
String tousername = json.getString("tousername");
|
String tousername = json.get("tousername").asString();
|
||||||
String agentid = json.getString("agentid");
|
String agentid = json.get("agentid").asString();
|
||||||
|
|
||||||
result[0] = tousername;
|
result[0] = tousername;
|
||||||
result[1] = encrypt_msg;
|
result[1] = encrypt_msg;
|
||||||
result[2] = agentid;
|
result[2] = agentid;
|
||||||
return result;
|
return result;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
log.error(e.getMessage(), e);
|
||||||
throw new AesException(AesException.ParseJsonError);
|
throw new AesException(AesException.ParseJsonError);
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成json消息
|
* 生成json消息
|
||||||
|
*
|
||||||
* @param encrypt 加密后的消息密文
|
* @param encrypt 加密后的消息密文
|
||||||
* @param signature 安全签名
|
* @param signature 安全签名
|
||||||
* @param timestamp 时间戳
|
* @param timestamp 时间戳
|
||||||
|
|||||||
@@ -13,7 +13,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.f8q8.qywechatarchive.modules.api.utils;
|
package com.f8q8.qywechatarchive.modules.api.utils;
|
||||||
|
|
||||||
|
import com.f8q8.qywechatarchive.common.utils.JsonTools;
|
||||||
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.codec.binary.Base64;
|
||||||
|
import tools.jackson.databind.JsonNode;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
@@ -46,10 +48,10 @@ public class WXBizJsonMsgCrypt {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
|
*
|
||||||
* @param token 企业微信后台,开发者设置的token
|
* @param token 企业微信后台,开发者设置的token
|
||||||
* @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey
|
* @param encodingAesKey 企业微信后台,开发者设置的EncodingAESKey
|
||||||
* @param receiveid, 不同场景含义不同,详见文档
|
* @param receiveid, 不同场景含义不同,详见文档
|
||||||
*
|
|
||||||
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
||||||
*/
|
*/
|
||||||
public WXBizJsonMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException {
|
public WXBizJsonMsgCrypt(String token, String encodingAesKey, String receiveid) throws AesException {
|
||||||
@@ -204,7 +206,6 @@ public class WXBizJsonMsgCrypt {
|
|||||||
* @param replyMsg 企业微信待回复用户的消息,json格式的字符串
|
* @param replyMsg 企业微信待回复用户的消息,json格式的字符串
|
||||||
* @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
|
* @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp
|
||||||
* @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
|
* @param nonce 随机串,可以自己生成,也可以用URL参数的nonce
|
||||||
*
|
|
||||||
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的json格式的字符串
|
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的json格式的字符串
|
||||||
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
||||||
*/
|
*/
|
||||||
@@ -237,7 +238,6 @@ public class WXBizJsonMsgCrypt {
|
|||||||
* @param timeStamp 时间戳,对应URL参数的timestamp
|
* @param timeStamp 时间戳,对应URL参数的timestamp
|
||||||
* @param nonce 随机串,对应URL参数的nonce
|
* @param nonce 随机串,对应URL参数的nonce
|
||||||
* @param postData 密文,对应POST请求的数据
|
* @param postData 密文,对应POST请求的数据
|
||||||
*
|
|
||||||
* @return 解密后的原文
|
* @return 解密后的原文
|
||||||
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
||||||
*/
|
*/
|
||||||
@@ -246,10 +246,10 @@ public class WXBizJsonMsgCrypt {
|
|||||||
|
|
||||||
// 密钥,公众账号的app secret
|
// 密钥,公众账号的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中的签名比较是否相等
|
// 和URL中的签名比较是否相等
|
||||||
// System.out.println("第三方收到URL中的签名:" + msg_sign);
|
// System.out.println("第三方收到URL中的签名:" + msg_sign);
|
||||||
@@ -257,19 +257,17 @@ public class WXBizJsonMsgCrypt {
|
|||||||
if (!signature.equals(msgSignature)) {
|
if (!signature.equals(msgSignature)) {
|
||||||
throw new AesException(AesException.ValidateSignatureError);
|
throw new AesException(AesException.ValidateSignatureError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解密
|
// 解密
|
||||||
String result = decrypt(encrypt[1].toString());
|
return decrypt(encrypt.get("Encrypt").asString());
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证URL
|
* 验证URL
|
||||||
|
*
|
||||||
* @param msgSignature 签名串,对应URL参数的msg_signature
|
* @param msgSignature 签名串,对应URL参数的msg_signature
|
||||||
* @param timeStamp 时间戳,对应URL参数的timestamp
|
* @param timeStamp 时间戳,对应URL参数的timestamp
|
||||||
* @param nonce 随机串,对应URL参数的nonce
|
* @param nonce 随机串,对应URL参数的nonce
|
||||||
* @param echoStr 随机串,对应URL参数的echostr
|
* @param echoStr 随机串,对应URL参数的echostr
|
||||||
*
|
|
||||||
* @return 解密之后的echostr
|
* @return 解密之后的echostr
|
||||||
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
* @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息
|
||||||
*/
|
*/
|
||||||
@@ -281,8 +279,7 @@ public class WXBizJsonMsgCrypt {
|
|||||||
throw new AesException(AesException.ValidateSignatureError);
|
throw new AesException(AesException.ValidateSignatureError);
|
||||||
}
|
}
|
||||||
|
|
||||||
String result = decrypt(echoStr);
|
return decrypt(echoStr);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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<Element> 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,6 +16,9 @@ typedef struct MediaData {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
public class Finance {
|
public class Finance {
|
||||||
|
|
||||||
|
private static final Object SDK_LOCK = new Object();
|
||||||
|
|
||||||
public native static long NewSdk();
|
public native static long NewSdk();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
spring:
|
spring:
|
||||||
datasource:
|
datasource:
|
||||||
url: jdbc:mysql://frps.zxl78585.com:3306/qy_wechat_archive?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false
|
url: jdbc:mysql://frps.zxl78585.com:3306/qy_wechat_archive?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
|
||||||
username: qyWechat
|
username: wecom
|
||||||
password: qyWechat@2026Test
|
password: wecom@2026Prod
|
||||||
hikari:
|
hikari:
|
||||||
maximum-pool-size: 20
|
maximum-pool-size: 20
|
||||||
minimum-idle: 2
|
minimum-idle: 2
|
||||||
@@ -25,7 +25,7 @@ wecom:
|
|||||||
# 同步间隔(秒)
|
# 同步间隔(秒)
|
||||||
interval: 300
|
interval: 300
|
||||||
# 每次拉取条数
|
# 每次拉取条数
|
||||||
batch-size: 500
|
batch-size: 50
|
||||||
logging:
|
logging:
|
||||||
file:
|
file:
|
||||||
name: d:/logs/wecomArchive/wecomArchive.log
|
name: d:/logs/wecomArchive/wecomArchive.log
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
spring:
|
spring:
|
||||||
datasource:
|
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
|
username: wecom
|
||||||
password: wecom@2026Prod
|
password: wecom@2026Prod
|
||||||
hikari:
|
hikari:
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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 = "<xml><ToUserName><![CDATA[wwb42d275eae6910cc]]></ToUserName><Encrypt><![CDATA[GrVgKnXnMS6/7T/PVitnID+W/1Ak9GHV0BkPaznk21Hg+01yZmIdfKhUPIcWPFEXd2ccyMTeO0i/ug6RyDe88vnrt2w5WZpZ2/rJqYBh9eAi+WBbHn/0Th3MoIER8/dChnGDbLn9T1oV+rrEI8wqlZre8oaYiDdfGqhNkQZ7xesaVJZhqHFswXdjSu239fbtEojKX9YS22ss34O6ZK9VEcO3mjV2rI1pQETLf4d7auWsKz1Sm2zkodALOZrrdEk/bn5md1QxmHXRZ2gEc4Pl0UHUji2HrQRiiCFYQ9lW1kMJs1Ib3ElSwNcLEuSaZJV/bW8I+TqfLWy0nPvawhYw0BsVCs5LofsVYtpYSuWca7wo6b3Ug9nzduoiHWBbO0ni]]></Encrypt><AgentID><![CDATA[2000004]]></AgentID></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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user