@@ -353,4 +353,22 @@ public interface WxCpService { | |||
* @param wxConfigProvider | |||
*/ | |||
public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider); | |||
/** | |||
* <pre> | |||
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试 | |||
* 默认:1000ms | |||
* </pre> | |||
* @param retrySleepMillis | |||
*/ | |||
void setRetrySleepMillis(int retrySleepMillis); | |||
/** | |||
* <pre> | |||
* 设置当微信系统响应系统繁忙时,最大重试次数 | |||
* 默认:5次 | |||
* </pre> | |||
* @param maxRetryTimes | |||
*/ | |||
void setMaxRetryTimes(int maxRetryTimes); | |||
} |
@@ -52,12 +52,14 @@ public class WxCpServiceImpl implements WxCpService { | |||
protected WxCpConfigStorage wxCpConfigStorage; | |||
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>(); | |||
protected CloseableHttpClient httpClient; | |||
protected HttpHost httpProxy; | |||
private int retrySleepMillis = 1000; | |||
private int maxRetryTimes = 5; | |||
public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | |||
try { | |||
return SHA1.gen(wxCpConfigStorage.getToken(), timestamp, nonce, data).equals(msgSignature); | |||
@@ -366,6 +368,33 @@ public class WxCpServiceImpl implements WxCpService { | |||
* @throws WxErrorException | |||
*/ | |||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
int retryTimes = 0; | |||
do { | |||
try { | |||
return executeInternal(executor, uri, data); | |||
} catch (WxErrorException e) { | |||
WxError error = e.getError(); | |||
/** | |||
* -1 系统繁忙, 1000ms后重试 | |||
*/ | |||
if (error.getErrorCode() == -1) { | |||
int sleepMillis = retrySleepMillis * (1 << retryTimes); | |||
try { | |||
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试(第" + (retryTimes + 1) + "次)"); | |||
Thread.sleep(sleepMillis); | |||
} catch (InterruptedException e1) { | |||
throw new RuntimeException(e1); | |||
} | |||
} else { | |||
throw e; | |||
} | |||
} | |||
} while(++retryTimes < maxRetryTimes); | |||
throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
} | |||
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
String accessToken = getAccessToken(false); | |||
String uriWithAccessToken = uri; | |||
@@ -381,31 +410,10 @@ public class WxCpServiceImpl implements WxCpService { | |||
* 42001 access_token超时 | |||
*/ | |||
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | |||
// 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||
wxCpConfigStorage.expireAccessToken(); | |||
return execute(executor, uri, data); | |||
} | |||
/** | |||
* -1 系统繁忙, 1000ms后重试 | |||
*/ | |||
if (error.getErrorCode() == -1) { | |||
if (retryTimes.get() == null) { | |||
retryTimes.set(0); | |||
} | |||
if (retryTimes.get() > 4) { | |||
retryTimes.set(0); | |||
throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
} | |||
int sleepMillis = 1000 * (1 << retryTimes.get()); | |||
try { | |||
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试"); | |||
Thread.sleep(sleepMillis); | |||
retryTimes.set(retryTimes.get() + 1); | |||
return execute(executor, uri, data); | |||
} catch (InterruptedException e1) { | |||
throw new RuntimeException(e1); | |||
} | |||
} | |||
if (error.getErrorCode() != 0) { | |||
throw new WxErrorException(error); | |||
} | |||
@@ -416,7 +424,6 @@ public class WxCpServiceImpl implements WxCpService { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
protected CloseableHttpClient getHttpclient() { | |||
return httpClient; | |||
} | |||
@@ -451,4 +458,15 @@ public class WxCpServiceImpl implements WxCpService { | |||
} | |||
} | |||
@Override | |||
public void setRetrySleepMillis(int retrySleepMillis) { | |||
this.retrySleepMillis = retrySleepMillis; | |||
} | |||
@Override | |||
public void setMaxRetryTimes(int maxRetryTimes) { | |||
this.maxRetryTimes = maxRetryTimes; | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
package me.chanjar.weixin.cp.api; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import org.testng.annotations.DataProvider; | |||
import org.testng.annotations.Test; | |||
import java.util.concurrent.ExecutionException; | |||
import java.util.concurrent.ExecutorService; | |||
import java.util.concurrent.Executors; | |||
import java.util.concurrent.Future; | |||
@Test | |||
public class WxCpBusyRetryTest { | |||
@DataProvider(name="getService") | |||
public Object[][] getService() { | |||
WxCpService service = new WxCpServiceImpl() { | |||
@Override | |||
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
WxError error = new WxError(); | |||
error.setErrorCode(-1); | |||
throw new WxErrorException(error); | |||
} | |||
}; | |||
service.setMaxRetryTimes(3); | |||
service.setRetrySleepMillis(500); | |||
return new Object[][] { | |||
new Object[] { service } | |||
}; | |||
} | |||
@Test(dataProvider = "getService", expectedExceptions = RuntimeException.class) | |||
public void testRetry(WxCpService service) throws WxErrorException { | |||
service.execute(null, null, null); | |||
} | |||
@Test(dataProvider = "getService") | |||
public void testRetryInThreadPool(final WxCpService service) throws InterruptedException, ExecutionException { | |||
// 当线程池中的线程复用的时候,还是能保证相同的重试次数 | |||
ExecutorService executorService = Executors.newFixedThreadPool(1); | |||
Runnable runnable = new Runnable() { | |||
@Override | |||
public void run() { | |||
try { | |||
System.out.println("====================="); | |||
System.out.println(Thread.currentThread().getName() + ": testRetry"); | |||
service.execute(null, null, null); | |||
} catch (WxErrorException e) { | |||
throw new RuntimeException(e); | |||
} catch (RuntimeException e) { | |||
// OK | |||
} | |||
} | |||
}; | |||
Future<?> submit1 = executorService.submit(runnable); | |||
Future<?> submit2 = executorService.submit(runnable); | |||
submit1.get(); | |||
submit2.get(); | |||
} | |||
} |
@@ -1,28 +1,29 @@ | |||
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > | |||
<suite name="Weixin-java-tool-suite" verbose="1"> | |||
<test name="API_Test"> | |||
<classes> | |||
<class name="me.chanjar.weixin.cp.api.WxCpBaseAPITest" /> | |||
<class name="me.chanjar.weixin.cp.api.WxCpMessageAPITest" /> | |||
<class name="me.chanjar.weixin.cp.api.WxMenuAPITest" /> | |||
<class name="me.chanjar.weixin.cp.api.WxCpDepartAPITest" /> | |||
<class name="me.chanjar.weixin.cp.api.WxCpMediaAPITest" /> | |||
<class name="me.chanjar.weixin.cp.api.WxCpMessageRouterTest" /> | |||
<class name="me.chanjar.weixin.cp.api.WxCpTagAPITest" /> | |||
<class name="me.chanjar.weixin.cp.api.WxCpUserAPITest" /> | |||
</classes> | |||
</test> | |||
<test name="API_Test"> | |||
<classes> | |||
<class name="me.chanjar.weixin.cp.api.WxCpBusyRetryTest"/> | |||
<class name="me.chanjar.weixin.cp.api.WxCpBaseAPITest"/> | |||
<class name="me.chanjar.weixin.cp.api.WxCpMessageAPITest"/> | |||
<class name="me.chanjar.weixin.cp.api.WxMenuAPITest"/> | |||
<class name="me.chanjar.weixin.cp.api.WxCpDepartAPITest"/> | |||
<class name="me.chanjar.weixin.cp.api.WxCpMediaAPITest"/> | |||
<class name="me.chanjar.weixin.cp.api.WxCpMessageRouterTest"/> | |||
<class name="me.chanjar.weixin.cp.api.WxCpTagAPITest"/> | |||
<class name="me.chanjar.weixin.cp.api.WxCpUserAPITest"/> | |||
</classes> | |||
</test> | |||
<test name="Bean_Test"> | |||
<classes> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpMessageTest" /> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlMessageTest" /> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessageTest" /> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessageTest" /> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessageTest" /> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessageTest" /> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessageTest" /> | |||
</classes> | |||
</test> | |||
<test name="Bean_Test"> | |||
<classes> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpMessageTest"/> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlMessageTest"/> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessageTest"/> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessageTest"/> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessageTest"/> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessageTest"/> | |||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessageTest"/> | |||
</classes> | |||
</test> | |||
</suite> |
@@ -461,4 +461,22 @@ public interface WxMpService { | |||
* @param wxConfigProvider | |||
*/ | |||
public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider); | |||
/** | |||
* <pre> | |||
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试 | |||
* 默认:1000ms | |||
* </pre> | |||
* @param retrySleepMillis | |||
*/ | |||
void setRetrySleepMillis(int retrySleepMillis); | |||
/** | |||
* <pre> | |||
* 设置当微信系统响应系统繁忙时,最大重试次数 | |||
* 默认:5次 | |||
* </pre> | |||
* @param maxRetryTimes | |||
*/ | |||
void setMaxRetryTimes(int maxRetryTimes); | |||
} |
@@ -54,12 +54,14 @@ public class WxMpServiceImpl implements WxMpService { | |||
protected WxMpConfigStorage wxMpConfigStorage; | |||
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>(); | |||
protected CloseableHttpClient httpClient; | |||
protected HttpHost httpProxy; | |||
private int retrySleepMillis = 1000; | |||
private int maxRetryTimes = 5; | |||
public boolean checkSignature(String timestamp, String nonce, String signature) { | |||
try { | |||
return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature); | |||
@@ -439,7 +441,7 @@ public class WxMpServiceImpl implements WxMpService { | |||
return execute(new SimplePostRequestExecutor(), url, postData); | |||
} | |||
/** | |||
/** | |||
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | |||
* @param executor | |||
* @param uri | |||
@@ -448,6 +450,33 @@ public class WxMpServiceImpl implements WxMpService { | |||
* @throws WxErrorException | |||
*/ | |||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
int retryTimes = 0; | |||
do { | |||
try { | |||
return executeInternal(executor, uri, data); | |||
} catch (WxErrorException e) { | |||
WxError error = e.getError(); | |||
/** | |||
* -1 系统繁忙, 1000ms后重试 | |||
*/ | |||
if (error.getErrorCode() == -1) { | |||
int sleepMillis = retrySleepMillis * (1 << retryTimes); | |||
try { | |||
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试(第" + (retryTimes + 1) + "次)"); | |||
Thread.sleep(sleepMillis); | |||
} catch (InterruptedException e1) { | |||
throw new RuntimeException(e1); | |||
} | |||
} else { | |||
throw e; | |||
} | |||
} | |||
} while(++retryTimes < maxRetryTimes); | |||
throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
} | |||
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
String accessToken = getAccessToken(false); | |||
String uriWithAccessToken = uri; | |||
@@ -467,27 +496,6 @@ public class WxMpServiceImpl implements WxMpService { | |||
wxMpConfigStorage.expireAccessToken(); | |||
return execute(executor, uri, data); | |||
} | |||
/** | |||
* -1 系统繁忙, 1000ms后重试 | |||
*/ | |||
if (error.getErrorCode() == -1) { | |||
if(retryTimes.get() == null) { | |||
retryTimes.set(0); | |||
} | |||
if (retryTimes.get() > 4) { | |||
retryTimes.set(0); | |||
throw new RuntimeException("微信服务端异常,超出重试次数"); | |||
} | |||
int sleepMillis = 1000 * (1 << retryTimes.get()); | |||
try { | |||
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试"); | |||
Thread.sleep(sleepMillis); | |||
retryTimes.set(retryTimes.get() + 1); | |||
return execute(executor, uri, data); | |||
} catch (InterruptedException e1) { | |||
throw new RuntimeException(e1); | |||
} | |||
} | |||
if (error.getErrorCode() != 0) { | |||
throw new WxErrorException(error); | |||
} | |||
@@ -533,4 +541,16 @@ public class WxMpServiceImpl implements WxMpService { | |||
} | |||
} | |||
@Override | |||
public void setRetrySleepMillis(int retrySleepMillis) { | |||
this.retrySleepMillis = retrySleepMillis; | |||
} | |||
@Override | |||
public void setMaxRetryTimes(int maxRetryTimes) { | |||
this.maxRetryTimes = maxRetryTimes; | |||
} | |||
} |
@@ -0,0 +1,66 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import org.testng.annotations.DataProvider; | |||
import org.testng.annotations.Test; | |||
import java.util.concurrent.ExecutionException; | |||
import java.util.concurrent.ExecutorService; | |||
import java.util.concurrent.Executors; | |||
import java.util.concurrent.Future; | |||
@Test | |||
public class WxMpBusyRetryTest { | |||
@DataProvider(name="getService") | |||
public Object[][] getService() { | |||
WxMpService service = new WxMpServiceImpl() { | |||
@Override | |||
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
WxError error = new WxError(); | |||
error.setErrorCode(-1); | |||
throw new WxErrorException(error); | |||
} | |||
}; | |||
service.setMaxRetryTimes(3); | |||
service.setRetrySleepMillis(500); | |||
return new Object[][] { | |||
new Object[] { service } | |||
}; | |||
} | |||
@Test(dataProvider = "getService", expectedExceptions = RuntimeException.class) | |||
public void testRetry(WxMpService service) throws WxErrorException { | |||
service.execute(null, null, null); | |||
} | |||
@Test(dataProvider = "getService") | |||
public void testRetryInThreadPool(final WxMpService service) throws InterruptedException, ExecutionException { | |||
// 当线程池中的线程复用的时候,还是能保证相同的重试次数 | |||
ExecutorService executorService = Executors.newFixedThreadPool(1); | |||
Runnable runnable = new Runnable() { | |||
@Override | |||
public void run() { | |||
try { | |||
System.out.println("====================="); | |||
System.out.println(Thread.currentThread().getName() + ": testRetry"); | |||
service.execute(null, null, null); | |||
} catch (WxErrorException e) { | |||
throw new RuntimeException(e); | |||
} catch (RuntimeException e) { | |||
// OK | |||
} | |||
} | |||
}; | |||
Future<?> submit1 = executorService.submit(runnable); | |||
Future<?> submit2 = executorService.submit(runnable); | |||
submit1.get(); | |||
submit2.get(); | |||
} | |||
} |
@@ -3,6 +3,7 @@ | |||
<suite name="Weixin-java-tool-suite" verbose="1"> | |||
<test name="API_Test"> | |||
<classes> | |||
<class name="me.chanjar.weixin.mp.api.WxMpBusyRetryTest" /> | |||
<class name="me.chanjar.weixin.mp.api.WxMpBaseAPITest" /> | |||
<class name="me.chanjar.weixin.mp.api.WxMpCustomMessageAPITest" /> | |||
<class name="me.chanjar.weixin.mp.api.WxMpMenuAPITest" /> | |||