Skip to content

Commit 54be19d

Browse files
Copilotbinarywang
andcommitted
添加SDK引用计数机制和新的安全API方法
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
1 parent c849913 commit 54be19d

5 files changed

Lines changed: 307 additions & 0 deletions

File tree

‎weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java‎

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,26 @@ public interface WxCpMsgAuditService {
2828
* @param timeout 超时时间,根据实际需要填写
2929
* @return 返回是否调用成功 chat datas
3030
* @throws Exception the exception
31+
* @deprecated 请使用 {@link #getChatRecords(long, long, String, String, long)} 代替,
32+
* 该方法会将SDK暴露给调用方,容易导致SDK生命周期管理混乱,引发JVM崩溃
3133
*/
34+
@Deprecated
3235
WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception;
3336

37+
/**
38+
* 拉取聊天记录函数(推荐使用)
39+
* 该方法不会将SDK暴露给调用方,SDK生命周期由框架自动管理,更加安全
40+
*
41+
* @param seq 从指定的seq开始拉取消息,注意的是返回的消息从seq+1开始返回,seq为之前接口返回的最大seq值。首次使用请使用seq:0
42+
* @param limit 一次拉取的消息条数,最大值1000条,超过1000条会返回错误
43+
* @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null
44+
* @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null
45+
* @param timeout 超时时间,根据实际需要填写
46+
* @return 返回聊天记录列表,不包含SDK信息
47+
* @throws Exception the exception
48+
*/
49+
List<WxCpChatDatas.WxCpChatData> getChatRecords(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception;
50+
3451
/**
3552
* 获取解密的聊天数据Model
3653
*
@@ -39,10 +56,24 @@ public interface WxCpMsgAuditService {
3956
* @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ...
4057
* @return 解密后的聊天数据 decrypt data
4158
* @throws Exception the exception
59+
* @deprecated 请使用 {@link #getDecryptChatData(WxCpChatDatas.WxCpChatData, Integer)} 代替,
60+
* 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃
4261
*/
62+
@Deprecated
4363
WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData,
4464
@NonNull Integer pkcs1) throws Exception;
4565

66+
/**
67+
* 获取解密的聊天数据Model(推荐使用)
68+
* 该方法不需要传入SDK,SDK由框架自动管理,更加安全
69+
*
70+
* @param chatData 聊天数据
71+
* @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ...
72+
* @return 解密后的聊天数据
73+
* @throws Exception the exception
74+
*/
75+
WxCpChatModel getDecryptChatData(@NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception;
76+
4677
/**
4778
* 获取解密的聊天数据明文
4879
*
@@ -51,9 +82,23 @@ WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatD
5182
* @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ...
5283
* @return 解密后的明文 chat plain text
5384
* @throws Exception the exception
85+
* @deprecated 请使用 {@link #getChatRecordPlainText(WxCpChatDatas.WxCpChatData, Integer)} 代替,
86+
* 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃
5487
*/
88+
@Deprecated
5589
String getChatPlainText(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception;
5690

91+
/**
92+
* 获取解密的聊天数据明文(推荐使用)
93+
* 该方法不需要传入SDK,SDK由框架自动管理,更加安全
94+
*
95+
* @param chatData 聊天数据
96+
* @param pkcs1 使用什么方式进行解密,1代表使用PKCS1进行解密,2代表PKCS8进行解密 ...
97+
* @return 解密后的明文
98+
* @throws Exception the exception
99+
*/
100+
String getChatRecordPlainText(@NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception;
101+
57102
/**
58103
* 获取媒体文件
59104
* 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。
@@ -69,10 +114,32 @@ WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatD
69114
* @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000
70115
* @param targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif
71116
* @throws WxErrorException the wx error exception
117+
* @deprecated 请使用 {@link #downloadMediaFile(String, String, String, long, String)} 代替,
118+
* 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃
72119
*/
120+
@Deprecated
73121
void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
74122
@NonNull String targetFilePath) throws WxErrorException;
75123

124+
/**
125+
* 获取媒体文件(推荐使用)
126+
* 该方法不需要传入SDK,SDK由框架自动管理,更加安全
127+
* 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。
128+
* <p>
129+
* 注意:
130+
* 根据上面返回的文件类型,拼接好存放文件的绝对路径即可。此时绝对路径写入文件流,来达到获取媒体文件的目的。
131+
* 详情可以看官方文档,亦可阅读此接口源码。
132+
*
133+
* @param sdkfileid 消息体内容中的sdkfileid信息
134+
* @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null
135+
* @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null
136+
* @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000
137+
* @param targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif
138+
* @throws WxErrorException the wx error exception
139+
*/
140+
void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
141+
@NonNull String targetFilePath) throws WxErrorException;
142+
76143
/**
77144
* 获取媒体文件 传入一个lambda,each所有的数据分片byte[],更加灵活
78145
* 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。
@@ -85,10 +152,29 @@ void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, St
85152
* @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000
86153
* @param action 传入一个lambda,each所有的数据分片
87154
* @throws WxErrorException the wx error exception
155+
* @deprecated 请使用 {@link #downloadMediaFile(String, String, String, long, Consumer)} 代替,
156+
* 该方法需要传入SDK,容易导致SDK生命周期管理混乱,引发JVM崩溃
88157
*/
158+
@Deprecated
89159
void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
90160
@NonNull Consumer<byte[]> action) throws WxErrorException;
91161

162+
/**
163+
* 获取媒体文件 传入一个lambda,each所有的数据分片byte[],更加灵活(推荐使用)
164+
* 该方法不需要传入SDK,SDK由框架自动管理,更加安全
165+
* 针对图片、文件等媒体数据,提供sdk接口拉取数据内容。
166+
* 详情可以看官方文档,亦可阅读此接口源码。
167+
*
168+
* @param sdkfileid 消息体内容中的sdkfileid信息
169+
* @param proxy 使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081,如果没有传null
170+
* @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123,如果没有传null
171+
* @param timeout 超时时间,分片数据需累加到文件存储。单次最大返回512K字节,如果文件比较大,自行设置长一点,比如timeout=10000
172+
* @param action 传入一个lambda,each所有的数据分片
173+
* @throws WxErrorException the wx error exception
174+
*/
175+
void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
176+
@NonNull Consumer<byte[]> action) throws WxErrorException;
177+
92178
/**
93179
* 获取会话内容存档开启成员列表
94180
* 企业可通过此接口,获取企业开启会话内容存档的成员列表

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

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,139 @@ public WxCpAgreeInfo checkSingleAgree(@NonNull WxCpCheckAgreeRequest checkAgreeR
280280
return WxCpAgreeInfo.fromJson(responseContent);
281281
}
282282

283+
@Override
284+
public List<WxCpChatDatas.WxCpChatData> getChatRecords(long seq, @NonNull long limit, String proxy, String passwd,
285+
@NonNull long timeout) throws Exception {
286+
// 获取或初始化SDK
287+
long sdk = this.initSdk();
288+
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
289+
290+
// 增加引用计数
291+
configStorage.incrementMsgAuditSdkRefCount(sdk);
292+
293+
try {
294+
long slice = Finance.NewSlice();
295+
long ret = Finance.GetChatData(sdk, seq, limit, proxy, passwd, timeout, slice);
296+
if (ret != 0) {
297+
Finance.FreeSlice(slice);
298+
throw new WxErrorException("getchatdata err ret " + ret);
299+
}
300+
301+
// 拉取会话存档
302+
String content = Finance.GetContentFromSlice(slice);
303+
Finance.FreeSlice(slice);
304+
WxCpChatDatas chatDatas = WxCpChatDatas.fromJson(content);
305+
if (chatDatas.getErrCode().intValue() != 0) {
306+
throw new WxErrorException(chatDatas.toJson());
307+
}
308+
309+
return chatDatas.getChatData();
310+
} finally {
311+
// 减少引用计数
312+
configStorage.decrementMsgAuditSdkRefCount(sdk);
313+
}
314+
}
315+
316+
@Override
317+
public WxCpChatModel getDecryptChatData(@NonNull WxCpChatDatas.WxCpChatData chatData,
318+
@NonNull Integer pkcs1) throws Exception {
319+
// 获取或初始化SDK
320+
long sdk = this.initSdk();
321+
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
322+
323+
// 增加引用计数
324+
configStorage.incrementMsgAuditSdkRefCount(sdk);
325+
326+
try {
327+
String plainText = this.decryptChatData(sdk, chatData, pkcs1);
328+
return WxCpChatModel.fromJson(plainText);
329+
} finally {
330+
// 减少引用计数
331+
configStorage.decrementMsgAuditSdkRefCount(sdk);
332+
}
333+
}
334+
335+
@Override
336+
public String getChatRecordPlainText(@NonNull WxCpChatDatas.WxCpChatData chatData,
337+
@NonNull Integer pkcs1) throws Exception {
338+
// 获取或初始化SDK
339+
long sdk = this.initSdk();
340+
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
341+
342+
// 增加引用计数
343+
configStorage.incrementMsgAuditSdkRefCount(sdk);
344+
345+
try {
346+
return this.decryptChatData(sdk, chatData, pkcs1);
347+
} finally {
348+
// 减少引用计数
349+
configStorage.decrementMsgAuditSdkRefCount(sdk);
350+
}
351+
}
352+
353+
@Override
354+
public void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
355+
@NonNull String targetFilePath) throws WxErrorException {
356+
// 获取或初始化SDK
357+
long sdk;
358+
try {
359+
sdk = this.initSdk();
360+
} catch (WxErrorException e) {
361+
throw e;
362+
} catch (Exception e) {
363+
throw new WxErrorException(e);
364+
}
365+
366+
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
367+
368+
// 增加引用计数
369+
configStorage.incrementMsgAuditSdkRefCount(sdk);
370+
371+
try {
372+
File targetFile = new File(targetFilePath);
373+
if (!targetFile.getParentFile().exists()) {
374+
targetFile.getParentFile().mkdirs();
375+
}
376+
this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, i -> {
377+
try {
378+
// 大于512k的文件会分片拉取,此处需要使用追加写,避免后面的分片覆盖之前的数据。
379+
FileOutputStream outputStream = new FileOutputStream(targetFile, true);
380+
outputStream.write(i);
381+
outputStream.close();
382+
} catch (Exception e) {
383+
e.printStackTrace();
384+
}
385+
});
386+
} finally {
387+
// 减少引用计数
388+
configStorage.decrementMsgAuditSdkRefCount(sdk);
389+
}
390+
}
391+
392+
@Override
393+
public void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
394+
@NonNull Consumer<byte[]> action) throws WxErrorException {
395+
// 获取或初始化SDK
396+
long sdk;
397+
try {
398+
sdk = this.initSdk();
399+
} catch (WxErrorException e) {
400+
throw e;
401+
} catch (Exception e) {
402+
throw new WxErrorException(e);
403+
}
404+
405+
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
406+
407+
// 增加引用计数
408+
configStorage.incrementMsgAuditSdkRefCount(sdk);
409+
410+
try {
411+
this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, action);
412+
} finally {
413+
// 减少引用计数
414+
configStorage.decrementMsgAuditSdkRefCount(sdk);
415+
}
416+
}
417+
283418
}

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

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,4 +292,30 @@ public interface WxCpConfigStorage {
292292
* 使会话存档SDK过期
293293
*/
294294
void expireMsgAuditSdk();
295+
296+
/**
297+
* 增加会话存档SDK的引用计数
298+
* 用于支持多线程安全的SDK生命周期管理
299+
*
300+
* @param sdk sdk id
301+
* @return 增加后的引用计数
302+
*/
303+
int incrementMsgAuditSdkRefCount(long sdk);
304+
305+
/**
306+
* 减少会话存档SDK的引用计数
307+
* 当引用计数降为0时,自动销毁SDK
308+
*
309+
* @param sdk sdk id
310+
* @return 减少后的引用计数,如果返回0表示SDK已被销毁
311+
*/
312+
int decrementMsgAuditSdkRefCount(long sdk);
313+
314+
/**
315+
* 获取会话存档SDK的引用计数
316+
*
317+
* @param sdk sdk id
318+
* @return 当前引用计数
319+
*/
320+
int getMsgAuditSdkRefCount(long sdk);
295321
}

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
5454
*/
5555
private volatile long msgAuditSdk;
5656
private volatile long msgAuditSdkExpiresTime;
57+
/**
58+
* 会话存档SDK引用计数,用于多线程安全的生命周期管理
59+
*/
60+
private volatile int msgAuditSdkRefCount;
5761
private volatile String oauth2redirectUri;
5862
private volatile String httpProxyHost;
5963
private volatile int httpProxyPort;
@@ -473,10 +477,36 @@ public synchronized void updateMsgAuditSdk(long sdk, int expiresInSeconds) {
473477
this.msgAuditSdk = sdk;
474478
// 预留200秒的时间
475479
this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
480+
// 重置引用计数
481+
this.msgAuditSdkRefCount = 0;
476482
}
477483

478484
@Override
479485
public void expireMsgAuditSdk() {
480486
this.msgAuditSdkExpiresTime = 0;
481487
}
488+
489+
@Override
490+
public synchronized int incrementMsgAuditSdkRefCount(long sdk) {
491+
if (this.msgAuditSdk == sdk) {
492+
return ++this.msgAuditSdkRefCount;
493+
}
494+
return 0;
495+
}
496+
497+
@Override
498+
public synchronized int decrementMsgAuditSdkRefCount(long sdk) {
499+
if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount > 0) {
500+
return --this.msgAuditSdkRefCount;
501+
}
502+
return 0;
503+
}
504+
505+
@Override
506+
public synchronized int getMsgAuditSdkRefCount(long sdk) {
507+
if (this.msgAuditSdk == sdk) {
508+
return this.msgAuditSdkRefCount;
509+
}
510+
return 0;
511+
}
482512
}

0 commit comments

Comments
 (0)