Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,30 +297,48 @@ public void getMediaFile(@NonNull long sdk, @NonNull String sdkfileid, String pr

@Override
public List<String> getPermitUserList(Integer type) throws WxErrorException {
// 获取会话存档专用的access token
String msgAuditAccessToken = this.cpService.getMsgAuditAccessToken(false);
final String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_PERMIT_USER_LIST);
// 手动拼接access_token参数
String urlWithToken = apiUrl + (apiUrl.contains("?") ? "&" : "?") + "access_token=" + msgAuditAccessToken;

JsonObject jsonObject = new JsonObject();
if (type != null) {
jsonObject.addProperty("type", type);
}
String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
// 使用不自动添加access token的post方法
String responseContent = this.cpService.postWithoutToken(urlWithToken, jsonObject.toString());
Copy link

Choose a reason for hiding this comment

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

这里通过 postWithoutToken + 手动拼接 access_token 绕开了 BaseWxCpServiceImpl.execute(...) 的 token 失效自动刷新/重试逻辑;如果会话存档 token 被提前失效(未到本地过期时间),这些接口可能会直接失败而不会触发刷新。

Other Locations
  • weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java:328
  • weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMsgAuditServiceImpl.java:341

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

return WxCpGsonBuilder.create().fromJson(GsonParser.parse(responseContent).getAsJsonArray("ids"),
new TypeToken<List<String>>() {
}.getType());
}

@Override
public WxCpGroupChat getGroupChat(@NonNull String roomid) throws WxErrorException {
// 获取会话存档专用的access token
String msgAuditAccessToken = this.cpService.getMsgAuditAccessToken(false);
final String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(GET_GROUP_CHAT);
// 手动拼接access_token参数
String urlWithToken = apiUrl + (apiUrl.contains("?") ? "&" : "?") + "access_token=" + msgAuditAccessToken;

JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("roomid", roomid);
String responseContent = this.cpService.post(apiUrl, jsonObject.toString());
// 使用不自动添加access token的post方法
String responseContent = this.cpService.postWithoutToken(urlWithToken, jsonObject.toString());
return WxCpGroupChat.fromJson(responseContent);
}

@Override
public WxCpAgreeInfo checkSingleAgree(@NonNull WxCpCheckAgreeRequest checkAgreeRequest) throws WxErrorException {
// 获取会话存档专用的access token
String msgAuditAccessToken = this.cpService.getMsgAuditAccessToken(false);
String apiUrl = this.cpService.getWxCpConfigStorage().getApiUrl(CHECK_SINGLE_AGREE);
String responseContent = this.cpService.post(apiUrl, checkAgreeRequest.toJson());
// 手动拼接access_token参数
String urlWithToken = apiUrl + (apiUrl.contains("?") ? "&" : "?") + "access_token=" + msgAuditAccessToken;

// 使用不自动添加access token的post方法
String responseContent = this.cpService.postWithoutToken(urlWithToken, checkAgreeRequest.toJson());
return WxCpAgreeInfo.fromJson(responseContent);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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.
Expand Down Expand Up @@ -74,6 +75,51 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
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.
Expand Down Expand Up @@ -75,6 +76,51 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,49 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
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.
*
Expand Down Expand Up @@ -63,6 +65,45 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import okhttp3.*;

import java.io.IOException;
import java.util.concurrent.locks.Lock;

import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_TOKEN;

Expand Down Expand Up @@ -74,6 +75,52 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
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);
Copy link

Choose a reason for hiding this comment

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

这里捕获 IOException 只记录日志但继续向下执行,resultContent 可能为 null(或不完整),随后 WxError.fromJson(resultContent, ...) 可能触发 NPE/解析异常并掩盖真实网络错误。

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎

}

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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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秒,无需每次重新初始化
Expand Down
Loading