Skip to content

Commit 1da6cf5

Browse files
authored
🎨 #3755 【企业微信】修复会话存档SDK重复初始化导致接口超限问题
1 parent e655a33 commit 1da6cf5

File tree

4 files changed

+148
-47
lines changed

4 files changed

+148
-47
lines changed

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java

Lines changed: 62 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import me.chanjar.weixin.cp.api.WxCpMsgAuditService;
1212
import me.chanjar.weixin.cp.api.WxCpService;
1313
import me.chanjar.weixin.cp.bean.msgaudit.*;
14+
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
1415
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
1516
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
1617
import org.apache.commons.lang3.StringUtils;
@@ -35,20 +36,59 @@
3536
public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
3637
private final WxCpService cpService;
3738

39+
/**
40+
* SDK初始化有效期,根据企微文档为7200秒
41+
*/
42+
private static final int SDK_EXPIRES_TIME = 7200;
43+
3844
@Override
3945
public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd,
4046
@NonNull long timeout) throws Exception {
41-
String configPath = cpService.getWxCpConfigStorage().getMsgAuditLibPath();
47+
// 获取或初始化SDK
48+
long sdk = this.initSdk();
49+
50+
long slice = Finance.NewSlice();
51+
long ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
52+
if (ret != 0) {
53+
Finance.FreeSlice(slice);
54+
throw new WxErrorException("getchatdata err ret " + ret);
55+
}
56+
57+
// 拉取会话存档
58+
String content = Finance.GetContentFromSlice(slice);
59+
Finance.FreeSlice(slice);
60+
WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content);
61+
if (chatDatas.getErrCode().intValue() != 0) {
62+
throw new WxErrorException(chatDatas.toJson());
63+
}
64+
65+
chatDatas.setSdk(sdk);
66+
return chatDatas;
67+
}
68+
69+
/**
70+
* 获取或初始化SDK,如果SDK已过期则重新初始化
71+
*
72+
* @return sdk id
73+
* @throws WxErrorException 初始化失败时抛出异常
74+
*/
75+
private synchronized long initSdk() throws WxErrorException {
76+
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
77+
78+
// 检查SDK是否已缓存且未过期
79+
if (!configStorage.isMsgAuditSdkExpired()) {
80+
long cachedSdk = configStorage.getMsgAuditSdk();
81+
if (cachedSdk > 0) {
82+
return cachedSdk;
83+
}
84+
}
85+
86+
// SDK未初始化或已过期,需要重新初始化
87+
String configPath = configStorage.getMsgAuditLibPath();
4288
if (StringUtils.isEmpty(configPath)) {
4389
throw new WxErrorException("请配置会话存档sdk文件的路径,不要配错了!!");
4490
}
4591

46-
/**
47-
* 完整的文件库路径:
48-
*
49-
* /www/osfile/libcrypto-1_1-x64.dll,libssl-1_1-x64.dll,libcurl-x64.dll,WeWorkFinanceSdk.dll,
50-
* libWeWorkFinanceSdk_Java.so
51-
*/
5292
// 替换斜杠
5393
String replacePath = configPath.replace("\\", "/");
5494
// 获取最后一个斜杠的下标,用作分割路径
@@ -79,36 +119,22 @@ public WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, S
79119

80120
Finance.loadingLibraries(osLib, prefixPath);
81121
long sdk = Finance.NewSdk();
82-
//因为会话存档单独有个secret,优先使用会话存档的secret
83-
String msgAuditSecret = cpService.getWxCpConfigStorage().getMsgAuditSecret();
122+
// 因为会话存档单独有个secret,优先使用会话存档的secret
123+
String msgAuditSecret = configStorage.getMsgAuditSecret();
84124
if (StringUtils.isEmpty(msgAuditSecret)) {
85-
msgAuditSecret = cpService.getWxCpConfigStorage().getCorpSecret();
125+
msgAuditSecret = configStorage.getCorpSecret();
86126
}
87-
long ret = Finance.Init(sdk, cpService.getWxCpConfigStorage().getCorpId(), msgAuditSecret);
127+
long ret = Finance.Init(sdk, configStorage.getCorpId(), msgAuditSecret);
88128
if (ret != 0) {
89129
Finance.DestroySdk(sdk);
90130
throw new WxErrorException("init sdk err ret " + ret);
91131
}
92132

93-
long slice = Finance.NewSlice();
94-
ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
95-
if (ret != 0) {
96-
Finance.FreeSlice(slice);
97-
Finance.DestroySdk(sdk);
98-
throw new WxErrorException("getchatdata err ret " + ret);
99-
}
100-
101-
// 拉取会话存档
102-
String content = Finance.GetContentFromSlice(slice);
103-
Finance.FreeSlice(slice);
104-
WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content);
105-
if (chatDatas.getErrCode().intValue() != 0) {
106-
Finance.DestroySdk(sdk);
107-
throw new WxErrorException(chatDatas.toJson());
108-
}
133+
// 缓存SDK
134+
configStorage.updateMsgAuditSdk(sdk, SDK_EXPIRES_TIME);
135+
log.debug("初始化会话存档SDK成功,sdk={}", sdk);
109136

110-
chatDatas.setSdk(sdk);
111-
return chatDatas;
137+
return sdk;
112138
}
113139

114140
@Override
@@ -128,36 +154,27 @@ public WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.Wx
128154
* @throws Exception the exception
129155
*/
130156
public String decryptChatData(long sdk, WxCpChatDatas.WxCpChatData chatData, Integer pkcs1) throws Exception {
131-
/**
132-
* 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。
133-
* msgAuditPriKey 会话存档私钥不能为空
134-
*/
157+
// 企业获取的会话内容,使用企业自行配置的消息加密公钥进行加密,企业可用自行保存的私钥解开会话内容数据。
158+
// msgAuditPriKey 会话存档私钥不能为空
135159
String priKey = cpService.getWxCpConfigStorage().getMsgAuditPriKey();
136160
if (StringUtils.isEmpty(priKey)) {
137161
throw new WxErrorException("请配置会话存档私钥【msgAuditPriKey】");
138162
}
139163

140164
String decryptByPriKey = WxCpCryptUtil.decryptPriKey(chatData.getEncryptRandomKey(), priKey, pkcs1);
141-
/**
142-
* 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。
143-
*/
165+
// 每次使用DecryptData解密会话存档前需要调用NewSlice获取一个slice,在使用完slice中数据后,还需要调用FreeSlice释放。
144166
long msg = Finance.NewSlice();
145167

146-
/**
147-
* 解密会话存档内容
148-
* sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。
149-
* 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。
150-
*/
168+
// 解密会话存档内容
169+
// sdk不会要求用户传入rsa私钥,保证用户会话存档数据只有自己能够解密。
170+
// 此处需要用户先用rsa私钥解密encrypt_random_key后,作为encrypt_key参数传入sdk来解密encrypt_chat_msg获取会话存档明文。
151171
int ret = Finance.DecryptData(sdk, decryptByPriKey, chatData.getEncryptChatMsg(), msg);
152172
if (ret != 0) {
153173
Finance.FreeSlice(msg);
154-
Finance.DestroySdk(sdk);
155174
throw new WxErrorException("msg err ret " + ret);
156175
}
157176

158-
/**
159-
* 明文
160-
*/
177+
// 明文
161178
String plainText = Finance.GetContentFromSlice(msg);
162179
Finance.FreeSlice(msg);
163180
return plainText;
@@ -209,7 +226,6 @@ public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String pr
209226
ret = Finance.GetMediaData(sdk, indexbuf, sdkfileid, proxy, passwd, timeout, mediaData);
210227
if (ret != 0) {
211228
Finance.FreeMediaData(mediaData);
212-
Finance.DestroySdk(sdk);
213229
throw new WxErrorException("getmediadata err ret " + ret);
214230
}
215231

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,36 @@ public interface WxCpConfigStorage {
260260

261261
/**
262262
* 获取会话存档的secret
263+
*
263264
* @return msg audit secret
264265
*/
265266
String getMsgAuditSecret();
267+
268+
/**
269+
* 获取会话存档SDK
270+
* 会话存档SDK初始化后有效期为7200秒,无需每次重新初始化
271+
*
272+
* @return sdk id,如果未初始化或已过期返回0
273+
*/
274+
long getMsgAuditSdk();
275+
276+
/**
277+
* 检查会话存档SDK是否已过期
278+
*
279+
* @return true: 已过期, false: 未过期
280+
*/
281+
boolean isMsgAuditSdkExpired();
282+
283+
/**
284+
* 更新会话存档SDK
285+
*
286+
* @param sdk sdk id
287+
* @param expiresInSeconds 过期时间(秒)
288+
*/
289+
void updateMsgAuditSdk(long sdk, int expiresInSeconds);
290+
291+
/**
292+
* 使会话存档SDK过期
293+
*/
294+
void expireMsgAuditSdk();
266295
}

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
4949
private volatile String msgAuditSecret;
5050
private volatile String msgAuditPriKey;
5151
private volatile String msgAuditLibPath;
52+
/**
53+
* 会话存档SDK及其过期时间
54+
*/
55+
private volatile long msgAuditSdk;
56+
private volatile long msgAuditSdkExpiresTime;
5257
private volatile String oauth2redirectUri;
5358
private volatile String httpProxyHost;
5459
private volatile int httpProxyPort;
@@ -444,10 +449,34 @@ public String getMsgAuditSecret() {
444449

445450
/**
446451
* 设置会话存档secret
447-
* @param msgAuditSecret
452+
*
453+
* @param msgAuditSecret the msg audit secret
454+
* @return this
448455
*/
449456
public WxCpDefaultConfigImpl setMsgAuditSecret(String msgAuditSecret) {
450457
this.msgAuditSecret = msgAuditSecret;
451458
return this;
452459
}
460+
461+
@Override
462+
public long getMsgAuditSdk() {
463+
return this.msgAuditSdk;
464+
}
465+
466+
@Override
467+
public boolean isMsgAuditSdkExpired() {
468+
return System.currentTimeMillis() > this.msgAuditSdkExpiresTime;
469+
}
470+
471+
@Override
472+
public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
473+
this.msgAuditSdk = sdk;
474+
// 预留200秒的时间
475+
this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
476+
}
477+
478+
@Override
479+
public void expireMsgAuditSdk() {
480+
this.msgAuditSdkExpiresTime = 0;
481+
}
453482
}

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
5050
private volatile File tmpDirFile;
5151
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
5252
private volatile String webhookKey;
53+
/**
54+
* 会话存档SDK及其过期时间(SDK是本地JVM变量,不适合存储到Redis)
55+
*/
56+
private volatile long msgAuditSdk;
57+
private volatile long msgAuditSdkExpiresTime;
5358

5459
/**
5560
* Instantiates a new Wx cp redis config.
@@ -470,4 +475,26 @@ public String getWebhookKey() {
470475
public String getMsgAuditSecret() {
471476
return null;
472477
}
478+
479+
@Override
480+
public long getMsgAuditSdk() {
481+
return this.msgAuditSdk;
482+
}
483+
484+
@Override
485+
public boolean isMsgAuditSdkExpired() {
486+
return System.currentTimeMillis() > this.msgAuditSdkExpiresTime;
487+
}
488+
489+
@Override
490+
public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
491+
this.msgAuditSdk = sdk;
492+
// 预留200秒的时间
493+
this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
494+
}
495+
496+
@Override
497+
public void expireMsgAuditSdk() {
498+
this.msgAuditSdkExpiresTime = 0;
499+
}
473500
}

0 commit comments

Comments
 (0)