From 9d81192c83359c6b9eb2a7b3f1bbb8b1813c2ab7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 02:54:51 +0000 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DRedis=E9=85=8D=E7=BD=AENPE?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E5=B9=B6=E6=94=B9=E8=BF=9B=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=A6=86=E7=9B=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com> --- .../cp/config/impl/WxCpRedisConfigImpl.java | 6 +- ...WxCpServiceGetMsgAuditAccessTokenTest.java | 193 +++++++++++------- 2 files changed, 126 insertions(+), 73 deletions(-) diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java index 2c9da893f..ef77d1d13 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java @@ -60,6 +60,10 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage { * 会话存档SDK引用计数,用于多线程安全的生命周期管理 */ private volatile int msgAuditSdkRefCount; + /** + * 会话存档access token锁(本地锁,不支持分布式) + */ + private final Lock msgAuditAccessTokenLock = new ReentrantLock(); /** * Instantiates a new Wx cp redis config. @@ -488,7 +492,7 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage { @Override public Lock getMsgAuditAccessTokenLock() { - return null; + return this.msgAuditAccessTokenLock; } @Override diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java index 9d2888a28..16c1e11f4 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java @@ -29,100 +29,80 @@ public class WxCpServiceGetMsgAuditAccessTokenTest { } /** - * 测试 WxCpServiceApacheHttpClientImpl 的 getMsgAuditAccessToken 方法 + * 测试会话存档access token的缓存机制 + * 验证当token未过期时,直接从配置中返回缓存的token */ @Test - public void testGetMsgAuditAccessToken_ApacheHttpClient() throws WxErrorException { - // 创建一个模拟实现,不实际调用HTTP请求 - WxCpServiceApacheHttpClientImpl service = new WxCpServiceApacheHttpClientImpl() { - @Override - public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { - // 验证配置是否正确使用 - WxCpConfigStorage storage = getWxCpConfigStorage(); - assertThat(storage.getMsgAuditSecret()).isEqualTo("testMsgAuditSecret"); - - // 模拟返回 token - return "mock_msg_audit_access_token"; - } - }; - service.setWxCpConfigStorage(config); - + public void testGetMsgAuditAccessToken_Cache() throws WxErrorException { + // 预先设置一个有效的token + config.updateMsgAuditAccessToken("cached_token", 7200); + + BaseWxCpServiceImpl service = createTestService(config); + + // 不强制刷新时应该返回缓存的token String token = service.getMsgAuditAccessToken(false); - assertThat(token).isEqualTo("mock_msg_audit_access_token"); + assertThat(token).isEqualTo("cached_token"); } /** - * 测试 WxCpServiceHttpComponentsImpl 的 getMsgAuditAccessToken 方法 + * 测试强制刷新会话存档access token + * 验证forceRefresh=true时会重新获取token */ @Test - public void testGetMsgAuditAccessToken_HttpComponents() throws WxErrorException { - // 创建一个模拟实现,不实际调用HTTP请求 - WxCpServiceHttpComponentsImpl service = new WxCpServiceHttpComponentsImpl() { - @Override - public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { - // 验证配置是否正确使用 - WxCpConfigStorage storage = getWxCpConfigStorage(); - assertThat(storage.getMsgAuditSecret()).isEqualTo("testMsgAuditSecret"); - - // 模拟返回 token - return "mock_msg_audit_access_token"; - } - }; - service.setWxCpConfigStorage(config); - - String token = service.getMsgAuditAccessToken(false); - assertThat(token).isEqualTo("mock_msg_audit_access_token"); + public void testGetMsgAuditAccessToken_ForceRefresh() throws WxErrorException { + // 预先设置一个有效的token + config.updateMsgAuditAccessToken("old_token", 7200); + + BaseWxCpServiceImpl service = createTestServiceWithMockToken(config, "new_token"); + + // 强制刷新应该获取新token + String token = service.getMsgAuditAccessToken(true); + assertThat(token).isEqualTo("new_token"); } /** - * 测试 WxCpServiceOkHttpImpl 的 getMsgAuditAccessToken 方法 + * 测试token过期时自动刷新 + * 验证当token已过期时,会自动重新获取 */ @Test - public void testGetMsgAuditAccessToken_OkHttp() throws WxErrorException { - // 创建一个模拟实现,不实际调用HTTP请求 - WxCpServiceOkHttpImpl service = new WxCpServiceOkHttpImpl() { - @Override - public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { - // 验证配置是否正确使用 - WxCpConfigStorage storage = getWxCpConfigStorage(); - assertThat(storage.getMsgAuditSecret()).isEqualTo("testMsgAuditSecret"); - - // 模拟返回 token - return "mock_msg_audit_access_token"; - } - }; - service.setWxCpConfigStorage(config); - + public void testGetMsgAuditAccessToken_Expired() throws WxErrorException { + // 设置一个已过期的token(过期时间为0) + config.updateMsgAuditAccessToken("expired_token", 0); + // 等待一下确保过期 + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + BaseWxCpServiceImpl service = createTestServiceWithMockToken(config, "refreshed_token"); + + // 过期的token应该被自动刷新 String token = service.getMsgAuditAccessToken(false); - assertThat(token).isEqualTo("mock_msg_audit_access_token"); + assertThat(token).isEqualTo("refreshed_token"); } /** - * 测试 WxCpServiceJoddHttpImpl 的 getMsgAuditAccessToken 方法 + * 测试获取锁机制 + * 验证配置中的锁可以正常获取和使用 */ @Test - public void testGetMsgAuditAccessToken_JoddHttp() throws WxErrorException { - // 创建一个模拟实现,不实际调用HTTP请求 - WxCpServiceJoddHttpImpl service = new WxCpServiceJoddHttpImpl() { - @Override - public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { - // 验证配置是否正确使用 - WxCpConfigStorage storage = getWxCpConfigStorage(); - assertThat(storage.getMsgAuditSecret()).isEqualTo("testMsgAuditSecret"); - - // 模拟返回 token - return "mock_msg_audit_access_token"; - } - }; - service.setWxCpConfigStorage(config); - - String token = service.getMsgAuditAccessToken(false); - assertThat(token).isEqualTo("mock_msg_audit_access_token"); + public void testGetMsgAuditAccessToken_Lock() { + // 验证配置提供的锁不为null + assertThat(config.getMsgAuditAccessTokenLock()).isNotNull(); + + // 验证锁可以正常使用 + config.getMsgAuditAccessTokenLock().lock(); + try { + assertThat(config.getMsgAuditAccessToken()).isNull(); + } finally { + config.getMsgAuditAccessTokenLock().unlock(); + } } /** * 创建一个用于测试的BaseWxCpServiceImpl实现, - * 模拟在msgAuditSecret未配置时抛出异常的行为 + * 用于测试缓存和过期逻辑 */ private BaseWxCpServiceImpl createTestService(WxCpConfigStorage config) { return new BaseWxCpServiceImpl() { @@ -148,12 +128,81 @@ public class WxCpServiceGetMsgAuditAccessTokenTest { @Override public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { + // 检查是否需要刷新 + if (!getWxCpConfigStorage().isMsgAuditAccessTokenExpired() && !forceRefresh) { + return getWxCpConfigStorage().getMsgAuditAccessToken(); + } + // 使用会话存档secret获取access_token String msgAuditSecret = getWxCpConfigStorage().getMsgAuditSecret(); if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) { throw new WxErrorException("会话存档secret未配置"); } - return "mock_token"; + + // 模拟HTTP请求失败,实际测试中应该返回缓存的token + return getWxCpConfigStorage().getMsgAuditAccessToken(); + } + + @Override + public void initHttp() { + } + + @Override + public WxCpConfigStorage getWxCpConfigStorage() { + return config; + } + }; + } + + /** + * 创建一个用于测试的BaseWxCpServiceImpl实现, + * 模拟返回指定的token(用于测试刷新逻辑) + */ + private BaseWxCpServiceImpl createTestServiceWithMockToken(WxCpConfigStorage config, String mockToken) { + return new BaseWxCpServiceImpl() { + @Override + public Object getRequestHttpClient() { + return null; + } + + @Override + public Object getRequestHttpProxy() { + return null; + } + + @Override + public HttpClientType getRequestType() { + return null; + } + + @Override + public String getAccessToken(boolean forceRefresh) throws WxErrorException { + return "test_access_token"; + } + + @Override + public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException { + // 使用锁机制 + var lock = getWxCpConfigStorage().getMsgAuditAccessTokenLock(); + lock.lock(); + try { + // 检查是否需要刷新 + if (!getWxCpConfigStorage().isMsgAuditAccessTokenExpired() && !forceRefresh) { + return getWxCpConfigStorage().getMsgAuditAccessToken(); + } + + // 使用会话存档secret获取access_token + String msgAuditSecret = getWxCpConfigStorage().getMsgAuditSecret(); + if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) { + throw new WxErrorException("会话存档secret未配置"); + } + + // 模拟获取新token并更新配置 + getWxCpConfigStorage().updateMsgAuditAccessToken(mockToken, 7200); + return mockToken; + } finally { + lock.unlock(); + } } @Override