1
0
mirror of synced 2026-02-11 14:17:48 +08:00

添加SDK引用计数机制和新的安全API方法

Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-01-14 03:55:07 +00:00
parent c849913f3b
commit 54be19d4e3
5 changed files with 307 additions and 0 deletions

View File

@@ -28,9 +28,26 @@ public interface WxCpMsgAuditService {
* @param timeout 超时时间,根据实际需要填写
* @return 返回是否调用成功 chat datas
* @throws Exception the exception
* @deprecated 请使用 {@link #getChatRecords(long, long, String, String, long)} 代替,
* 该方法会将SDK暴露给调用方容易导致SDK生命周期管理混乱引发JVM崩溃
*/
@Deprecated
WxCpChatDatas getChatDatas(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception;
/**
* 拉取聊天记录函数(推荐使用)
* 该方法不会将SDK暴露给调用方SDK生命周期由框架自动管理更加安全
*
* @param seq 从指定的seq开始拉取消息注意的是返回的消息从seq+1开始返回seq为之前接口返回的最大seq值。首次使用请使用seq:0
* @param limit 一次拉取的消息条数最大值1000条超过1000条会返回错误
* @param proxy 使用代理的请求需要传入代理的链接。如socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081如果没有传null
* @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123如果没有传null
* @param timeout 超时时间,根据实际需要填写
* @return 返回聊天记录列表不包含SDK信息
* @throws Exception the exception
*/
List<WxCpChatDatas.WxCpChatData> getChatRecords(long seq, @NonNull long limit, String proxy, String passwd, @NonNull long timeout) throws Exception;
/**
* 获取解密的聊天数据Model
*
@@ -39,10 +56,24 @@ public interface WxCpMsgAuditService {
* @param pkcs1 使用什么方式进行解密1代表使用PKCS1进行解密2代表PKCS8进行解密 ...
* @return 解密后的聊天数据 decrypt data
* @throws Exception the exception
* @deprecated 请使用 {@link #getDecryptChatData(WxCpChatDatas.WxCpChatData, Integer)} 代替,
* 该方法需要传入SDK容易导致SDK生命周期管理混乱引发JVM崩溃
*/
@Deprecated
WxCpChatModel getDecryptData(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData,
@NonNull Integer pkcs1) throws Exception;
/**
* 获取解密的聊天数据Model推荐使用
* 该方法不需要传入SDKSDK由框架自动管理更加安全
*
* @param chatData 聊天数据
* @param pkcs1 使用什么方式进行解密1代表使用PKCS1进行解密2代表PKCS8进行解密 ...
* @return 解密后的聊天数据
* @throws Exception the exception
*/
WxCpChatModel getDecryptChatData(@NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception;
/**
* 获取解密的聊天数据明文
*
@@ -51,9 +82,23 @@ public interface WxCpMsgAuditService {
* @param pkcs1 使用什么方式进行解密1代表使用PKCS1进行解密2代表PKCS8进行解密 ...
* @return 解密后的明文 chat plain text
* @throws Exception the exception
* @deprecated 请使用 {@link #getChatRecordPlainText(WxCpChatDatas.WxCpChatData, Integer)} 代替,
* 该方法需要传入SDK容易导致SDK生命周期管理混乱引发JVM崩溃
*/
@Deprecated
String getChatPlainText(@NonNull long sdk, @NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception;
/**
* 获取解密的聊天数据明文(推荐使用)
* 该方法不需要传入SDKSDK由框架自动管理更加安全
*
* @param chatData 聊天数据
* @param pkcs1 使用什么方式进行解密1代表使用PKCS1进行解密2代表PKCS8进行解密 ...
* @return 解密后的明文
* @throws Exception the exception
*/
String getChatRecordPlainText(@NonNull WxCpChatDatas.WxCpChatData chatData, @NonNull Integer pkcs1) throws Exception;
/**
* 获取媒体文件
* 针对图片、文件等媒体数据提供sdk接口拉取数据内容。
@@ -69,10 +114,32 @@ public interface WxCpMsgAuditService {
* @param timeout 超时时间分片数据需累加到文件存储。单次最大返回512K字节如果文件比较大自行设置长一点比如timeout=10000
* @param targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif
* @throws WxErrorException the wx error exception
* @deprecated 请使用 {@link #downloadMediaFile(String, String, String, long, String)} 代替,
* 该方法需要传入SDK容易导致SDK生命周期管理混乱引发JVM崩溃
*/
@Deprecated
void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
@NonNull String targetFilePath) throws WxErrorException;
/**
* 获取媒体文件(推荐使用)
* 该方法不需要传入SDKSDK由框架自动管理更加安全
* 针对图片、文件等媒体数据提供sdk接口拉取数据内容。
* <p>
* 注意:
* 根据上面返回的文件类型,拼接好存放文件的绝对路径即可。此时绝对路径写入文件流,来达到获取媒体文件的目的。
* 详情可以看官方文档,亦可阅读此接口源码。
*
* @param sdkfileid 消息体内容中的sdkfileid信息
* @param proxy 使用代理的请求需要传入代理的链接。如socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081如果没有传null
* @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123如果没有传null
* @param timeout 超时时间分片数据需累加到文件存储。单次最大返回512K字节如果文件比较大自行设置长一点比如timeout=10000
* @param targetFilePath 目标文件绝对路径+实际文件名,比如:/usr/local/file/20220114/474f866b39d10718810d55262af82662.gif
* @throws WxErrorException the wx error exception
*/
void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
@NonNull String targetFilePath) throws WxErrorException;
/**
* 获取媒体文件 传入一个lambdaeach所有的数据分片byte[],更加灵活
* 针对图片、文件等媒体数据提供sdk接口拉取数据内容。
@@ -85,10 +152,29 @@ public interface WxCpMsgAuditService {
* @param timeout 超时时间分片数据需累加到文件存储。单次最大返回512K字节如果文件比较大自行设置长一点比如timeout=10000
* @param action 传入一个lambdaeach所有的数据分片
* @throws WxErrorException the wx error exception
* @deprecated 请使用 {@link #downloadMediaFile(String, String, String, long, Consumer)} 代替,
* 该方法需要传入SDK容易导致SDK生命周期管理混乱引发JVM崩溃
*/
@Deprecated
void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
@NonNull Consumer<byte[]> action) throws WxErrorException;
/**
* 获取媒体文件 传入一个lambdaeach所有的数据分片byte[],更加灵活(推荐使用)
* 该方法不需要传入SDKSDK由框架自动管理更加安全
* 针对图片、文件等媒体数据提供sdk接口拉取数据内容。
* 详情可以看官方文档,亦可阅读此接口源码。
*
* @param sdkfileid 消息体内容中的sdkfileid信息
* @param proxy 使用代理的请求需要传入代理的链接。如socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081如果没有传null
* @param passwd 代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123如果没有传null
* @param timeout 超时时间分片数据需累加到文件存储。单次最大返回512K字节如果文件比较大自行设置长一点比如timeout=10000
* @param action 传入一个lambdaeach所有的数据分片
* @throws WxErrorException the wx error exception
*/
void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
@NonNull Consumer<byte[]> action) throws WxErrorException;
/**
* 获取会话内容存档开启成员列表
* 企业可通过此接口,获取企业开启会话内容存档的成员列表

View File

@@ -280,4 +280,139 @@ public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
return WxCpAgreeInfo.fromJson(responseContent);
}
@Override
public List<WxCpChatDatas.WxCpChatData> getChatRecords(long seq, @NonNull long limit, String proxy, String passwd,
@NonNull long timeout) throws Exception {
// 获取或初始化SDK
long sdk = this.initSdk();
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
// 增加引用计数
configStorage.incrementMsgAuditSdkRefCount(sdk);
try {
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());
}
return chatDatas.getChatData();
} finally {
// 减少引用计数
configStorage.decrementMsgAuditSdkRefCount(sdk);
}
}
@Override
public WxCpChatModel getDecryptChatData(@NonNull WxCpChatDatas.WxCpChatData chatData,
@NonNull Integer pkcs1) throws Exception {
// 获取或初始化SDK
long sdk = this.initSdk();
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
// 增加引用计数
configStorage.incrementMsgAuditSdkRefCount(sdk);
try {
String plainText = this.decryptChatData(sdk, chatData, pkcs1);
return WxCpChatModel.fromJson(plainText);
} finally {
// 减少引用计数
configStorage.decrementMsgAuditSdkRefCount(sdk);
}
}
@Override
public String getChatRecordPlainText(@NonNull WxCpChatDatas.WxCpChatData chatData,
@NonNull Integer pkcs1) throws Exception {
// 获取或初始化SDK
long sdk = this.initSdk();
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
// 增加引用计数
configStorage.incrementMsgAuditSdkRefCount(sdk);
try {
return this.decryptChatData(sdk, chatData, pkcs1);
} finally {
// 减少引用计数
configStorage.decrementMsgAuditSdkRefCount(sdk);
}
}
@Override
public void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
@NonNull String targetFilePath) throws WxErrorException {
// 获取或初始化SDK
long sdk;
try {
sdk = this.initSdk();
} catch (WxErrorException e) {
throw e;
} catch (Exception e) {
throw new WxErrorException(e);
}
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
// 增加引用计数
configStorage.incrementMsgAuditSdkRefCount(sdk);
try {
File targetFile = new File(targetFilePath);
if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
}
this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, i -> {
try {
// 大于512k的文件会分片拉取此处需要使用追加写避免后面的分片覆盖之前的数据。
FileOutputStream outputStream = new FileOutputStream(targetFile, true);
outputStream.write(i);
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
});
} finally {
// 减少引用计数
configStorage.decrementMsgAuditSdkRefCount(sdk);
}
}
@Override
public void downloadMediaFile(@NonNull String sdkfileid, String proxy, String passwd, @NonNull long timeout,
@NonNull Consumer<byte[]> action) throws WxErrorException {
// 获取或初始化SDK
long sdk;
try {
sdk = this.initSdk();
} catch (WxErrorException e) {
throw e;
} catch (Exception e) {
throw new WxErrorException(e);
}
WxCpConfigStorage configStorage = cpService.getWxCpConfigStorage();
// 增加引用计数
configStorage.incrementMsgAuditSdkRefCount(sdk);
try {
this.getMediaFile(sdk, sdkfileid, proxy, passwd, timeout, action);
} finally {
// 减少引用计数
configStorage.decrementMsgAuditSdkRefCount(sdk);
}
}
}

View File

@@ -292,4 +292,30 @@ public interface WxCpConfigStorage {
* 使会话存档SDK过期
*/
void expireMsgAuditSdk();
/**
* 增加会话存档SDK的引用计数
* 用于支持多线程安全的SDK生命周期管理
*
* @param sdk sdk id
* @return 增加后的引用计数
*/
int incrementMsgAuditSdkRefCount(long sdk);
/**
* 减少会话存档SDK的引用计数
* 当引用计数降为0时自动销毁SDK
*
* @param sdk sdk id
* @return 减少后的引用计数如果返回0表示SDK已被销毁
*/
int decrementMsgAuditSdkRefCount(long sdk);
/**
* 获取会话存档SDK的引用计数
*
* @param sdk sdk id
* @return 当前引用计数
*/
int getMsgAuditSdkRefCount(long sdk);
}

View File

@@ -54,6 +54,10 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
*/
private volatile long msgAuditSdk;
private volatile long msgAuditSdkExpiresTime;
/**
* 会话存档SDK引用计数用于多线程安全的生命周期管理
*/
private volatile int msgAuditSdkRefCount;
private volatile String oauth2redirectUri;
private volatile String httpProxyHost;
private volatile int httpProxyPort;
@@ -473,10 +477,36 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
this.msgAuditSdk = sdk;
// 预留200秒的时间
this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
// 重置引用计数
this.msgAuditSdkRefCount = 0;
}
@Override
public void expireMsgAuditSdk() {
this.msgAuditSdkExpiresTime = 0;
}
@Override
public synchronized int incrementMsgAuditSdkRefCount(long sdk) {
if (this.msgAuditSdk == sdk) {
return ++this.msgAuditSdkRefCount;
}
return 0;
}
@Override
public synchronized int decrementMsgAuditSdkRefCount(long sdk) {
if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount > 0) {
return --this.msgAuditSdkRefCount;
}
return 0;
}
@Override
public synchronized int getMsgAuditSdkRefCount(long sdk) {
if (this.msgAuditSdk == sdk) {
return this.msgAuditSdkRefCount;
}
return 0;
}
}

View File

@@ -55,6 +55,10 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
*/
private volatile long msgAuditSdk;
private volatile long msgAuditSdkExpiresTime;
/**
* 会话存档SDK引用计数用于多线程安全的生命周期管理
*/
private volatile int msgAuditSdkRefCount;
/**
* Instantiates a new Wx cp redis config.
@@ -491,10 +495,36 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
this.msgAuditSdk = sdk;
// 预留200秒的时间
this.msgAuditSdkExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
// 重置引用计数
this.msgAuditSdkRefCount = 0;
}
@Override
public void expireMsgAuditSdk() {
this.msgAuditSdkExpiresTime = 0;
}
@Override
public synchronized int incrementMsgAuditSdkRefCount(long sdk) {
if (this.msgAuditSdk == sdk) {
return ++this.msgAuditSdkRefCount;
}
return 0;
}
@Override
public synchronized int decrementMsgAuditSdkRefCount(long sdk) {
if (this.msgAuditSdk == sdk && this.msgAuditSdkRefCount > 0) {
return --this.msgAuditSdkRefCount;
}
return 0;
}
@Override
public synchronized int getMsgAuditSdkRefCount(long sdk) {
if (this.msgAuditSdk == sdk) {
return this.msgAuditSdkRefCount;
}
return 0;
}
}