execute用来封装发送http请求。并不是并发线程的控制器。
1.接口定义 WxMpService
/**
* 初始化http请求对象.
*/
void initHttp();
/**
* @return RequestHttp对象
*/
RequestHttp getRequestHttp();
/**
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求.
*/
String get(String url, String queryParam) throws WxErrorException;
/**
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求.
*/
String post(String url, String postData) throws WxErrorException;
/**
* Service没有实现某个API的时候,可以用这个,
* 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
* 可以参考,{@link MediaUploadRequestExecutor}的实现方法
*/
T execute(RequestExecutor executor, String uri, E data) throws WxErrorException;
实现类 --- BaseWxMpServiceImpl
@Override
public String get(String url, String queryParam) throws WxErrorException {
return execute(SimpleGetRequestExecutor.create(this), url, queryParam);
}
@Override
public String post(String url, String postData) throws WxErrorException {
return execute(SimplePostRequestExecutor.create(this), url, postData);
}
2.具体实现:BaseWxMpServiceImpl---重试控制:-1 系统繁忙, 1000ms后重试
private int maxRetryTimes = 5; 重试次数
private int retrySleepMillis = 1000; 重试时间
/**
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求.
* << : 左移运算符,num << 1,相当于num乘以2
>> : 右移运算符,num >> 1,相当于num除以2
*/
@Override
public T execute(RequestExecutor executor, String uri, E data) throws WxErrorException {
int retryTimes = 0;
do {
try {
return this.executeInternal(executor, uri, data);
} catch (WxErrorException e) {
if (retryTimes + 1 > this.maxRetryTimes) {
this.log.warn("重试达到最大次数【{}】", maxRetryTimes);
//最后一次重试失败后,直接抛出异常,不再等待
throw new RuntimeException("微信服务端异常,超出重试次数");
}
WxError error = e.getError();
// -1 系统繁忙, 1000ms后重试
if (error.getErrorCode() == -1) {
int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
try {
this.log.warn("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
Thread.sleep(sleepMillis);
} catch (InterruptedException e1) {
throw new RuntimeException(e1);
}
} else {
throw e;
}
}
} while (retryTimes++ < this.maxRetryTimes);
this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
throw new RuntimeException("微信服务端异常,超出重试次数");
}
3.具体实现---发送
public T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException {
//1.屏蔽日志中敏感信息
E dataForLog = DataUtils.handleDataWithSecret(data);
//2.请求uri参数中不允许有access_token
if (uri.contains("access_token=")) {
throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
}
//3.获取access_token
String accessToken = getAccessToken(false);
//4.uri参数后拼接access_token
String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken;
//5.调用请求执行器
try {
T result = executor.execute(uriWithAccessToken, data);
this.log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, dataForLog, result);
return result;
} catch (WxErrorException e) {
WxError error = e.getError();
/*
* 6.发生以下情况时尝试刷新access_token
* 40001 获取access_token时AppSecret错误,或者access_token无效
* 42001 access_token超时
* 40014 不合法的access_token,请开发者认真比对access_token的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口
*/
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) {
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
this.getWxMpConfigStorage().expireAccessToken();
if (this.getWxMpConfigStorage().autoRefreshToken()) {
return this.execute(executor, uri, data);
}
}
if (error.getErrorCode() != 0) {
this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uriWithAccessToken, dataForLog, error);
throw new WxErrorException(error, e);
}
return null;
} catch (IOException e) {
this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage());
throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e);
}
}
4.http请求执行器接口定义
public interface RequestExecutor {
/**
* 执行http请求.
*
* @param uri uri
* @param data 数据
* @return 响应结果
* @throws WxErrorException 自定义异常
* @throws IOException io异常
*/
T execute(String uri, E data) throws WxErrorException, IOException;
/**
* 执行http请求.
*
* @param uri uri
* @param data 数据
* @param handler http响应处理器
* @throws WxErrorException 自定义异常
* @throws IOException io异常
*/
void execute(String uri, E data, ResponseHandler handler) throws WxErrorException, IOException;
}
5.http请求执行器实现
客户端 CloseableHttpClient client
响应 CloseableHttpResponse
参数 HttpEntity --- httpPost.setEntity(entity)
请求 Request对象 HttpPost
配置 RequestConfig --- httpPost.setConfig(reqConfig)
执行请求 response = client.execute(req);
响应体 entity = response.getEntity();
public String execute(String uri, String postEntity) throws WxErrorException, IOException {
HttpPost httpPost = new HttpPost(uri);
if (requestHttp.getRequestHttpProxy() != null) {
RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
httpPost.setConfig(config);
}
if (postEntity != null) {
StringEntity entity = new StringEntity(postEntity, Consts.UTF_8);
httpPost.setEntity(entity);
}
try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) {
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
if (responseContent.isEmpty()) {
throw new WxErrorException(WxError.builder().errorCode(9999).errorMsg("无响应内容").build());
}
if (responseContent.startsWith("")) {
//xml格式输出直接返回
return responseContent;
}
//如果是响应体是xml,直接返回。如果是json,包装错误码
WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return responseContent;
} finally {
httpPost.releaseConnection();
}
}