Skip to content

Commit 62a3931

Browse files
authored
🎨 binarywang#2115 优化公众号、小程序、企业微信的access token自动刷新逻辑,避免循环递归调用
1 parent dfb02ea commit 62a3931

File tree

8 files changed

+200
-28
lines changed

8 files changed

+200
-28
lines changed

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) thro
241241
int retryTimes = 0;
242242
do {
243243
try {
244-
return this.executeInternal(executor, uri, data);
244+
return this.executeInternal(executor, uri, data, false);
245245
} catch (WxErrorException e) {
246246
if (retryTimes + 1 > this.maxRetryTimes) {
247247
log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
@@ -271,7 +271,7 @@ public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) thro
271271
throw new WxRuntimeException("微信服务端异常,超出重试次数");
272272
}
273273

274-
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
274+
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data, boolean doNotAutoRefresh) throws WxErrorException {
275275
E dataForLog = DataUtils.handleDataWithSecret(data);
276276

277277
if (uri.contains("access_token=")) {
@@ -291,9 +291,11 @@ protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E
291291
if (WxConsts.ACCESS_TOKEN_ERROR_CODES.contains(error.getErrorCode())) {
292292
// 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
293293
this.configStorage.expireAccessToken();
294-
if (this.getWxCpConfigStorage().autoRefreshToken()) {
294+
if (this.getWxCpConfigStorage().autoRefreshToken() && !doNotAutoRefresh) {
295295
log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
296-
return this.execute(executor, uri, data);
296+
//下一次不再自动重试
297+
//当小程序误调用第三方平台专属接口时,第三方无法使用小程序的access token,如果可以继续自动获取token会导致无限循环重试,直到栈溢出
298+
return this.executeInternal(executor, uri, data, true);
297299
}
298300
}
299301

weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public Object[][] getService() {
2222

2323
@Override
2424
public synchronized <T, E> T executeInternal(
25-
RequestExecutor<T, E> executor, String uri, E data)
25+
RequestExecutor<T, E> executor, String uri, E data, boolean doNotAutoRefresh)
2626
throws WxErrorException {
2727
log.info("Executed");
2828
throw new WxErrorException("something");

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

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
11
package me.chanjar.weixin.cp.api.impl;
22

33
import com.google.inject.Inject;
4+
import me.chanjar.weixin.common.error.WxError;
45
import me.chanjar.weixin.common.error.WxErrorException;
6+
import me.chanjar.weixin.common.error.WxMpErrorMsgEnum;
7+
import me.chanjar.weixin.common.util.http.HttpType;
8+
import me.chanjar.weixin.common.util.http.RequestExecutor;
59
import me.chanjar.weixin.cp.api.ApiTestModule;
610
import me.chanjar.weixin.cp.api.WxCpService;
11+
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
12+
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
13+
import org.mockito.Mockito;
14+
import org.testng.Assert;
715
import org.testng.annotations.Guice;
816
import org.testng.annotations.Test;
917

18+
import java.io.IOException;
19+
import java.util.HashMap;
20+
import java.util.concurrent.atomic.AtomicInteger;
21+
1022
import static org.assertj.core.api.Assertions.assertThat;
23+
import static org.mockito.Mockito.mock;
1124

1225
/**
1326
* <pre>
@@ -35,6 +48,61 @@ public void testJsCode2Session() throws WxErrorException {
3548

3649
@Test
3750
public void testGetProviderToken() throws WxErrorException {
38-
assertThat(this.wxService.getProviderToken("111","123")).isNotNull();
51+
assertThat(this.wxService.getProviderToken("111", "123")).isNotNull();
52+
}
53+
54+
55+
@Test
56+
public void testExecuteAutoRefreshToken() throws WxErrorException, IOException {
57+
//测试access token获取时的重试机制
58+
WxCpDefaultConfigImpl config = new WxCpDefaultConfigImpl();
59+
BaseWxCpServiceImpl service = new BaseWxCpServiceImpl() {
60+
@Override
61+
public Object getRequestHttpClient() {
62+
return null;
63+
}
64+
65+
@Override
66+
public Object getRequestHttpProxy() {
67+
return null;
68+
}
69+
70+
@Override
71+
public HttpType getRequestType() {
72+
return null;
73+
}
74+
75+
@Override
76+
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
77+
return "模拟一个过期的access token:" + System.currentTimeMillis();
78+
}
79+
80+
@Override
81+
public void initHttp() {
82+
83+
}
84+
85+
@Override
86+
public WxCpConfigStorage getWxCpConfigStorage() {
87+
return config;
88+
}
89+
};
90+
config.setAgentId(1);
91+
service.setWxCpConfigStorage(config);
92+
RequestExecutor<Object, Object> re = mock(RequestExecutor.class);
93+
94+
AtomicInteger counter = new AtomicInteger();
95+
Mockito.when(re.execute(Mockito.anyString(), Mockito.any(), Mockito.any())).thenAnswer(invocation -> {
96+
counter.incrementAndGet();
97+
WxError error = WxError.builder().errorCode(WxMpErrorMsgEnum.CODE_40001.getCode()).errorMsg(WxMpErrorMsgEnum.CODE_40001.getMsg()).build();
98+
throw new WxErrorException(error);
99+
});
100+
try {
101+
Object execute = service.execute(re, "http://baidu.com", new HashMap<>());
102+
Assert.assertTrue(false, "代码应该不会执行到这里");
103+
} catch (WxErrorException e) {
104+
Assert.assertEquals(WxMpErrorMsgEnum.CODE_40001.getCode(), e.getError().getErrorCode());
105+
Assert.assertEquals(2, counter.get());
106+
}
39107
}
40108
}

weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) thro
207207
int retryTimes = 0;
208208
do {
209209
try {
210-
return this.executeInternal(executor, uri, data);
210+
return this.executeInternal(executor, uri, data, false);
211211
} catch (WxErrorException e) {
212212
if (retryTimes + 1 > this.maxRetryTimes) {
213213
log.warn("重试达到最大次数【{}】", maxRetryTimes);
@@ -238,7 +238,7 @@ public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) thro
238238
throw new WxRuntimeException("微信服务端异常,超出重试次数");
239239
}
240240

241-
private <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
241+
private <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data, boolean doNotAutoRefreshToken) throws WxErrorException {
242242
E dataForLog = DataUtils.handleDataWithSecret(data);
243243

244244
if (uri.contains("access_token=")) {
@@ -271,9 +271,11 @@ private <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E d
271271
} finally {
272272
lock.unlock();
273273
}
274-
if (this.getWxMaConfig().autoRefreshToken()) {
274+
if (this.getWxMaConfig().autoRefreshToken() && !doNotAutoRefreshToken) {
275275
log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
276-
return this.execute(executor, uri, data);
276+
//下一次不再自动重试
277+
//当小程序误调用第三方平台专属接口时,第三方无法使用小程序的access token,如果可以继续自动获取token会导致无限循环重试,直到栈溢出
278+
return this.executeInternal(executor, uri, data, true);
277279
}
278280
}
279281

weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImplTest.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,25 @@
22

33
import cn.binarywang.wx.miniapp.api.WxMaService;
44
import cn.binarywang.wx.miniapp.config.WxMaConfig;
5+
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
56
import cn.binarywang.wx.miniapp.test.ApiTestModule;
67
import com.google.inject.Inject;
8+
import me.chanjar.weixin.common.error.WxError;
79
import me.chanjar.weixin.common.error.WxErrorException;
10+
import me.chanjar.weixin.common.error.WxMpErrorMsgEnum;
11+
import me.chanjar.weixin.common.util.http.RequestExecutor;
812
import org.apache.commons.lang3.StringUtils;
13+
import org.mockito.Mockito;
14+
import org.testng.Assert;
915
import org.testng.annotations.Guice;
1016
import org.testng.annotations.Test;
1117

18+
import java.io.IOException;
19+
import java.util.HashMap;
20+
import java.util.concurrent.atomic.AtomicInteger;
21+
1222
import static org.assertj.core.api.Assertions.assertThat;
23+
import static org.mockito.Mockito.mock;
1324
import static org.testng.Assert.assertNotEquals;
1425
import static org.testng.Assert.assertTrue;
1526

@@ -101,6 +112,35 @@ public void testGet() {
101112
public void testExecute() {
102113
}
103114

115+
@Test
116+
public void testExecuteAutoRefreshToken() throws WxErrorException, IOException {
117+
//测试access token获取时的重试机制
118+
WxMaServiceImpl service = new WxMaServiceImpl() {
119+
@Override
120+
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
121+
return "模拟一个过期的access token:" + System.currentTimeMillis();
122+
}
123+
};
124+
WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl();
125+
config.setAppid("1");
126+
service.setWxMaConfig(config);
127+
RequestExecutor<Object, Object> re = mock(RequestExecutor.class);
128+
129+
AtomicInteger counter = new AtomicInteger();
130+
Mockito.when(re.execute(Mockito.anyString(), Mockito.any(), Mockito.any())).thenAnswer(invocation -> {
131+
counter.incrementAndGet();
132+
WxError error = WxError.builder().errorCode(WxMpErrorMsgEnum.CODE_40001.getCode()).errorMsg(WxMpErrorMsgEnum.CODE_40001.getMsg()).build();
133+
throw new WxErrorException(error);
134+
});
135+
try {
136+
Object execute = service.execute(re, "http://baidu.com", new HashMap<>());
137+
Assert.assertTrue(false, "代码应该不会执行到这里");
138+
} catch (WxErrorException e) {
139+
Assert.assertEquals(WxMpErrorMsgEnum.CODE_40001.getCode(), e.getError().getErrorCode());
140+
Assert.assertEquals(2, counter.get());
141+
}
142+
}
143+
104144
@Test
105145
public void testGetWxMaConfig() {
106146
}

weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) thro
341341
int retryTimes = 0;
342342
do {
343343
try {
344-
return this.executeInternal(executor, uri, data);
344+
return this.executeInternal(executor, uri, data, false);
345345
} catch (WxErrorException e) {
346346
WxError error = e.getError();
347347
// -1 系统繁忙, 1000ms后重试
@@ -370,7 +370,7 @@ public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) thro
370370
throw new WxRuntimeException("微信服务端异常,超出重试次数");
371371
}
372372

373-
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
373+
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data, boolean doNotAutoRefresh) throws WxErrorException {
374374
E dataForLog = DataUtils.handleDataWithSecret(data);
375375

376376
if (uri.contains("access_token=")) {
@@ -399,9 +399,11 @@ protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E
399399
} finally {
400400
lock.unlock();
401401
}
402-
if (this.getWxMpConfigStorage().autoRefreshToken()) {
402+
if (this.getWxMpConfigStorage().autoRefreshToken() && !doNotAutoRefresh) {
403403
log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
404-
return this.execute(executor, uri, data);
404+
//下一次不再自动重试
405+
//当小程序误调用第三方平台专属接口时,第三方无法使用小程序的access token,如果可以继续自动获取token会导致无限循环重试,直到栈溢出
406+
return this.executeInternal(executor, uri, data, true);
405407
}
406408
}
407409

weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBusyRetryTest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package me.chanjar.weixin.mp.api;
22

33
import lombok.extern.slf4j.Slf4j;
4-
import me.chanjar.weixin.common.error.WxError;
54
import me.chanjar.weixin.common.error.WxErrorException;
65
import me.chanjar.weixin.common.error.WxRuntimeException;
76
import me.chanjar.weixin.common.util.http.RequestExecutor;
87
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl;
9-
import org.testng.annotations.*;
8+
import org.testng.annotations.DataProvider;
9+
import org.testng.annotations.Test;
1010

1111
import java.util.concurrent.ExecutionException;
1212
import java.util.concurrent.ExecutorService;
@@ -23,7 +23,7 @@ public Object[][] getService() {
2323

2424
@Override
2525
public synchronized <T, E> T executeInternal(
26-
RequestExecutor<T, E> executor, String uri, E data)
26+
RequestExecutor<T, E> executor, String uri, E data, boolean doNotAutoRefresh)
2727
throws WxErrorException {
2828
log.info("Executed");
2929
throw new WxErrorException("something");
@@ -37,7 +37,7 @@ public synchronized <T, E> T executeInternal(
3737

3838
@Test(dataProvider = "getService", expectedExceptions = RuntimeException.class)
3939
public void testRetry(WxMpService service) throws WxErrorException {
40-
service.execute(null, (String)null, null);
40+
service.execute(null, (String) null, null);
4141
}
4242

4343
@Test(dataProvider = "getService")
@@ -48,7 +48,7 @@ public void testRetryInThreadPool(final WxMpService service) throws InterruptedE
4848
try {
4949
System.out.println("=====================");
5050
System.out.println(Thread.currentThread().getName() + ": testRetry");
51-
service.execute(null, (String)null, null);
51+
service.execute(null, (String) null, null);
5252
} catch (WxErrorException e) {
5353
throw new WxRuntimeException(e);
5454
} catch (RuntimeException e) {

0 commit comments

Comments
 (0)