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