🎨 #3755 【企业微信】修复会话存档SDK重复初始化导致接口超限问题
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user