Skip to content

Commit 9d81192

Browse files
Copilotbinarywang
andcommitted
修复Redis配置NPE问题并改进测试覆盖
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
1 parent d49e21c commit 9d81192

File tree

2 files changed

+126
-73
lines changed

2 files changed

+126
-73
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
6060
* 会话存档SDK引用计数,用于多线程安全的生命周期管理
6161
*/
6262
private volatile int msgAuditSdkRefCount;
63+
/**
64+
* 会话存档access token锁(本地锁,不支持分布式)
65+
*/
66+
private final Lock msgAuditAccessTokenLock = new ReentrantLock();
6367

6468
/**
6569
* Instantiates a new Wx cp redis config.
@@ -488,7 +492,7 @@ public String getMsgAuditAccessToken() {
488492

489493
@Override
490494
public Lock getMsgAuditAccessTokenLock() {
491-
return null;
495+
return this.msgAuditAccessTokenLock;
492496
}
493497

494498
@Override

weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpServiceGetMsgAuditAccessTokenTest.java

Lines changed: 121 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -29,100 +29,80 @@ public void setUp() {
2929
}
3030

3131
/**
32-
* 测试 WxCpServiceApacheHttpClientImpl 的 getMsgAuditAccessToken 方法
32+
* 测试会话存档access token的缓存机制
33+
* 验证当token未过期时,直接从配置中返回缓存的token
3334
*/
3435
@Test
35-
public void testGetMsgAuditAccessToken_ApacheHttpClient() throws WxErrorException {
36-
// 创建一个模拟实现,不实际调用HTTP请求
37-
WxCpServiceApacheHttpClientImpl service = new WxCpServiceApacheHttpClientImpl() {
38-
@Override
39-
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
40-
// 验证配置是否正确使用
41-
WxCpConfigStorage storage = getWxCpConfigStorage();
42-
assertThat(storage.getMsgAuditSecret()).isEqualTo("testMsgAuditSecret");
43-
44-
// 模拟返回 token
45-
return "mock_msg_audit_access_token";
46-
}
47-
};
48-
service.setWxCpConfigStorage(config);
49-
36+
public void testGetMsgAuditAccessToken_Cache() throws WxErrorException {
37+
// 预先设置一个有效的token
38+
config.updateMsgAuditAccessToken("cached_token", 7200);
39+
40+
BaseWxCpServiceImpl service = createTestService(config);
41+
42+
// 不强制刷新时应该返回缓存的token
5043
String token = service.getMsgAuditAccessToken(false);
51-
assertThat(token).isEqualTo("mock_msg_audit_access_token");
44+
assertThat(token).isEqualTo("cached_token");
5245
}
5346

5447
/**
55-
* 测试 WxCpServiceHttpComponentsImpl 的 getMsgAuditAccessToken 方法
48+
* 测试强制刷新会话存档access token
49+
* 验证forceRefresh=true时会重新获取token
5650
*/
5751
@Test
58-
public void testGetMsgAuditAccessToken_HttpComponents() throws WxErrorException {
59-
// 创建一个模拟实现,不实际调用HTTP请求
60-
WxCpServiceHttpComponentsImpl service = new WxCpServiceHttpComponentsImpl() {
61-
@Override
62-
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
63-
// 验证配置是否正确使用
64-
WxCpConfigStorage storage = getWxCpConfigStorage();
65-
assertThat(storage.getMsgAuditSecret()).isEqualTo("testMsgAuditSecret");
66-
67-
// 模拟返回 token
68-
return "mock_msg_audit_access_token";
69-
}
70-
};
71-
service.setWxCpConfigStorage(config);
72-
73-
String token = service.getMsgAuditAccessToken(false);
74-
assertThat(token).isEqualTo("mock_msg_audit_access_token");
52+
public void testGetMsgAuditAccessToken_ForceRefresh() throws WxErrorException {
53+
// 预先设置一个有效的token
54+
config.updateMsgAuditAccessToken("old_token", 7200);
55+
56+
BaseWxCpServiceImpl service = createTestServiceWithMockToken(config, "new_token");
57+
58+
// 强制刷新应该获取新token
59+
String token = service.getMsgAuditAccessToken(true);
60+
assertThat(token).isEqualTo("new_token");
7561
}
7662

7763
/**
78-
* 测试 WxCpServiceOkHttpImpl 的 getMsgAuditAccessToken 方法
64+
* 测试token过期时自动刷新
65+
* 验证当token已过期时,会自动重新获取
7966
*/
8067
@Test
81-
public void testGetMsgAuditAccessToken_OkHttp() throws WxErrorException {
82-
// 创建一个模拟实现,不实际调用HTTP请求
83-
WxCpServiceOkHttpImpl service = new WxCpServiceOkHttpImpl() {
84-
@Override
85-
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
86-
// 验证配置是否正确使用
87-
WxCpConfigStorage storage = getWxCpConfigStorage();
88-
assertThat(storage.getMsgAuditSecret()).isEqualTo("testMsgAuditSecret");
89-
90-
// 模拟返回 token
91-
return "mock_msg_audit_access_token";
92-
}
93-
};
94-
service.setWxCpConfigStorage(config);
95-
68+
public void testGetMsgAuditAccessToken_Expired() throws WxErrorException {
69+
// 设置一个已过期的token(过期时间为0)
70+
config.updateMsgAuditAccessToken("expired_token", 0);
71+
// 等待一下确保过期
72+
try {
73+
Thread.sleep(100);
74+
} catch (InterruptedException e) {
75+
Thread.currentThread().interrupt();
76+
}
77+
78+
BaseWxCpServiceImpl service = createTestServiceWithMockToken(config, "refreshed_token");
79+
80+
// 过期的token应该被自动刷新
9681
String token = service.getMsgAuditAccessToken(false);
97-
assertThat(token).isEqualTo("mock_msg_audit_access_token");
82+
assertThat(token).isEqualTo("refreshed_token");
9883
}
9984

10085
/**
101-
* 测试 WxCpServiceJoddHttpImpl 的 getMsgAuditAccessToken 方法
86+
* 测试获取锁机制
87+
* 验证配置中的锁可以正常获取和使用
10288
*/
10389
@Test
104-
public void testGetMsgAuditAccessToken_JoddHttp() throws WxErrorException {
105-
// 创建一个模拟实现,不实际调用HTTP请求
106-
WxCpServiceJoddHttpImpl service = new WxCpServiceJoddHttpImpl() {
107-
@Override
108-
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
109-
// 验证配置是否正确使用
110-
WxCpConfigStorage storage = getWxCpConfigStorage();
111-
assertThat(storage.getMsgAuditSecret()).isEqualTo("testMsgAuditSecret");
112-
113-
// 模拟返回 token
114-
return "mock_msg_audit_access_token";
115-
}
116-
};
117-
service.setWxCpConfigStorage(config);
118-
119-
String token = service.getMsgAuditAccessToken(false);
120-
assertThat(token).isEqualTo("mock_msg_audit_access_token");
90+
public void testGetMsgAuditAccessToken_Lock() {
91+
// 验证配置提供的锁不为null
92+
assertThat(config.getMsgAuditAccessTokenLock()).isNotNull();
93+
94+
// 验证锁可以正常使用
95+
config.getMsgAuditAccessTokenLock().lock();
96+
try {
97+
assertThat(config.getMsgAuditAccessToken()).isNull();
98+
} finally {
99+
config.getMsgAuditAccessTokenLock().unlock();
100+
}
121101
}
122102

123103
/**
124104
* 创建一个用于测试的BaseWxCpServiceImpl实现,
125-
* 模拟在msgAuditSecret未配置时抛出异常的行为
105+
* 用于测试缓存和过期逻辑
126106
*/
127107
private BaseWxCpServiceImpl createTestService(WxCpConfigStorage config) {
128108
return new BaseWxCpServiceImpl() {
@@ -148,12 +128,81 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
148128

149129
@Override
150130
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
131+
// 检查是否需要刷新
132+
if (!getWxCpConfigStorage().isMsgAuditAccessTokenExpired() && !forceRefresh) {
133+
return getWxCpConfigStorage().getMsgAuditAccessToken();
134+
}
135+
151136
// 使用会话存档secret获取access_token
152137
String msgAuditSecret = getWxCpConfigStorage().getMsgAuditSecret();
153138
if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) {
154139
throw new WxErrorException("会话存档secret未配置");
155140
}
156-
return "mock_token";
141+
142+
// 模拟HTTP请求失败,实际测试中应该返回缓存的token
143+
return getWxCpConfigStorage().getMsgAuditAccessToken();
144+
}
145+
146+
@Override
147+
public void initHttp() {
148+
}
149+
150+
@Override
151+
public WxCpConfigStorage getWxCpConfigStorage() {
152+
return config;
153+
}
154+
};
155+
}
156+
157+
/**
158+
* 创建一个用于测试的BaseWxCpServiceImpl实现,
159+
* 模拟返回指定的token(用于测试刷新逻辑)
160+
*/
161+
private BaseWxCpServiceImpl createTestServiceWithMockToken(WxCpConfigStorage config, String mockToken) {
162+
return new BaseWxCpServiceImpl() {
163+
@Override
164+
public Object getRequestHttpClient() {
165+
return null;
166+
}
167+
168+
@Override
169+
public Object getRequestHttpProxy() {
170+
return null;
171+
}
172+
173+
@Override
174+
public HttpClientType getRequestType() {
175+
return null;
176+
}
177+
178+
@Override
179+
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
180+
return "test_access_token";
181+
}
182+
183+
@Override
184+
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
185+
// 使用锁机制
186+
var lock = getWxCpConfigStorage().getMsgAuditAccessTokenLock();
187+
lock.lock();
188+
try {
189+
// 检查是否需要刷新
190+
if (!getWxCpConfigStorage().isMsgAuditAccessTokenExpired() && !forceRefresh) {
191+
return getWxCpConfigStorage().getMsgAuditAccessToken();
192+
}
193+
194+
// 使用会话存档secret获取access_token
195+
String msgAuditSecret = getWxCpConfigStorage().getMsgAuditSecret();
196+
if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) {
197+
throw new WxErrorException("会话存档secret未配置");
198+
}
199+
200+
// 模拟获取新token并更新配置
201+
getWxCpConfigStorage().updateMsgAuditAccessToken(mockToken, 7200);
202+
return mockToken;
203+
} finally {
204+
lock.unlock();
205+
}
157206
}
158207

159208
@Override

0 commit comments

Comments
 (0)