diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md
index 3d4fae241..82f6bdd8b 100644
--- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md
+++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md
@@ -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=
diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java
index ab843ab5b..0aad25f14 100644
--- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java
+++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java
@@ -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
diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java
index 18ca00dd7..52a53debd 100644
--- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java
+++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/enums/HttpClientType.java
@@ -10,5 +10,13 @@ public enum HttpClientType {
/**
* HttpClient.
*/
- HttpClient
+ HttpClient,
+ /**
+ * OkHttp.
+ */
+ OkHttp,
+ /**
+ * JoddHttp.
+ */
+ JoddHttp,
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
new file mode 100644
index 000000000..ee891ecb7
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
@@ -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 Binary Wang
+ * @see #doGetAccessTokenRequest
+ */
+@Slf4j
+public abstract class BaseWxMaServiceImpl implements WxMaService, RequestHttp {
+ 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 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 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 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) {
+ 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 executeInternal(RequestExecutor 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;
+ }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java
new file mode 100644
index 000000000..c69772a5d
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceHttpClientImpl.java
@@ -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 Binary Wang
+ */
+@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) {
+ }
+ }
+ }
+ }
+
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
index dd134d925..16478f841 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
@@ -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 Binary Wang
*/
@Slf4j
-public class WxMaServiceImpl implements WxMaService, RequestHttp {
- 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 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 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 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) {
- 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 executeInternal(RequestExecutor 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;
- }
}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java
new file mode 100644
index 000000000..984d0d3fe
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceJoddHttpImpl.java
@@ -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 {
+ 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();
+ }
+
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
new file mode 100644
index 000000000..a36444d97
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceOkHttpImpl.java
@@ -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 {
+
+ 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();
+ }
+ }
+}
diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
index 6cbf584c3..af3f37b79 100644
--- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
+++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java
@@ -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 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