Skip to content

【企业微信】重构会话存档SDK生命周期为ThreadLocal模式#3935

Open
OldQi wants to merge 4 commits intobinarywang:developfrom
OldQi:develop
Open

【企业微信】重构会话存档SDK生命周期为ThreadLocal模式#3935
OldQi wants to merge 4 commits intobinarywang:developfrom
OldQi:develop

Conversation

@OldQi
Copy link

@OldQi OldQi commented Mar 18, 2026

  • 移除7200秒过期和引用计数机制,解决频繁初始化及线程安全问题
  • 每线程持有独立SDK实例,实现懒初始化和线程内复用
  • 新增closeThreadLocalSdk()和closeAllSdks()接口,用于显式释放资源
  • 废弃WxCpConfigStorage中旧的SDK管理方法及相关字段,保留兼容实现
  • 更新WxCpMsgAuditServiceImpl,实现ThreadLocal管理SDK实例
  • 修改API调用逻辑,移除获取和释放SDK的旧流程,简化调用流程
  • 补充单元测试覆盖多线程及线程池场景验证安全性
  • 更新相关文档,增加使用示例与注意事项指导正确使用方法

- 移除7200秒过期和引用计数机制,解决频繁初始化及线程安全问题
- 每线程持有独立SDK实例,实现懒初始化和线程内复用
- 新增closeThreadLocalSdk()和closeAllSdks()接口,用于显式释放资源
- 废弃WxCpConfigStorage中旧的SDK管理方法及相关字段,保留兼容实现
- 更新WxCpMsgAuditServiceImpl,实现ThreadLocal管理SDK实例
- 修改API调用逻辑,移除获取和释放SDK的旧流程,简化调用流程
- 补充单元测试覆盖多线程及线程池场景验证安全性
- 更新相关文档,增加使用示例与注意事项指导正确使用方法
@OldQi OldQi changed the title refactor(msg-audit): 重构会话存档SDK生命周期为ThreadLocal模式 【企业微信】重构会话存档SDK生命周期为ThreadLocal模式 Mar 18, 2026
OldQi added 3 commits March 18, 2026 11:32
- 将获取或初始化SDK的方法替换为直接创建SDK
- 简化了SDK的调用流程
- 提高代码清晰度和维护性
- 移除 managedSdks 集合及相关代码,不再统一追踪所有SDK实例
- 采用ThreadLocal懒初始化,线程内部复用SDK实例
- closeAllSdks() 方法改为调用 closeThreadLocalSdk(),只清理当前线程资源
- 保留旧API方法,内部调用统一替换为 getOrInitThreadLocalSdk()
- 文档更新,明确多线程和多实例场景下的SDK管理及DestroySdk调用规范
- 删除手动调用 Finance.DestroySdk() 的旧测试代码示例,防止误用造成内存泄漏
@binarywang
Copy link
Owner

auggie review

@augmentcode
Copy link

augmentcode bot commented Mar 18, 2026

🤖 Augment PR Summary

Summary: This PR refactors the WeCom (企业微信) message-audit SDK lifecycle from a shared/expiry/ref-count model to a per-thread ThreadLocal model to improve safety and reduce repeated init.

Changes:

  • Introduce per-thread SDK instance management in WxCpMsgAuditServiceImpl with lazy init and thread-local reuse.
  • Remove the 7200s “expires” rule and the reference-count acquire/release flow from the runtime path.
  • Add explicit resource release APIs to WxCpMsgAuditService: closeThreadLocalSdk() and closeAllSdks().
  • Deprecate legacy SDK lifecycle/cache/ref-count methods in WxCpConfigStorage and mark legacy fields/methods as deprecated in WxCpDefaultConfigImpl while keeping compatibility.
  • Simplify safe APIs (getChatRecords/getDecryptChatData/downloadMediaFile) to use thread-local SDK without per-call release.
  • Update unit tests to cover the new safe APIs and demonstrate finally-block cleanup for thread-pool reuse scenarios.
  • Add design documentation describing the ThreadLocal lifecycle approach and recommended usage patterns.

Technical Notes: Uses a concurrent set to track created SDK ids for bulk cleanup; callers in thread pools are expected to call closeThreadLocalSdk() in finally to avoid long-lived native resources.

🤖 Was this summary useful? React with 👍 or 👎

Copy link

@augmentcode augmentcode bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 4 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

@NonNull long timeout) throws Exception {
// 获取或初始化SDK
long sdk = this.initSdk();
long sdk = this.createSdk();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getChatDatas() 这里直接调用 createSdk() 会导致每次调用都新建 SDK(不走 ThreadLocal 复用),同时也不会进入 managedSdks,因此 closeAllSdks() 无法统一释放这些实例。若调用方没有显式 Finance.DestroySdk(chatDatas.getSdk()),可能造成原生资源泄漏。

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

}
@Override
public void closeAllSdks() {
managedSdks.forEach(sdk -> {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closeAllSdks()closeThreadLocalSdk() 之间没有任何并发协调;如果应用关闭时仍有任务线程在 finally 里调用 closeThreadLocalSdk(),同一个 sdk 可能被 Finance.DestroySdk() 调用两次(native double-free 风险)。

Severity: high

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

log.info("关闭会话存档SDK,sdk={}", sdk);
});
managedSdks.clear();
threadLocalSdk.remove();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

closeAllSdks() 只对当前线程执行 threadLocalSdk.remove();其他线程的 ThreadLocal 仍可能保留已被 Destroy 的 sdk id。若关闭阶段之后仍有调用发生,getOrInitThreadLocalSdk() 可能返回已销毁的 sdk,存在 use-after-destroy 风险。

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

if (!targetFile.getParentFile().exists()) {
targetFile.getParentFile().mkdirs();
File targetFile = new File(targetFilePath);
if (!targetFile.getParentFile().exists()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里 targetFile.getParentFile() 可能为 null(例如 targetFilePath 没有父目录段时),会在 exists() 处触发 NPE,导致下载直接失败。建议对 parent 为空的情况做兼容处理。

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

本次 PR 针对企业微信“会话存档”能力重构原生 Finance SDK 的生命周期管理方式,目标是减少频繁初始化/销毁并降低多线程共享句柄带来的线程安全风险,通过 ThreadLocal 让每个线程持有独立 SDK 实例,并提供显式资源释放接口。

Changes:

  • WxCpMsgAuditServiceImpl 中引入 ThreadLocal<Long>managedSdks,新增 closeThreadLocalSdk() / closeAllSdks(),并将推荐 API(不暴露 sdk)切换为线程内复用模式。
  • WxCpConfigStorageWxCpDefaultConfigImpl 中旧的“SDK缓存/过期/引用计数”相关字段与方法标记为 @Deprecated 以保持兼容。
  • 调整测试用例示例,强调线程池场景需在 finally 中调用 closeThreadLocalSdk();新增一份设计说明文档。

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java 用 ThreadLocal 管理会话存档 SDK,新增显式关闭接口并简化推荐 API 调用链
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMsgAuditService.java 对外暴露 closeThreadLocalSdk()/closeAllSdks() 生命周期接口
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java 废弃旧的 SDK 缓存/过期/引用计数 API(保留以兼容)
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java 废弃旧字段与实现方法,继续提供兼容行为
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java 更新示例测试:在 finally 调用 closeThreadLocalSdk()
docs/giggly-pondering-turtle.md 新增 ThreadLocal 生命周期重构方案说明文档

Comment on lines +374 to +376
FileOutputStream outputStream = new FileOutputStream(targetFile, true);
outputStream.write(i);
outputStream.close();
Comment on lines 303 to +307
* 获取会话存档SDK
* 会话存档SDK初始化后有效期为7200秒,无需每次重新初始化
*
* @return sdk id,如果未初始化或已过期返回0
* @deprecated SDK 生命周期已改由 {@link me.chanjar.weixin.cp.api.WxCpMsgAuditService} 内部的 ThreadLocal
Comment on lines +1 to +4
# 会话存档SDK生命周期重构方案

## Context

Comment on lines +162 to 166
log.info("关闭会话存档SDK,sdk={}", sdk);
});
managedSdks.clear();
threadLocalSdk.remove();
}
private long getOrInitThreadLocalSdk() throws WxErrorException {
Long sdk = threadLocalSdk.get();
if (sdk != null && sdk > 0) {
return sdk;
Comment on lines +159 to +163
public void closeAllSdks() {
managedSdks.forEach(sdk -> {
Finance.DestroySdk(sdk);
log.info("关闭会话存档SDK,sdk={}", sdk);
});
Comment on lines +93 to +94
* 创建并初始化一个新 SDK 实例(私有,只在当前线程无 SDK 时调用)。
* Finance.loadingLibraries() 底层依赖 System.load(),JVM 保证同一库不重复加载,多线程并发调用安全。
Comment on lines +42 to +46
| `weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java` | 废弃旧SDK管理方法 |
| `weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java` | 废弃旧字段/方法 |
| `weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMsgAuditTest.java` | 补充测试 |
| `docs/CP_MSG_AUDIT_SDK_SAFE_USAGE.md` | 更新文档 |

Comment on lines 757 to 761
/**
* 测试新的安全API方法(推荐使用)
* 这些方法不需要手动管理SDK生命周期,更加安全
* 这些方法不需要手动管理SDK生命周期,SDK由框架 ThreadLocal 模式统一管理。
* 线程池场景下,在 finally 块中调用 closeThreadLocalSdk() 防止SDK随线程复用泄漏。
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants