1
0
mirror of synced 2026-03-23 21:18:58 +08:00

🎨 #3873 【企业微信】修复会话存档API使用错误secret导致权限异常的问题

This commit is contained in:
Copilot
2026-03-03 16:02:48 +08:00
committed by GitHub
parent 2a9cff0180
commit e93380cb1e
13 changed files with 627 additions and 3 deletions

View File

@@ -57,6 +57,19 @@ public interface WxCpService extends WxService {
*/
String getAccessToken(boolean forceRefresh) throws WxErrorException;
/**
* <pre>
* 获取会话存档access_token本方法线程安全
* 会话存档相关接口需要使用会话存档secret获取单独的access_token
* 详情请见: https://developer.work.weixin.qq.com/document/path/91782
* </pre>
*
* @param forceRefresh 强制刷新
* @return 会话存档专用的access token
* @throws WxErrorException the wx error exception
*/
String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException;
/**
* 获得jsapi_ticket,不强制刷新jsapi_ticket
*
@@ -194,6 +207,19 @@ public interface WxCpService extends WxService {
*/
String postWithoutToken(String url, String postData) throws WxErrorException;
/**
* <pre>
* 使用会话存档access token发起post请求
* 会话存档相关API需要使用会话存档专用的secret获取独立的access token
* </pre>
*
* @param url 接口地址
* @param postData 请求body字符串
* @return the string
* @throws WxErrorException the wx error exception
*/
String postForMsgAudit(String url, String postData) throws WxErrorException;
/**
* <pre>
* Service没有实现某个API的时候可以用这个

View File

@@ -302,6 +302,16 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
return this.executeNormal(SimplePostRequestExecutor.create(this), url, postData);
}
@Override
public String postForMsgAudit(String url, String postData) throws WxErrorException {
// 获取会话存档专用的access token
String msgAuditAccessToken = getMsgAuditAccessToken(false);
// 拼接access_token参数
String urlWithToken = url + (url.contains("?") ? "&" : "?") + "access_token=" + msgAuditAccessToken;
// 使用executeNormal方法不自动添加token
return this.executeNormal(SimplePostRequestExecutor.create(this), urlWithToken, postData);
}
/**
* 向微信端发送请求在这里执行的策略是当发生access_token过期时才去刷新然后重新执行请求而不是全局定时请求.
*/

View File

@@ -302,7 +302,7 @@ public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
if (type != null) {
jsonObject.addProperty("type", type);
}
String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
String responseContent = this.cpService.postForMsgAudit(apiUrl, jsonObject.toString());
return WxCpGsonBuilder.create().fromJson(GsonParser.parse(responseContent).getAsJsonArray("ids"),
new TypeToken<List<String>>() {
}.getType());
@@ -313,14 +313,14 @@ public class WxCpMsgAuditServiceImpl implements WxCpMsgAuditService {
final String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_GROUP_CHAT);
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("roomid", roomid);
String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
String responseContent = this.cpService.postForMsgAudit(apiUrl, jsonObject.toString());
return WxCpGroupChat.fromJson(responseContent);
}
@Override
public WxCpAgreeInfo checkSingleAgree(@NonNull WxCpCheckAgreeRequest checkAgreeRequest) throws WxErrorException {
String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(CHECK_SINGLE_AGREE);
String responseContent = this.cpService.post(apiUrl, checkAgreeRequest.toJson());
String responseContent = this.cpService.postForMsgAudit(apiUrl, checkAgreeRequest.toJson());
return WxCpAgreeInfo.fromJson(responseContent);
}

View File

@@ -17,6 +17,7 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
import java.util.concurrent.locks.Lock;
/**
* The type Wx cp service apache http client.
@@ -74,6 +75,51 @@ public class WxCpServiceApacheHttpClientImpl extends BaseWxCpServiceImpl<Closeab
return this.configStorage.getAccessToken();
}
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getMsgAuditAccessToken();
}
Lock lock = this.configStorage.getMsgAuditAccessTokenLock();
lock.lock();
try {
// 拿到锁之后再次判断一下最新的token是否过期避免重刷
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getMsgAuditAccessToken();
}
// 使用会话存档secret获取access_token
String msgAuditSecret = this.configStorage.getMsgAuditSecret();
if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) {
throw new WxErrorException("会话存档secret未配置");
}
String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),
this.configStorage.getCorpId(), msgAuditSecret);
try {
HttpGet httpGet = new HttpGet(url);
if (this.httpProxy != null) {
RequestConfig config = RequestConfig.custom()
.setProxy(this.httpProxy).build();
httpGet.setConfig(config);
}
String resultContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);
WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateMsgAuditAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
} catch (IOException e) {
throw new WxRuntimeException(e);
}
} finally {
lock.unlock();
}
return this.configStorage.getMsgAuditAccessToken();
}
@Override
public void initHttp() {
ApacheHttpClientBuilder apacheHttpClientBuilder = this.configStorage

View File

@@ -17,6 +17,7 @@ import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.core5.http.HttpHost;
import java.io.IOException;
import java.util.concurrent.locks.Lock;
/**
* The type Wx cp service apache http client.
@@ -75,6 +76,51 @@ public class WxCpServiceHttpComponentsImpl extends BaseWxCpServiceImpl<Closeable
return this.configStorage.getAccessToken();
}
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getMsgAuditAccessToken();
}
Lock lock = this.configStorage.getMsgAuditAccessTokenLock();
lock.lock();
try {
// 拿到锁之后再次判断一下最新的token是否过期避免重刷
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getMsgAuditAccessToken();
}
// 使用会话存档secret获取access_token
String msgAuditSecret = this.configStorage.getMsgAuditSecret();
if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) {
throw new WxErrorException("会话存档secret未配置");
}
String url = String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),
this.configStorage.getCorpId(), msgAuditSecret);
try {
HttpGet httpGet = new HttpGet(url);
if (this.httpProxy != null) {
RequestConfig config = RequestConfig.custom()
.setProxy(this.httpProxy).build();
httpGet.setConfig(config);
}
String resultContent = getRequestHttpClient().execute(httpGet, BasicResponseHandler.INSTANCE);
WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateMsgAuditAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
} catch (IOException e) {
throw new WxRuntimeException(e);
}
} finally {
lock.unlock();
}
return this.configStorage.getMsgAuditAccessToken();
}
@Override
public void initHttp() {
HttpComponentsClientBuilder apacheHttpClientBuilder = DefaultHttpComponentsClientBuilder.get();

View File

@@ -70,6 +70,49 @@ public class WxCpServiceImpl extends WxCpServiceApacheHttpClientImpl {
return configStorage.getAccessToken();
}
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
final WxCpConfigStorage configStorage = getWxCpConfigStorage();
if (!configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
return configStorage.getMsgAuditAccessToken();
}
Lock lock = configStorage.getMsgAuditAccessTokenLock();
lock.lock();
try {
// 拿到锁之后再次判断一下最新的token是否过期避免重刷
if (!configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
return configStorage.getMsgAuditAccessToken();
}
// 使用会话存档secret获取access_token
String msgAuditSecret = configStorage.getMsgAuditSecret();
if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) {
throw new WxErrorException("会话存档secret未配置");
}
String url = String.format(configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),
this.configStorage.getCorpId(), msgAuditSecret);
try {
HttpGet httpGet = new HttpGet(url);
if (getRequestHttpProxy() != null) {
RequestConfig config = RequestConfig.custom().setProxy(getRequestHttpProxy()).build();
httpGet.setConfig(config);
}
String resultContent = getRequestHttpClient().execute(httpGet, ApacheBasicResponseHandler.INSTANCE);
WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
configStorage.updateMsgAuditAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
} catch (IOException e) {
throw new WxRuntimeException(e);
}
} finally {
lock.unlock();
}
return configStorage.getMsgAuditAccessToken();
}
@Override
public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException {
final WxCpConfigStorage configStorage = getWxCpConfigStorage();

View File

@@ -13,6 +13,8 @@ import me.chanjar.weixin.common.util.http.HttpClientType;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
import java.util.concurrent.locks.Lock;
/**
* The type Wx cp service jodd http.
*
@@ -63,6 +65,45 @@ public class WxCpServiceJoddHttpImpl extends BaseWxCpServiceImpl<HttpConnectionP
return this.configStorage.getAccessToken();
}
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getMsgAuditAccessToken();
}
Lock lock = this.configStorage.getMsgAuditAccessTokenLock();
lock.lock();
try {
// 拿到锁之后再次判断一下最新的token是否过期避免重刷
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getMsgAuditAccessToken();
}
// 使用会话存档secret获取access_token
String msgAuditSecret = this.configStorage.getMsgAuditSecret();
if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) {
throw new WxErrorException("会话存档secret未配置");
}
HttpRequest request = HttpRequest.get(String.format(this.configStorage.getApiUrl(WxCpApiPathConsts.GET_TOKEN),
this.configStorage.getCorpId(), msgAuditSecret));
if (this.httpProxy != null) {
httpClient.useProxy(this.httpProxy);
}
request.withConnectionProvider(httpClient);
HttpResponse response = request.send();
String resultContent = response.bodyText();
WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateMsgAuditAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
} finally {
lock.unlock();
}
return this.configStorage.getMsgAuditAccessToken();
}
@Override
public void initHttp() {
if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {

View File

@@ -12,6 +12,7 @@ import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import okhttp3.*;
import java.io.IOException;
import java.util.concurrent.locks.Lock;
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_TOKEN;
@@ -74,6 +75,52 @@ public class WxCpServiceOkHttpImpl extends BaseWxCpServiceImpl<OkHttpClient, OkH
return this.configStorage.getAccessToken();
}
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getMsgAuditAccessToken();
}
Lock lock = this.configStorage.getMsgAuditAccessTokenLock();
lock.lock();
try {
// 拿到锁之后再次判断一下最新的token是否过期避免重刷
if (!this.configStorage.isMsgAuditAccessTokenExpired() && !forceRefresh) {
return this.configStorage.getMsgAuditAccessToken();
}
// 使用会话存档secret获取access_token
String msgAuditSecret = this.configStorage.getMsgAuditSecret();
if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) {
throw new WxErrorException("会话存档secret未配置");
}
//得到httpClient
OkHttpClient client = getRequestHttpClient();
//请求的request
Request request = new Request.Builder()
.url(String.format(this.configStorage.getApiUrl(GET_TOKEN), this.configStorage.getCorpId(),
msgAuditSecret))
.get()
.build();
String resultContent = null;
try (Response response = client.newCall(request).execute()) {
resultContent = response.body().string();
} catch (IOException e) {
log.error(e.getMessage(), e);
}
WxError error = WxError.fromJson(resultContent, WxType.CP);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateMsgAuditAccessToken(accessToken.getAccessToken(),
accessToken.getExpiresIn());
} finally {
lock.unlock();
}
return this.configStorage.getMsgAuditAccessToken();
}
@Override
public void initHttp() {
log.debug("WxCpServiceOkHttpImpl initHttp");

View File

@@ -265,6 +265,40 @@ public interface WxCpConfigStorage {
*/
String getMsgAuditSecret();
/**
* 获取会话存档的access token
*
* @return msg audit access token
*/
String getMsgAuditAccessToken();
/**
* 获取会话存档access token的锁
*
* @return msg audit access token lock
*/
Lock getMsgAuditAccessTokenLock();
/**
* 检查会话存档access token是否已过期
*
* @return true: 已过期, false: 未过期
*/
boolean isMsgAuditAccessTokenExpired();
/**
* 强制将会话存档access token过期掉
*/
void expireMsgAuditAccessToken();
/**
* 更新会话存档access token
*
* @param accessToken 会话存档access token
* @param expiresInSeconds 过期时间(秒)
*/
void updateMsgAuditAccessToken(String accessToken, int expiresInSeconds);
/**
* 获取会话存档SDK
* 会话存档SDK初始化后有效期为7200秒无需每次重新初始化

View File

@@ -50,6 +50,15 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
private volatile String msgAuditSecret;
private volatile String msgAuditPriKey;
private volatile String msgAuditLibPath;
/**
* 会话存档access token及其过期时间
*/
private volatile String msgAuditAccessToken;
private volatile long msgAuditAccessTokenExpiresTime;
/**
* 会话存档access token锁
*/
protected transient Lock msgAuditAccessTokenLock = new ReentrantLock();
/**
* 会话存档SDK及其过期时间
*/
@@ -463,6 +472,33 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
return this;
}
@Override
public String getMsgAuditAccessToken() {
return this.msgAuditAccessToken;
}
@Override
public Lock getMsgAuditAccessTokenLock() {
return this.msgAuditAccessTokenLock;
}
@Override
public boolean isMsgAuditAccessTokenExpired() {
return System.currentTimeMillis() > this.msgAuditAccessTokenExpiresTime;
}
@Override
public void expireMsgAuditAccessToken() {
this.msgAuditAccessTokenExpiresTime = 0;
}
@Override
public synchronized void updateMsgAuditAccessToken(String accessToken, int expiresInSeconds) {
this.msgAuditAccessToken = accessToken;
// 预留200秒的时间
this.msgAuditAccessTokenExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
}
@Override
public long getMsgAuditSdk() {
return this.msgAuditSdk;

View File

@@ -60,6 +60,17 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
* 会话存档SDK引用计数用于多线程安全的生命周期管理
*/
private volatile int msgAuditSdkRefCount;
/**
* 会话存档access token锁本地锁不支持分布式
*
* <p>注意此实现使用本地ReentrantLock在多实例部署时无法保证跨JVM的同步。
* 由于本类已标记为 @Deprecated建议在生产环境中自行实现支持分布式锁的配置存储。
* 可以考虑使用 Redisson 或 Spring Integration 提供的 Redis 分布式锁实现。</p>
*
* @see #expireMsgAuditAccessToken()
* @see #updateMsgAuditAccessToken(String, int)
*/
private final Lock msgAuditAccessTokenLock = new ReentrantLock();
/**
* Instantiates a new Wx cp redis config.
@@ -481,6 +492,31 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
return null;
}
@Override
public String getMsgAuditAccessToken() {
return null;
}
@Override
public Lock getMsgAuditAccessTokenLock() {
return this.msgAuditAccessTokenLock;
}
@Override
public boolean isMsgAuditAccessTokenExpired() {
return true;
}
@Override
public void expireMsgAuditAccessToken() {
// 不支持
}
@Override
public void updateMsgAuditAccessToken(String accessToken, int expiresInSeconds) {
// 不支持
}
@Override
public long getMsgAuditSdk() {
return this.msgAuditSdk;

View File

@@ -101,6 +101,11 @@ public class BaseWxCpServiceImplTest {
return "模拟一个过期的access token:" + System.currentTimeMillis();
}
@Override
public String getMsgAuditAccessToken(boolean forceRefresh) throws WxErrorException {
return "mock_msg_audit_access_token";
}
@Override
public void initHttp() {

View File

@@ -0,0 +1,254 @@
package me.chanjar.weixin.cp.api.impl;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.http.HttpClientType;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import java.util.concurrent.locks.Lock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* 测试 getMsgAuditAccessToken 方法在各个实现类中的正确性
*
* @author Binary Wang
*/
@Test
public class WxCpServiceGetMsgAuditAccessTokenTest {
private WxCpDefaultConfigImpl config;
@BeforeMethod
public void setUp() {
config = new WxCpDefaultConfigImpl();
config.setCorpId("testCorpId");
config.setCorpSecret("testCorpSecret");
config.setMsgAuditSecret("testMsgAuditSecret");
}
/**
* 测试会话存档access token的缓存机制
* 验证当token未过期时直接从配置中返回缓存的token
*/
@Test
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("cached_token");
}
/**
* 测试强制刷新会话存档access token
* 验证forceRefresh=true时会重新获取token
*/
@Test
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");
}
/**
* 测试token过期时自动刷新
* 验证当token已过期时会自动重新获取
*/
@Test
public void testGetMsgAuditAccessToken_Expired() throws WxErrorException {
// 设置一个已过期的token过期时间为负数确保立即过期
config.updateMsgAuditAccessToken("expired_token", -1);
BaseWxCpServiceImpl service = createTestServiceWithMockToken(config, "refreshed_token");
// 过期的token应该被自动刷新
String token = service.getMsgAuditAccessToken(false);
assertThat(token).isEqualTo("refreshed_token");
}
/**
* 测试获取锁机制
* 验证配置中的锁可以正常获取和使用
*/
@Test
public void testGetMsgAuditAccessToken_Lock() {
// 验证配置提供的锁不为null
assertThat(config.getMsgAuditAccessTokenLock()).isNotNull();
// 验证锁可以正常使用
config.getMsgAuditAccessTokenLock().lock();
try {
assertThat(config.getMsgAuditAccessToken()).isNull();
} finally {
config.getMsgAuditAccessTokenLock().unlock();
}
}
/**
* 检查token是否需要刷新的公共逻辑
*/
private boolean shouldRefreshToken(WxCpConfigStorage storage, boolean forceRefresh) {
return storage.isMsgAuditAccessTokenExpired() || forceRefresh;
}
/**
* 验证会话存档secret是否已配置的公共逻辑
*/
private void validateMsgAuditSecret(String msgAuditSecret) throws WxErrorException {
if (msgAuditSecret == null || msgAuditSecret.trim().isEmpty()) {
throw new WxErrorException("会话存档secret未配置");
}
}
/**
* 创建一个用于测试的BaseWxCpServiceImpl实现
* 用于测试缓存和过期逻辑
*/
private BaseWxCpServiceImpl createTestService(WxCpConfigStorage config) {
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 {
// 检查是否需要刷新
if (!shouldRefreshToken(getWxCpConfigStorage(), forceRefresh)) {
return getWxCpConfigStorage().getMsgAuditAccessToken();
}
// 使用会话存档secret获取access_token
String msgAuditSecret = getWxCpConfigStorage().getMsgAuditSecret();
validateMsgAuditSecret(msgAuditSecret);
// 返回缓存的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 {
// 使用锁机制
Lock lock = getWxCpConfigStorage().getMsgAuditAccessTokenLock();
lock.lock();
try {
// 检查是否需要刷新
if (!shouldRefreshToken(getWxCpConfigStorage(), forceRefresh)) {
return getWxCpConfigStorage().getMsgAuditAccessToken();
}
// 使用会话存档secret获取access_token
String msgAuditSecret = getWxCpConfigStorage().getMsgAuditSecret();
validateMsgAuditSecret(msgAuditSecret);
// 模拟获取新token并更新配置
getWxCpConfigStorage().updateMsgAuditAccessToken(mockToken, 7200);
return mockToken;
} finally {
lock.unlock();
}
}
@Override
public void initHttp() {
}
@Override
public WxCpConfigStorage getWxCpConfigStorage() {
return config;
}
};
}
/**
* 测试当 MsgAuditSecret 未配置时应该抛出异常
*/
@Test
public void testGetMsgAuditAccessToken_WithoutSecret() {
config.setMsgAuditSecret(null);
BaseWxCpServiceImpl service = createTestService(config);
// 验证当 secret 为 null 时抛出异常
assertThatThrownBy(() -> service.getMsgAuditAccessToken(true))
.isInstanceOf(WxErrorException.class)
.hasMessageContaining("会话存档secret未配置");
}
/**
* 测试当 MsgAuditSecret 为空字符串时应该抛出异常
*/
@Test
public void testGetMsgAuditAccessToken_WithEmptySecret() {
config.setMsgAuditSecret(" ");
BaseWxCpServiceImpl service = createTestService(config);
// 验证当 secret 为空字符串时抛出异常
assertThatThrownBy(() -> service.getMsgAuditAccessToken(true))
.isInstanceOf(WxErrorException.class)
.hasMessageContaining("会话存档secret未配置");
}
}