🎨 #571 微信小程序接口请求增加多种http客户端

This commit is contained in:
Mario Luo 2020-06-01 22:35:59 +08:00 committed by GitHub
parent 6c3d090ebd
commit 1c60839890
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 653 additions and 426 deletions

View File

@ -23,7 +23,7 @@
wx.miniapp.config-storage.redis.host = 127.0.0.1
wx.miniapp.config-storage.redis.port = 6379
# http客户端配置
wx.miniapp.config-storage.http-client-type=HttpClient # http客户端类型: HttpClient(默认)
wx.miniapp.config-storage.http-client-type=HttpClient # http客户端类型: HttpClient(默认), OkHttp, JoddHttp
wx.miniapp.config-storage.http-proxy-host=
wx.miniapp.config-storage.http-proxy-port=
wx.miniapp.config-storage.http-proxy-username=

View File

@ -1,11 +1,14 @@
package com.binarywang.spring.starter.wxjava.miniapp.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceHttpClientImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceJoddHttpImpl;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceOkHttpImpl;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl;
import com.binarywang.spring.starter.wxjava.miniapp.enums.StorageType;
import com.binarywang.spring.starter.wxjava.miniapp.enums.HttpClientType;
import com.binarywang.spring.starter.wxjava.miniapp.properties.ConfigStorage;
import com.binarywang.spring.starter.wxjava.miniapp.properties.RedisProperties;
import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties;
@ -49,9 +52,19 @@ public class WxMaAutoConfiguration {
@Bean
@ConditionalOnMissingBean(WxMaService.class)
public WxMaService service(WxMaConfig wxMaConfig) {
final WxMaServiceImpl service = new WxMaServiceImpl();
service.setWxMaConfig(wxMaConfig);
return service;
HttpClientType httpClientType = wxMaProperties.getConfigStorage().getHttpClientType();
WxMaService wxMaService;
if (httpClientType == HttpClientType.OkHttp) {
wxMaService = new WxMaServiceOkHttpImpl();
} else if (httpClientType == HttpClientType.JoddHttp) {
wxMaService = new WxMaServiceJoddHttpImpl();
} else if (httpClientType == HttpClientType.HttpClient) {
wxMaService = new WxMaServiceHttpClientImpl();
} else {
wxMaService = new WxMaServiceImpl();
}
wxMaService.setWxMaConfig(wxMaConfig);
return wxMaService;
}
@Bean

View File

@ -10,5 +10,13 @@ public enum HttpClientType {
/**
* HttpClient.
*/
HttpClient
HttpClient,
/**
* OkHttp.
*/
OkHttp,
/**
* JoddHttp.
*/
JoddHttp,
}

View File

@ -0,0 +1,397 @@
package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.*;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.WxType;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.DataUtils;
import me.chanjar.weixin.common.util.crypto.SHA1;
import me.chanjar.weixin.common.util.http.RequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*;
/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* @see #doGetAccessTokenRequest
*/
@Slf4j
public abstract class BaseWxMaServiceImpl<H, P> implements WxMaService, RequestHttp<H, P> {
private static final JsonParser JSON_PARSER = new JsonParser();
private WxMaConfig wxMaConfig;
private final WxMaMsgService kefuService = new WxMaMsgServiceImpl(this);
private final WxMaMediaService materialService = new WxMaMediaServiceImpl(this);
private final WxMaUserService userService = new WxMaUserServiceImpl(this);
private final WxMaQrcodeService qrCodeService = new WxMaQrcodeServiceImpl(this);
private final WxMaTemplateService templateService = new WxMaTemplateServiceImpl(this);
private final WxMaAnalysisService analysisService = new WxMaAnalysisServiceImpl(this);
private final WxMaCodeService codeService = new WxMaCodeServiceImpl(this);
private final WxMaSettingService settingService = new WxMaSettingServiceImpl(this);
private final WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this);
private final WxMaShareService shareService = new WxMaShareServiceImpl(this);
private final WxMaRunService runService = new WxMaRunServiceImpl(this);
private final WxMaSecCheckService secCheckService = new WxMaSecCheckServiceImpl(this);
private final WxMaPluginService pluginService = new WxMaPluginServiceImpl(this);
private final WxMaExpressService expressService = new WxMaExpressServiceImpl(this);
private final WxMaSubscribeService subscribeService = new WxMaSubscribeServiceImpl(this);
private final WxMaCloudService cloudService = new WxMaCloudServiceImpl(this);
private final WxMaLiveService liveService = new WxMaLiveServiceImpl(this);
private int retrySleepMillis = 1000;
private int maxRetryTimes = 5;
protected static final Gson GSON = new Gson();
@Override
public RequestHttp getRequestHttp() {
return this;
}
@Override
public String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo)
throws WxErrorException {
Map<String, String> params = new HashMap<>(8);
params.put("openid", openid);
if (StringUtils.isNotEmpty(transactionId)) {
params.put("transaction_id", transactionId);
}
if (StringUtils.isNotEmpty(mchId)) {
params.put("mch_id", mchId);
}
if (StringUtils.isNotEmpty(outTradeNo)) {
params.put("out_trade_no", outTradeNo);
}
String responseContent = this.get(GET_PAID_UNION_ID_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
WxError error = WxError.fromJson(responseContent, WxType.MiniApp);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return JSON_PARSER.parse(responseContent).getAsJsonObject().get("unionid").getAsString();
}
@Override
public WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException {
final WxMaConfig config = getWxMaConfig();
Map<String, String> params = new HashMap<>(8);
params.put("appid", config.getAppid());
params.put("secret", config.getSecret());
params.put("js_code", jsCode);
params.put("grant_type", "authorization_code");
String result = get(JSCODE_TO_SESSION_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
return WxMaJscode2SessionResult.fromJson(result);
}
@Override
public void setDynamicData(int lifespan, String type, int scene, String data) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("lifespan", lifespan);
jsonObject.addProperty("query", WxGsonBuilder.create().toJson(ImmutableMap.of("type", type)));
jsonObject.addProperty("data", data);
jsonObject.addProperty("scene", scene);
this.post(SET_DYNAMIC_DATA_URL, jsonObject.toString());
}
@Override
public boolean checkSignature(String timestamp, String nonce, String signature) {
try {
return SHA1.gen(this.getWxMaConfig().getToken(), timestamp, nonce).equals(signature);
} catch (Exception e) {
log.error("Checking signature failed, and the reason is :" + e.getMessage());
return false;
}
}
@Override
public String getAccessToken() throws WxErrorException {
return getAccessToken(false);
}
@Override
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
if (!forceRefresh && !this.getWxMaConfig().isAccessTokenExpired()) {
return this.getWxMaConfig().getAccessToken();
}
Lock lock = this.getWxMaConfig().getAccessTokenLock();
boolean locked = false;
try {
do {
locked = lock.tryLock(100, TimeUnit.MILLISECONDS);
if (!forceRefresh && !this.getWxMaConfig().isAccessTokenExpired()) {
return this.getWxMaConfig().getAccessToken();
}
} while (!locked);
String response = doGetAccessTokenRequest();
return extractAccessToken(response);
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
} finally {
if (locked) {
lock.unlock();
}
}
}
/**
* 通过网络请求获取AccessToken
*
* @return
* @throws IOException
*/
protected abstract String doGetAccessTokenRequest() throws IOException;
@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);
}
@Override
public String post(String url, Object obj) throws WxErrorException {
return this.execute(SimplePostRequestExecutor.create(this), url, WxGsonBuilder.create().toJson(obj));
}
/**
* 向微信端发送请求在这里执行的策略是当发生access_token过期时才去刷新然后重新执行请求而不是全局定时请求
*/
@Override
public <T, E> T execute(RequestExecutor<T, E> 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) {
log.warn("重试达到最大次数【{}】", maxRetryTimes);
//最后一次重试失败后直接抛出异常不再等待
throw new WxErrorException(WxError.builder()
.errorCode(e.getError().getErrorCode())
.errorMsg("微信服务端异常,超出重试次数!")
.build());
}
WxError error = e.getError();
// -1 系统繁忙, 1000ms后重试
if (error.getErrorCode() == -1) {
int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
try {
log.warn("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
Thread.sleep(sleepMillis);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
}
} else {
throw e;
}
}
} while (retryTimes++ < this.maxRetryTimes);
log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
throw new RuntimeException("微信服务端异常,超出重试次数");
}
private <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
E dataForLog = DataUtils.handleDataWithSecret(data);
if (uri.contains("access_token=")) {
throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
}
String accessToken = getAccessToken(false);
String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken;
try {
T result = executor.execute(uriWithAccessToken, data, WxType.MiniApp);
log.debug("\n【请求地址】: {}\n【请求参数】{}\n【响应数据】{}", uriWithAccessToken, dataForLog, result);
return result;
} catch (WxErrorException e) {
WxError error = e.getError();
/*
* 发生以下情况时尝试刷新access_token
*/
if (error.getErrorCode() == ERR_40001
|| error.getErrorCode() == ERR_42001
|| error.getErrorCode() == ERR_40014) {
// 强制设置WxMaConfig的access token过期了这样在下一次请求里就会刷新access token
Lock lock = this.getWxMaConfig().getAccessTokenLock();
lock.lock();
try {
if (StringUtils.equals(this.getWxMaConfig().getAccessToken(), accessToken)) {
this.getWxMaConfig().expireAccessToken();
}
} catch (Exception ex) {
this.getWxMaConfig().expireAccessToken();
} finally {
lock.unlock();
}
if (this.getWxMaConfig().autoRefreshToken()) {
return this.execute(executor, uri, data);
}
}
if (error.getErrorCode() != 0) {
log.error("\n【请求地址】: {}\n【请求参数】{}\n【错误信息】{}", uriWithAccessToken, dataForLog, error);
throw new WxErrorException(error, e);
}
return null;
} catch (IOException e) {
log.error("\n【请求地址】: {}\n【请求参数】{}\n【异常信息】{}", uriWithAccessToken, dataForLog, e.getMessage());
throw new RuntimeException(e);
}
}
/**
* 设置当前的AccessToken
*
* @param resultContent
* @return
* @throws WxErrorException
*/
protected String extractAccessToken(String resultContent) throws WxErrorException {
WxMaConfig config = this.getWxMaConfig();
WxError error = WxError.fromJson(resultContent, WxType.MiniApp);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
config.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
return accessToken.getAccessToken();
}
@Override
public WxMaConfig getWxMaConfig() {
return this.wxMaConfig;
}
@Override
public void setWxMaConfig(WxMaConfig wxConfigProvider) {
this.wxMaConfig = wxConfigProvider;
this.initHttp();
}
@Override
public void setRetrySleepMillis(int retrySleepMillis) {
this.retrySleepMillis = retrySleepMillis;
}
@Override
public void setMaxRetryTimes(int maxRetryTimes) {
this.maxRetryTimes = maxRetryTimes;
}
@Override
public WxMaMsgService getMsgService() {
return this.kefuService;
}
@Override
public WxMaMediaService getMediaService() {
return this.materialService;
}
@Override
public WxMaUserService getUserService() {
return this.userService;
}
@Override
public WxMaQrcodeService getQrcodeService() {
return this.qrCodeService;
}
@Override
public WxMaTemplateService getTemplateService() {
return this.templateService;
}
@Override
public WxMaSubscribeService getSubscribeService() {
return this.subscribeService;
}
@Override
public WxMaAnalysisService getAnalysisService() {
return this.analysisService;
}
@Override
public WxMaCodeService getCodeService() {
return this.codeService;
}
@Override
public WxMaJsapiService getJsapiService() {
return this.jsapiService;
}
@Override
public WxMaSettingService getSettingService() {
return this.settingService;
}
@Override
public WxMaShareService getShareService() {
return this.shareService;
}
@Override
public WxMaRunService getRunService() {
return this.runService;
}
@Override
public WxMaSecCheckService getSecCheckService() {
return this.secCheckService;
}
@Override
public WxMaPluginService getPluginService() {
return this.pluginService;
}
@Override
public WxMaExpressService getExpressService() {
return this.expressService;
}
@Override
public WxMaCloudService getCloudService() {
return this.cloudService;
}
@Override
public WxMaLiveService getLiveService() {
return this.liveService;
}
}

View File

@ -0,0 +1,88 @@
package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.util.http.HttpType;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Slf4j
public class WxMaServiceHttpClientImpl extends BaseWxMaServiceImpl {
private CloseableHttpClient httpClient;
private HttpHost httpProxy;
@Override
public void initHttp() {
WxMaConfig configStorage = this.getWxMaConfig();
ApacheHttpClientBuilder apacheHttpClientBuilder = configStorage.getApacheHttpClientBuilder();
if (null == apacheHttpClientBuilder) {
apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get();
}
apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
.httpProxyPort(configStorage.getHttpProxyPort())
.httpProxyUsername(configStorage.getHttpProxyUsername())
.httpProxyPassword(configStorage.getHttpProxyPassword());
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
}
this.httpClient = apacheHttpClientBuilder.build();
}
@Override
public CloseableHttpClient getRequestHttpClient() {
return httpClient;
}
@Override
public HttpHost getRequestHttpProxy() {
return httpProxy;
}
@Override
public HttpType getRequestType() {
return HttpType.APACHE_HTTP;
}
@Override
protected String doGetAccessTokenRequest() throws IOException {
String url = String.format(WxMaService.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
HttpGet httpGet = null;
CloseableHttpResponse response = null;
try {
httpGet = new HttpGet(url);
if (this.getRequestHttpProxy() != null) {
RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
httpGet.setConfig(config);
}
response = getRequestHttpClient().execute(httpGet);
return new BasicResponseHandler().handleResponse(response);
} finally {
if (httpGet != null) {
httpGet.releaseConnection();
}
if (response != null) {
try {
response.close();
} catch (IOException e) {
}
}
}
}
}

View File

@ -1,428 +1,11 @@
package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.*;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.WxType;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.error.WxError;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.util.DataUtils;
import me.chanjar.weixin.common.util.crypto.SHA1;
import me.chanjar.weixin.common.util.http.*;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*;
/**
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
@Slf4j
public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpClient, HttpHost> {
private static final JsonParser JSON_PARSER = new JsonParser();
private CloseableHttpClient httpClient;
private HttpHost httpProxy;
private WxMaConfig wxMaConfig;
public class WxMaServiceImpl extends WxMaServiceHttpClientImpl {
private final WxMaMsgService kefuService = new WxMaMsgServiceImpl(this);
private final WxMaMediaService materialService = new WxMaMediaServiceImpl(this);
private final WxMaUserService userService = new WxMaUserServiceImpl(this);
private final WxMaQrcodeService qrCodeService = new WxMaQrcodeServiceImpl(this);
private final WxMaTemplateService templateService = new WxMaTemplateServiceImpl(this);
private final WxMaAnalysisService analysisService = new WxMaAnalysisServiceImpl(this);
private final WxMaCodeService codeService = new WxMaCodeServiceImpl(this);
private final WxMaSettingService settingService = new WxMaSettingServiceImpl(this);
private final WxMaJsapiService jsapiService = new WxMaJsapiServiceImpl(this);
private final WxMaShareService shareService = new WxMaShareServiceImpl(this);
private final WxMaRunService runService = new WxMaRunServiceImpl(this);
private final WxMaSecCheckService secCheckService = new WxMaSecCheckServiceImpl(this);
private final WxMaPluginService pluginService = new WxMaPluginServiceImpl(this);
private final WxMaExpressService expressService = new WxMaExpressServiceImpl(this);
private final WxMaSubscribeService subscribeService = new WxMaSubscribeServiceImpl(this);
private final WxMaCloudService cloudService = new WxMaCloudServiceImpl(this);
private final WxMaLiveService liveService = new WxMaLiveServiceImpl(this);
private int retrySleepMillis = 1000;
private int maxRetryTimes = 5;
protected static final Gson GSON = new Gson();
@Override
public CloseableHttpClient getRequestHttpClient() {
return httpClient;
}
@Override
public HttpHost getRequestHttpProxy() {
return httpProxy;
}
@Override
public HttpType getRequestType() {
return HttpType.APACHE_HTTP;
}
@Override
public void initHttp() {
WxMaConfig configStorage = this.getWxMaConfig();
ApacheHttpClientBuilder apacheHttpClientBuilder = configStorage.getApacheHttpClientBuilder();
if (null == apacheHttpClientBuilder) {
apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get();
}
apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost())
.httpProxyPort(configStorage.getHttpProxyPort())
.httpProxyUsername(configStorage.getHttpProxyUsername())
.httpProxyPassword(configStorage.getHttpProxyPassword());
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort());
}
this.httpClient = apacheHttpClientBuilder.build();
}
@Override
public RequestHttp getRequestHttp() {
return this;
}
@Override
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
if (!this.getWxMaConfig().isAccessTokenExpired() && !forceRefresh) {
return this.getWxMaConfig().getAccessToken();
}
Lock lock = this.getWxMaConfig().getAccessTokenLock();
lock.lock();
try {
if (!this.getWxMaConfig().isAccessTokenExpired() && !forceRefresh) {
return this.getWxMaConfig().getAccessToken();
}
String url = String.format(WxMaService.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(),
this.getWxMaConfig().getSecret());
try {
HttpGet httpGet = new HttpGet(url);
if (this.getRequestHttpProxy() != null) {
RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build();
httpGet.setConfig(config);
}
try (CloseableHttpResponse response = getRequestHttpClient().execute(httpGet)) {
String resultContent = new BasicResponseHandler().handleResponse(response);
WxError error = WxError.fromJson(resultContent, WxType.MiniApp);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.getWxMaConfig().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
return this.getWxMaConfig().getAccessToken();
} finally {
httpGet.releaseConnection();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
} finally {
lock.unlock();
}
}
@Override
public String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo)
throws WxErrorException {
Map<String, String> params = new HashMap<>(8);
params.put("openid", openid);
if (StringUtils.isNotEmpty(transactionId)) {
params.put("transaction_id", transactionId);
}
if (StringUtils.isNotEmpty(mchId)) {
params.put("mch_id", mchId);
}
if (StringUtils.isNotEmpty(outTradeNo)) {
params.put("out_trade_no", outTradeNo);
}
String responseContent = this.get(GET_PAID_UNION_ID_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
WxError error = WxError.fromJson(responseContent, WxType.MiniApp);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return JSON_PARSER.parse(responseContent).getAsJsonObject().get("unionid").getAsString();
}
@Override
public WxMaJscode2SessionResult jsCode2SessionInfo(String jsCode) throws WxErrorException {
final WxMaConfig config = getWxMaConfig();
Map<String, String> params = new HashMap<>(8);
params.put("appid", config.getAppid());
params.put("secret", config.getSecret());
params.put("js_code", jsCode);
params.put("grant_type", "authorization_code");
String result = get(JSCODE_TO_SESSION_URL, Joiner.on("&").withKeyValueSeparator("=").join(params));
return WxMaJscode2SessionResult.fromJson(result);
}
@Override
public void setDynamicData(int lifespan, String type, int scene, String data) throws WxErrorException {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("lifespan", lifespan);
jsonObject.addProperty("query", WxGsonBuilder.create().toJson(ImmutableMap.of("type", type)));
jsonObject.addProperty("data", data);
jsonObject.addProperty("scene", scene);
this.post(SET_DYNAMIC_DATA_URL, jsonObject.toString());
}
@Override
public boolean checkSignature(String timestamp, String nonce, String signature) {
try {
return SHA1.gen(this.getWxMaConfig().getToken(), timestamp, nonce).equals(signature);
} catch (Exception e) {
log.error("Checking signature failed, and the reason is :" + e.getMessage());
return false;
}
}
@Override
public String getAccessToken() throws WxErrorException {
return getAccessToken(false);
}
@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);
}
@Override
public String post(String url, Object obj) throws WxErrorException {
return this.execute(SimplePostRequestExecutor.create(this), url, WxGsonBuilder.create().toJson(obj));
}
/**
* 向微信端发送请求在这里执行的策略是当发生access_token过期时才去刷新然后重新执行请求而不是全局定时请求
*/
@Override
public <T, E> T execute(RequestExecutor<T, E> 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) {
log.warn("重试达到最大次数【{}】", maxRetryTimes);
//最后一次重试失败后直接抛出异常不再等待
throw new WxErrorException(WxError.builder()
.errorCode(e.getError().getErrorCode())
.errorMsg("微信服务端异常,超出重试次数!")
.build());
}
WxError error = e.getError();
// -1 系统繁忙, 1000ms后重试
if (error.getErrorCode() == -1) {
int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
try {
log.warn("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
Thread.sleep(sleepMillis);
} catch (InterruptedException e1) {
Thread.currentThread().interrupt();
}
} else {
throw e;
}
}
} while (retryTimes++ < this.maxRetryTimes);
log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
throw new RuntimeException("微信服务端异常,超出重试次数");
}
private <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
E dataForLog = DataUtils.handleDataWithSecret(data);
if (uri.contains("access_token=")) {
throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
}
String accessToken = getAccessToken(false);
String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken;
try {
T result = executor.execute(uriWithAccessToken, data, WxType.MiniApp);
log.debug("\n【请求地址】: {}\n【请求参数】{}\n【响应数据】{}", uriWithAccessToken, dataForLog, result);
return result;
} catch (WxErrorException e) {
WxError error = e.getError();
/*
* 发生以下情况时尝试刷新access_token
*/
if (error.getErrorCode() == ERR_40001
|| error.getErrorCode() == ERR_42001
|| error.getErrorCode() == ERR_40014) {
// 强制设置WxMaConfig的access token过期了这样在下一次请求里就会刷新access token
Lock lock = this.getWxMaConfig().getAccessTokenLock();
lock.lock();
try {
if (StringUtils.equals(this.getWxMaConfig().getAccessToken(), accessToken)) {
this.getWxMaConfig().expireAccessToken();
}
} catch (Exception ex) {
this.getWxMaConfig().expireAccessToken();
} finally {
lock.unlock();
}
if (this.getWxMaConfig().autoRefreshToken()) {
return this.execute(executor, uri, data);
}
}
if (error.getErrorCode() != 0) {
log.error("\n【请求地址】: {}\n【请求参数】{}\n【错误信息】{}", uriWithAccessToken, dataForLog, error);
throw new WxErrorException(error, e);
}
return null;
} catch (IOException e) {
log.error("\n【请求地址】: {}\n【请求参数】{}\n【异常信息】{}", uriWithAccessToken, dataForLog, e.getMessage());
throw new RuntimeException(e);
}
}
@Override
public WxMaConfig getWxMaConfig() {
return this.wxMaConfig;
}
@Override
public void setWxMaConfig(WxMaConfig wxConfigProvider) {
this.wxMaConfig = wxConfigProvider;
this.initHttp();
}
@Override
public void setRetrySleepMillis(int retrySleepMillis) {
this.retrySleepMillis = retrySleepMillis;
}
@Override
public void setMaxRetryTimes(int maxRetryTimes) {
this.maxRetryTimes = maxRetryTimes;
}
@Override
public WxMaMsgService getMsgService() {
return this.kefuService;
}
@Override
public WxMaMediaService getMediaService() {
return this.materialService;
}
@Override
public WxMaUserService getUserService() {
return this.userService;
}
@Override
public WxMaQrcodeService getQrcodeService() {
return this.qrCodeService;
}
@Override
public WxMaTemplateService getTemplateService() {
return this.templateService;
}
@Override
public WxMaSubscribeService getSubscribeService() {
return this.subscribeService;
}
@Override
public WxMaAnalysisService getAnalysisService() {
return this.analysisService;
}
@Override
public WxMaCodeService getCodeService() {
return this.codeService;
}
@Override
public WxMaJsapiService getJsapiService() {
return this.jsapiService;
}
@Override
public WxMaSettingService getSettingService() {
return this.settingService;
}
@Override
public WxMaShareService getShareService() {
return this.shareService;
}
@Override
public WxMaRunService getRunService() {
return this.runService;
}
@Override
public WxMaSecCheckService getSecCheckService() {
return this.secCheckService;
}
@Override
public WxMaPluginService getPluginService() {
return this.pluginService;
}
@Override
public WxMaExpressService getExpressService() {
return this.expressService;
}
@Override
public WxMaCloudService getCloudService() {
return this.cloudService;
}
@Override
public WxMaLiveService getLiveService() {
return this.liveService;
}
}

View File

@ -0,0 +1,59 @@
package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import jodd.http.HttpConnectionProvider;
import jodd.http.HttpRequest;
import jodd.http.ProxyInfo;
import jodd.http.net.SocketHttpConnectionProvider;
import me.chanjar.weixin.common.util.http.HttpType;
import java.io.IOException;
/**
* jodd-http方式实现.
*
* @author someone
*/
public class WxMaServiceJoddHttpImpl extends BaseWxMaServiceImpl<HttpConnectionProvider, ProxyInfo> {
private HttpConnectionProvider httpClient;
private ProxyInfo httpProxy;
@Override
public void initHttp() {
WxMaConfig configStorage = this.getWxMaConfig();
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
this.httpProxy = new ProxyInfo(ProxyInfo.ProxyType.HTTP, configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword());
}
this.httpClient = new SocketHttpConnectionProvider();
}
@Override
public HttpConnectionProvider getRequestHttpClient() {
return httpClient;
}
@Override
public ProxyInfo getRequestHttpProxy() {
return httpProxy;
}
@Override
public HttpType getRequestType() {
return HttpType.JODD_HTTP;
}
@Override
protected String doGetAccessTokenRequest() throws IOException {
String url = String.format(WxMaService.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
HttpRequest request = HttpRequest.get(url);
if (this.getRequestHttpProxy() != null) {
SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider();
provider.useProxy(getRequestHttpProxy());
request.withConnectionProvider(provider);
}
return request.send().bodyText();
}
}

View File

@ -0,0 +1,72 @@
package cn.binarywang.wx.miniapp.api.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import me.chanjar.weixin.common.util.http.HttpType;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
import okhttp3.*;
import java.io.IOException;
import java.util.Objects;
/**
* okhttp实现.
*/
public class WxMaServiceOkHttpImpl extends BaseWxMaServiceImpl<OkHttpClient, OkHttpProxyInfo> {
private OkHttpClient httpClient;
private OkHttpProxyInfo httpProxy;
@Override
public void initHttp() {
WxMaConfig wxMpConfigStorage = this.getWxMaConfig();
//设置代理
if (wxMpConfigStorage.getHttpProxyHost() != null && wxMpConfigStorage.getHttpProxyPort() > 0) {
httpProxy = OkHttpProxyInfo.httpProxy(wxMpConfigStorage.getHttpProxyHost(),
wxMpConfigStorage.getHttpProxyPort(),
wxMpConfigStorage.getHttpProxyUsername(),
wxMpConfigStorage.getHttpProxyPassword());
}
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
if (httpProxy != null) {
clientBuilder.proxy(getRequestHttpProxy().getProxy());
//设置授权
clientBuilder.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
});
}
httpClient = clientBuilder.build();
}
@Override
public OkHttpClient getRequestHttpClient() {
return httpClient;
}
@Override
public OkHttpProxyInfo getRequestHttpProxy() {
return httpProxy;
}
@Override
public HttpType getRequestType() {
return HttpType.OK_HTTP;
}
@Override
protected String doGetAccessTokenRequest() throws IOException {
String url = String.format(WxMaService.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(), this.getWxMaConfig().getSecret());
Request request = new Request.Builder().url(url).get().build();
try (Response response = getRequestHttpClient().newCall(request).execute()) {
return Objects.requireNonNull(response.body()).string();
}
}
}

View File

@ -1,5 +1,6 @@
package me.chanjar.weixin.open.api.impl;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
@ -196,6 +197,7 @@ public class WxOpenMaServiceImpl extends WxMaServiceImpl implements WxOpenMaServ
/**
* 解除绑定小程序体验者
*
* @param userstr 人员对应的唯一字符串 可通过获取已绑定的体验者列表获取人员对应的字符串
* @return
* @throws WxErrorException
@ -306,7 +308,8 @@ public class WxOpenMaServiceImpl extends WxMaServiceImpl implements WxOpenMaServ
public File getTestQrcode(String pagePath, Map<String, String> params) throws WxErrorException {
WxMaQrcodeParam qrcodeParam = WxMaQrcodeParam.create(pagePath);
qrcodeParam.addPageParam(params);
return execute(MaQrCodeRequestExecutor.create(getRequestHttp()), API_TEST_QRCODE, qrcodeParam);
WxMaService wxMaService = this;
return wxMaService.execute(MaQrCodeRequestExecutor.create(getRequestHttp()), API_TEST_QRCODE, qrcodeParam);
}
/**
@ -547,7 +550,6 @@ public class WxOpenMaServiceImpl extends WxMaServiceImpl implements WxOpenMaServ
}
/**
* 加急审核申请
* 有加急次数的第三方可以通过该接口对已经提审的小程序进行加急操作加急后的小程序预计2-12小时内审完
@ -564,6 +566,7 @@ public class WxOpenMaServiceImpl extends WxMaServiceImpl implements WxOpenMaServ
/**
* (1)增加或修改二维码规则
*
* @param wxQrcodeJumpRule
* @return
* @throws WxErrorException
@ -576,6 +579,7 @@ public class WxOpenMaServiceImpl extends WxMaServiceImpl implements WxOpenMaServ
/**
* (2)获取已设置的二维码规则
*
* @return
* @throws WxErrorException
*/
@ -587,6 +591,7 @@ public class WxOpenMaServiceImpl extends WxMaServiceImpl implements WxOpenMaServ
/**
* (3)获取校验文件名称及内容
*
* @return
* @throws WxErrorException
*/
@ -598,6 +603,7 @@ public class WxOpenMaServiceImpl extends WxMaServiceImpl implements WxOpenMaServ
/**
* (4)删除已设置的二维码规则
*
* @param prefix
* @return
* @throws WxErrorException
@ -612,6 +618,7 @@ public class WxOpenMaServiceImpl extends WxMaServiceImpl implements WxOpenMaServ
/**
* (5)发布已设置的二维码规则
*
* @param prefix
* @return
* @throws WxErrorException