diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java index 7f9b69393..cdf559ad7 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java @@ -11,6 +11,7 @@ import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.WxCpMsgAuditService; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.bean.msgaudit.*; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import org.apache.commons.lang3.StringUtils; @@ -35,20 +36,59 @@ import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.MsgAudit.*; public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService { private final WxCpService cpService; + /** + * SDK初始化有效期,根据企微文档为7200秒 + */ + private static final int SDK_EXPIRES_TIME = 7200; + @Override public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception { - String configPath = cpService.getWxCpConfigStorage().getMsgAuditLibPath(); + // 获取或初始化SDK + long sdk = this.initSdk(); + + long slice = Finance.NewSlice(); + long ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice); + if (ret != 0) { + Finance.FreeSlice(slice); + throw new WxErrorException("getchatdata err ret " + ret); + } + + // 拉取会话存档 + String content = Finance.GetContentFromSlice(slice); + Finance.FreeSlice(slice); + WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content); + if (chatDatas.getErrCode().intValue() != 0) { + throw new WxErrorException(chatDatas.toJson()); + } + + chatDatas.setSdk(sdk); + return chatDatas; + } + + /** + * 获取或初始化SDK,如果SDK已过期则重新初始化 + * + * @return sdk id + * @throws WxErrorException 初始化失败时抛出异常 + */ + private synchronized long initSdk() throws WxErrorException { + WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage(); + + // 检查SDK是否已缓存且未过期 + if (!configStorage.isMsgAuditSdkExpired()) { + long cachedSdk = configStorage.getMsgAuditSdk(); + if (cachedSdk > 0) { + return cachedSdk; + } + } + + // SDK未初始化或已过期,需要重新初始化 + String configPath = configStorage.getMsgAuditLibPath(); if (StringUtils.isEmpty(configPath)) { throw new WxErrorException("请配置会话存档sdk文件的路径,不要配错了!!"); } - /** - * 完整的文件库路径: - * - * /www/osfile/libcrypto-1_1-x64.dll,libssl-1_1-x64.dll,libcurl-x64.dll,WeWorkFinanceSdk.dll, - * libWeWorkFinanceSdk_Java.so - */ // 替换斜杠 String replacePath = configPath.replace("\\", "/"); // 获取最后一个斜杠的下标,用作分割路径 @@ -79,36 +119,22 @@ public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService { Finance.loadingLibraries(osLib, prefixPath); long sdk = Finance.NewSdk(); - //因为会话存档单独有个secret,优先使用会话存档的secret - String msgAuditSecret = cpService.getWxCpConfigStorage().getMsgAuditSecret(); + // 因为会话存档单独有个secret,优先使用会话存档的secret + String msgAuditSecret = configStorage.getMsgAuditSecret(); if (StringUtils.isEmpty(msgAuditSecret)) { - msgAuditSecret = cpService.getWxCpConfigStorage().getCorpSecret(); + msgAuditSecret = configStorage.getCorpSecret(); } - long ret = Finance.Init(sdk, cpService.getWxCpConfigStorage().getCorpId(), msgAuditSecret); + long ret = Finance.Init(sdk, configStorage.getCorpId(), msgAuditSecret); if (ret != 0) { Finance.DestroySdk(sdk); throw new WxErrorException("init sdk err ret " + ret); } - long slice = Finance.NewSlice(); - ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice); - if (ret != 0) { - Finance.FreeSlice(slice); - Finance.DestroySdk(sdk); - throw new WxErrorException("getchatdata err ret " + ret); - } + // 缓存SDK + configStorage.updateMsgAuditSdk(sdk, SDK_EXPIRES_TIME); + log.debug("初始化会话存档SDK成功,sdk={}", sdk); - // 拉取会话存档 - String content = Finance.GetContentFromSlice(slice); - Finance.FreeSlice(slice); - WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content); - if (chatDatas.getErrCode().intValue() != 0) { - Finance.DestroySdk(sdk); - throw new WxErrorException(chatDatas.toJson()); - } - - chatDatas.setSdk(sdk); - return chatDatas; + return sdk; } @Override @@ -128,36 +154,27 @@ public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService { * @throws Exception the exception */ public String decryptChatData(long sdk, WxCpChatDatas.WxCpChatData chatData, Integer pkcs1) throws Exception { - /** - * 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。 - * msgAuditPriKey 会话存档私钥不能为空 - */ + // 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。 + // msgAuditPriKey 会话存档私钥不能为空 String priKey = cpService.getWxCpConfigStorage().getMsgAuditPriKey(); if (StringUtils.isEmpty(priKey)) { throw new WxErrorException("请配置会话存档私钥【msgAuditPriKey】"); } String decryptByPriKey = WxCpCryptUtil.decryptPriKey(chatData.getEncryptRandomKey(), priKey, pkcs1); - /** - * 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。 - */ + // 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。 long msg = Finance.NewSlice(); - /** - * 解密会话存档内容 - * sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。 - * 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。 - */ + // 解密会话存档内容 + // sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。 + // 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。 int ret = Finance.DecryptData(sdk, decryptByPriKey, chatData.getEncryptChatMsg(), msg); if (ret != 0) { Finance.FreeSlice(msg); - Finance.DestroySdk(sdk); throw new WxErrorException("msg err ret " + ret); } - /** - * 明文 - */ + // 明文 String plainText = Finance.GetContentFromSlice(msg); Finance.FreeSlice(msg); return plainText; @@ -209,7 +226,6 @@ public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService { ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, proxy, passwd, timeout, mediaData); if (ret != 0) { Finance.FreeMediaData(mediaData); - Finance.DestroySdk(sdk); throw new WxErrorException("getmediadata err ret " + ret); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java index 36203aab1..8b968e540 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java @@ -260,7 +260,36 @@ public interface WxCpConfigStorage { /** * 获取会话存档的secret + * * @return msg audit secret */ String getMsgAuditSecret(); + + /** + * 获取会话存档SDK + * 会话存档SDK初始化后有效期为7200秒,无需每次重新初始化 + * + * @return sdk id,如果未初始化或已过期返回0 + */ + long getMsgAuditSdk(); + + /** + * 检查会话存档SDK是否已过期 + * + * @return true: 已过期, false: 未过期 + */ + boolean isMsgAuditSdkExpired(); + + /** + * 更新会话存档SDK + * + * @param sdk sdk id + * @param expiresInSeconds 过期时间(秒) + */ + void updateMsgAuditSdk(long sdk, int expiresInSeconds); + + /** + * 使会话存档SDK过期 + */ + void expireMsgAuditSdk(); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java index 57647b371..4bf13f24e 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java @@ -49,6 +49,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable { private volatile String msgAuditSecret; private volatile String msgAuditPriKey; private volatile String msgAuditLibPath; + /** + * 会话存档SDK及其过期时间 + */ + private volatile long msgAuditSdk; + private volatile long msgAuditSdkExpiresTime; private volatile String oauth2redirectUri; private volatile String httpProxyHost; private volatile int httpProxyPort; @@ -444,10 +449,34 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable { /** * 设置会话存档secret - * @param msgAuditSecret + * + * @param msgAuditSecret the msg audit secret + * @return this */ public WxCpDefaultConfigImpl setMsgAuditSecret(String msgAuditSecret) { this.msgAuditSecret = msgAuditSecret; return this; } + + @Override + public long getMsgAuditSdk() { + return this.msgAuditSdk; + } + + @Override + public boolean isMsgAuditSdkExpired() { + return System.currentTimeMillis() > this.msgAuditSdkExpiresTime; + } + + @Override + public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) { + this.msgAuditSdk = sdk; + // 预留200秒的时间 + this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; + } + + @Override + public void expireMsgAuditSdk() { + this.msgAuditSdkExpiresTime = 0; + } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java index 4225cff0a..15c0ff672 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java @@ -50,6 +50,11 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage { private volatile File tmpDirFile; private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; private volatile String webhookKey; + /** + * 会话存档SDK及其过期时间(SDK是本地JVM变量,不适合存储到Redis) + */ + private volatile long msgAuditSdk; + private volatile long msgAuditSdkExpiresTime; /** * Instantiates a new Wx cp redis config. @@ -470,4 +475,26 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage { public String getMsgAuditSecret() { return null; } + + @Override + public long getMsgAuditSdk() { + return this.msgAuditSdk; + } + + @Override + public boolean isMsgAuditSdkExpired() { + return System.currentTimeMillis() > this.msgAuditSdkExpiresTime; + } + + @Override + public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) { + this.msgAuditSdk = sdk; + // 预留200秒的时间 + this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; + } + + @Override + public void expireMsgAuditSdk() { + this.msgAuditSdkExpiresTime = 0; + } }