Skip to content

Commit e53c921

Browse files
committed
fix: WxMpServiceImpl中用ThreadLocal记录重试次数,这个是有问题的,因为在多线程(线程池)环境下ThreadLocal是不会被清0的
1 parent 1635024 commit e53c921

File tree

8 files changed

+280
-72
lines changed

8 files changed

+280
-72
lines changed

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,4 +353,22 @@ public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputS
353353
* @param wxConfigProvider
354354
*/
355355
public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider);
356+
357+
/**
358+
* <pre>
359+
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试
360+
* 默认:1000ms
361+
* </pre>
362+
* @param retrySleepMillis
363+
*/
364+
void setRetrySleepMillis(int retrySleepMillis);
365+
366+
/**
367+
* <pre>
368+
* 设置当微信系统响应系统繁忙时,最大重试次数
369+
* 默认:5次
370+
* </pre>
371+
* @param maxRetryTimes
372+
*/
373+
void setMaxRetryTimes(int maxRetryTimes);
356374
}

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java

Lines changed: 43 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,14 @@ public class WxCpServiceImpl implements WxCpService {
5252

5353
protected WxCpConfigStorage wxCpConfigStorage;
5454

55-
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>();
56-
5755
protected CloseableHttpClient httpClient;
5856

5957
protected HttpHost httpProxy;
6058

59+
private int retrySleepMillis = 1000;
60+
61+
private int maxRetryTimes = 5;
62+
6163
public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) {
6264
try {
6365
return SHA1.gen(wxCpConfigStorage.getToken(), timestamp, nonce, data).equals(msgSignature);
@@ -366,6 +368,33 @@ public String post(String url, String postData) throws WxErrorException {
366368
* @throws WxErrorException
367369
*/
368370
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
371+
int retryTimes = 0;
372+
do {
373+
try {
374+
return executeInternal(executor, uri, data);
375+
} catch (WxErrorException e) {
376+
WxError error = e.getError();
377+
/**
378+
* -1 系统繁忙, 1000ms后重试
379+
*/
380+
if (error.getErrorCode() == -1) {
381+
int sleepMillis = retrySleepMillis * (1 << retryTimes);
382+
try {
383+
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试(第" + (retryTimes + 1) + "次)");
384+
Thread.sleep(sleepMillis);
385+
} catch (InterruptedException e1) {
386+
throw new RuntimeException(e1);
387+
}
388+
} else {
389+
throw e;
390+
}
391+
}
392+
} while(++retryTimes < maxRetryTimes);
393+
394+
throw new RuntimeException("微信服务端异常,超出重试次数");
395+
}
396+
397+
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
369398
String accessToken = getAccessToken(false);
370399

371400
String uriWithAccessToken = uri;
@@ -381,31 +410,10 @@ public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) thro
381410
* 42001 access_token超时
382411
*/
383412
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
384-
// 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
413+
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
385414
wxCpConfigStorage.expireAccessToken();
386415
return execute(executor, uri, data);
387416
}
388-
/**
389-
* -1 系统繁忙, 1000ms后重试
390-
*/
391-
if (error.getErrorCode() == -1) {
392-
if (retryTimes.get() == null) {
393-
retryTimes.set(0);
394-
}
395-
if (retryTimes.get() > 4) {
396-
retryTimes.set(0);
397-
throw new RuntimeException("微信服务端异常,超出重试次数");
398-
}
399-
int sleepMillis = 1000 * (1 << retryTimes.get());
400-
try {
401-
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试");
402-
Thread.sleep(sleepMillis);
403-
retryTimes.set(retryTimes.get() + 1);
404-
return execute(executor, uri, data);
405-
} catch (InterruptedException e1) {
406-
throw new RuntimeException(e1);
407-
}
408-
}
409417
if (error.getErrorCode() != 0) {
410418
throw new WxErrorException(error);
411419
}
@@ -416,7 +424,6 @@ public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) thro
416424
throw new RuntimeException(e);
417425
}
418426
}
419-
420427
protected CloseableHttpClient getHttpclient() {
421428
return httpClient;
422429
}
@@ -451,4 +458,15 @@ public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) {
451458
}
452459
}
453460

461+
@Override
462+
public void setRetrySleepMillis(int retrySleepMillis) {
463+
this.retrySleepMillis = retrySleepMillis;
464+
}
465+
466+
467+
@Override
468+
public void setMaxRetryTimes(int maxRetryTimes) {
469+
this.maxRetryTimes = maxRetryTimes;
470+
}
471+
454472
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package me.chanjar.weixin.cp.api;
2+
3+
import me.chanjar.weixin.common.bean.result.WxError;
4+
import me.chanjar.weixin.common.exception.WxErrorException;
5+
import me.chanjar.weixin.common.util.http.RequestExecutor;
6+
import org.testng.annotations.DataProvider;
7+
import org.testng.annotations.Test;
8+
9+
import java.util.concurrent.ExecutionException;
10+
import java.util.concurrent.ExecutorService;
11+
import java.util.concurrent.Executors;
12+
import java.util.concurrent.Future;
13+
14+
@Test
15+
public class WxCpBusyRetryTest {
16+
17+
@DataProvider(name="getService")
18+
public Object[][] getService() {
19+
WxCpService service = new WxCpServiceImpl() {
20+
21+
@Override
22+
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
23+
WxError error = new WxError();
24+
error.setErrorCode(-1);
25+
throw new WxErrorException(error);
26+
}
27+
};
28+
29+
service.setMaxRetryTimes(3);
30+
service.setRetrySleepMillis(500);
31+
return new Object[][] {
32+
new Object[] { service }
33+
};
34+
}
35+
36+
@Test(dataProvider = "getService", expectedExceptions = RuntimeException.class)
37+
public void testRetry(WxCpService service) throws WxErrorException {
38+
service.execute(null, null, null);
39+
}
40+
41+
@Test(dataProvider = "getService")
42+
public void testRetryInThreadPool(final WxCpService service) throws InterruptedException, ExecutionException {
43+
// 当线程池中的线程复用的时候,还是能保证相同的重试次数
44+
ExecutorService executorService = Executors.newFixedThreadPool(1);
45+
Runnable runnable = new Runnable() {
46+
@Override
47+
public void run() {
48+
try {
49+
System.out.println("=====================");
50+
System.out.println(Thread.currentThread().getName() + ": testRetry");
51+
service.execute(null, null, null);
52+
} catch (WxErrorException e) {
53+
throw new RuntimeException(e);
54+
} catch (RuntimeException e) {
55+
// OK
56+
}
57+
}
58+
};
59+
Future<?> submit1 = executorService.submit(runnable);
60+
Future<?> submit2 = executorService.submit(runnable);
61+
62+
submit1.get();
63+
submit2.get();
64+
}
65+
66+
}
Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,29 @@
11
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
22

33
<suite name="Weixin-java-tool-suite" verbose="1">
4-
<test name="API_Test">
5-
<classes>
6-
<class name="me.chanjar.weixin.cp.api.WxCpBaseAPITest" />
7-
<class name="me.chanjar.weixin.cp.api.WxCpMessageAPITest" />
8-
<class name="me.chanjar.weixin.cp.api.WxMenuAPITest" />
9-
<class name="me.chanjar.weixin.cp.api.WxCpDepartAPITest" />
10-
<class name="me.chanjar.weixin.cp.api.WxCpMediaAPITest" />
11-
<class name="me.chanjar.weixin.cp.api.WxCpMessageRouterTest" />
12-
<class name="me.chanjar.weixin.cp.api.WxCpTagAPITest" />
13-
<class name="me.chanjar.weixin.cp.api.WxCpUserAPITest" />
14-
</classes>
15-
</test>
4+
<test name="API_Test">
5+
<classes>
6+
<class name="me.chanjar.weixin.cp.api.WxCpBusyRetryTest"/>
7+
<class name="me.chanjar.weixin.cp.api.WxCpBaseAPITest"/>
8+
<class name="me.chanjar.weixin.cp.api.WxCpMessageAPITest"/>
9+
<class name="me.chanjar.weixin.cp.api.WxMenuAPITest"/>
10+
<class name="me.chanjar.weixin.cp.api.WxCpDepartAPITest"/>
11+
<class name="me.chanjar.weixin.cp.api.WxCpMediaAPITest"/>
12+
<class name="me.chanjar.weixin.cp.api.WxCpMessageRouterTest"/>
13+
<class name="me.chanjar.weixin.cp.api.WxCpTagAPITest"/>
14+
<class name="me.chanjar.weixin.cp.api.WxCpUserAPITest"/>
15+
</classes>
16+
</test>
1617

17-
<test name="Bean_Test">
18-
<classes>
19-
<class name="me.chanjar.weixin.cp.bean.WxCpMessageTest" />
20-
<class name="me.chanjar.weixin.cp.bean.WxCpXmlMessageTest" />
21-
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessageTest" />
22-
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessageTest" />
23-
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessageTest" />
24-
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessageTest" />
25-
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessageTest" />
26-
</classes>
27-
</test>
18+
<test name="Bean_Test">
19+
<classes>
20+
<class name="me.chanjar.weixin.cp.bean.WxCpMessageTest"/>
21+
<class name="me.chanjar.weixin.cp.bean.WxCpXmlMessageTest"/>
22+
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessageTest"/>
23+
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessageTest"/>
24+
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessageTest"/>
25+
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessageTest"/>
26+
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessageTest"/>
27+
</classes>
28+
</test>
2829
</suite>

weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,4 +461,22 @@ public interface WxMpService {
461461
* @param wxConfigProvider
462462
*/
463463
public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider);
464+
465+
/**
466+
* <pre>
467+
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试
468+
* 默认:1000ms
469+
* </pre>
470+
* @param retrySleepMillis
471+
*/
472+
void setRetrySleepMillis(int retrySleepMillis);
473+
474+
/**
475+
* <pre>
476+
* 设置当微信系统响应系统繁忙时,最大重试次数
477+
* 默认:5次
478+
* </pre>
479+
* @param maxRetryTimes
480+
*/
481+
void setMaxRetryTimes(int maxRetryTimes);
464482
}

weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,14 @@ public class WxMpServiceImpl implements WxMpService {
5454

5555
protected WxMpConfigStorage wxMpConfigStorage;
5656

57-
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>();
58-
5957
protected CloseableHttpClient httpClient;
6058

6159
protected HttpHost httpProxy;
6260

61+
private int retrySleepMillis = 1000;
62+
63+
private int maxRetryTimes = 5;
64+
6365
public boolean checkSignature(String timestamp, String nonce, String signature) {
6466
try {
6567
return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature);
@@ -439,7 +441,7 @@ public String post(String url, String postData) throws WxErrorException {
439441
return execute(new SimplePostRequestExecutor(), url, postData);
440442
}
441443

442-
/**
444+
/**
443445
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
444446
* @param executor
445447
* @param uri
@@ -448,6 +450,33 @@ public String post(String url, String postData) throws WxErrorException {
448450
* @throws WxErrorException
449451
*/
450452
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
453+
int retryTimes = 0;
454+
do {
455+
try {
456+
return executeInternal(executor, uri, data);
457+
} catch (WxErrorException e) {
458+
WxError error = e.getError();
459+
/**
460+
* -1 系统繁忙, 1000ms后重试
461+
*/
462+
if (error.getErrorCode() == -1) {
463+
int sleepMillis = retrySleepMillis * (1 << retryTimes);
464+
try {
465+
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试(第" + (retryTimes + 1) + "次)");
466+
Thread.sleep(sleepMillis);
467+
} catch (InterruptedException e1) {
468+
throw new RuntimeException(e1);
469+
}
470+
} else {
471+
throw e;
472+
}
473+
}
474+
} while(++retryTimes < maxRetryTimes);
475+
476+
throw new RuntimeException("微信服务端异常,超出重试次数");
477+
}
478+
479+
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
451480
String accessToken = getAccessToken(false);
452481

453482
String uriWithAccessToken = uri;
@@ -467,27 +496,6 @@ public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) thro
467496
wxMpConfigStorage.expireAccessToken();
468497
return execute(executor, uri, data);
469498
}
470-
/**
471-
* -1 系统繁忙, 1000ms后重试
472-
*/
473-
if (error.getErrorCode() == -1) {
474-
if(retryTimes.get() == null) {
475-
retryTimes.set(0);
476-
}
477-
if (retryTimes.get() > 4) {
478-
retryTimes.set(0);
479-
throw new RuntimeException("微信服务端异常,超出重试次数");
480-
}
481-
int sleepMillis = 1000 * (1 << retryTimes.get());
482-
try {
483-
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试");
484-
Thread.sleep(sleepMillis);
485-
retryTimes.set(retryTimes.get() + 1);
486-
return execute(executor, uri, data);
487-
} catch (InterruptedException e1) {
488-
throw new RuntimeException(e1);
489-
}
490-
}
491499
if (error.getErrorCode() != 0) {
492500
throw new WxErrorException(error);
493501
}
@@ -533,4 +541,16 @@ public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) {
533541
}
534542
}
535543

544+
545+
@Override
546+
public void setRetrySleepMillis(int retrySleepMillis) {
547+
this.retrySleepMillis = retrySleepMillis;
548+
}
549+
550+
551+
@Override
552+
public void setMaxRetryTimes(int maxRetryTimes) {
553+
this.maxRetryTimes = maxRetryTimes;
554+
}
555+
536556
}

0 commit comments

Comments
 (0)