From 2f57e4c41a601363200b905de659fb3785cf04c1 Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Thu, 13 Apr 2017 22:19:31 +0800 Subject: [PATCH 1/7] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 5f10da8a..b5533318 100644 --- a/readme.md +++ b/readme.md @@ -52,7 +52,7 @@ --------------------------------- ## Maven & Gradle 最新正式版本 -* 微信支付(暂时为测试版本): +* 微信支付: maven: ```xml From 05165179c239c1dd99e9b3cdc33f2150350e033e Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Mon, 17 Apr 2017 22:34:47 +0800 Subject: [PATCH 2/7] update version to 2.7.0-SNAPSHOT --- pom.xml | 2 +- weixin-java-common/pom.xml | 2 +- weixin-java-cp/pom.xml | 2 +- weixin-java-mp/pom.xml | 2 +- weixin-java-pay/pom.xml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 2447fd71..57ad67f0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.github.binarywang weixin-java-parent - 2.6.0 + 2.7.0-SNAPSHOT pom WeiXin Java Tools - Parent 微信公众号、企业号上级POM diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index 946c65c2..6c7366b5 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang weixin-java-parent - 2.6.0 + 2.7.0-SNAPSHOT weixin-java-common diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml index 88f83f0f..ab165436 100644 --- a/weixin-java-cp/pom.xml +++ b/weixin-java-cp/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang weixin-java-parent - 2.6.0 + 2.7.0-SNAPSHOT weixin-java-cp diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml index 3f4bf649..77d2488a 100644 --- a/weixin-java-mp/pom.xml +++ b/weixin-java-mp/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang weixin-java-parent - 2.6.0 + 2.7.0-SNAPSHOT weixin-java-mp WeiXin Java Tools - MP diff --git a/weixin-java-pay/pom.xml b/weixin-java-pay/pom.xml index d0d50455..e351ff19 100644 --- a/weixin-java-pay/pom.xml +++ b/weixin-java-pay/pom.xml @@ -5,7 +5,7 @@ weixin-java-parent com.github.binarywang - 2.6.0 + 2.7.0-SNAPSHOT 4.0.0 From 5c78a1131948936d46e88337829d86b3d4a7e3fe Mon Sep 17 00:00:00 2001 From: DDLeEHi <569198459@qq.com> Date: Tue, 18 Apr 2017 11:04:00 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=AC=AC=E4=B8=89?= =?UTF-8?q?=E6=96=B9=E5=B9=B3=E5=8F=B0SDK=E6=94=AF=E4=BB=98=20(#186)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 增加第三方平台SDK支付 增加微信SDK支付,预下单流程、参数(除开APPID为绑定APP之后发放的APPID)与公众号相同,预下单成功之后需要二次签名,格式需要严格按要求处理,用于SDK校验,代码注释中有说明 * SDK第三方平台支付,大概按照这个项目的代码格式调整了一下,格式还有不合适之处,麻烦调整下 之前我用的是treemap,忘记导包所以报错,现在用这个项目里的加密方式,用普通的hashmap也行 * SDK支付 按照要求修改过格式了 --- .../wxpay/service/impl/WxPayServiceImpl.java | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java index c26d0583..5d442df9 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java @@ -196,17 +196,38 @@ public class WxPayServiceImpl implements WxPayService { } Map payInfo = new HashMap<>(); - payInfo.put("appId", getConfig().getAppId()); - // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。 - // 但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 - payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); - payInfo.put("nonceStr", String.valueOf(System.currentTimeMillis())); - payInfo.put("package", "prepay_id=" + prepayId); - payInfo.put("signType", "MD5"); - if ("NATIVE".equals(request.getTradeType())) { + if ("NATIVE".equals(request.getTradeType())) + { payInfo.put("codeUrl", unifiedOrderResult.getCodeURL()); + }else if ("APP".equals(request.getTradeType())){ + // APP支付绑定的是微信开放平台上的账号,APPID为开放平台上绑定APP后发放的参数 + String appId = getConfig().getAppId(); + Map configMap = new HashMap<>(); + // 此map用于参与调起sdk支付的二次签名,格式全小写,timestamp只能是10位,格式固定,切勿修改 + String partnerid = getConfig().getMchId(); + configMap.put("prepayid", prepayId); + configMap.put("partnerid", partnerid); + configMap.put("package", "Sign=WXPay"); + configMap.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000)); + configMap.put("noncestr", String.valueOf(System.currentTimeMillis())); + configMap.put("appid", appId); + // 此map用于客户端与微信服务器交互 + payInfo.put("paySign", SignUtils.createSign(payInfo, this.getConfig().getMchKey())); + payInfo.put("prepayId", prepayId); + payInfo.put("partnerId", partnerid); + payInfo.put("appId", appId); + payInfo.put("packageValue", "Sign=WXPay"); + payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); + payInfo.put("nonceStr", String.valueOf(System.currentTimeMillis())); + }else if ("JSAPI".equals(request.getTradeType())){ + payInfo.put("appId", unifiedOrderResult.getAppid()); + // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 + payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); + payInfo.put("nonceStr", String.valueOf(System.currentTimeMillis())); + payInfo.put("package", "prepay_id=" + prepayId); + payInfo.put("signType", "MD5"); + payInfo.put("paySign", SignUtils.createSign(payInfo, this.getConfig().getMchKey())); } - payInfo.put("paySign", SignUtils.createSign(payInfo, this.getConfig().getMchKey())); return payInfo; } From 1efec6bc75abfeed48d23f92dfed9e70cd0257b0 Mon Sep 17 00:00:00 2001 From: Binary Wang Date: Tue, 18 Apr 2017 11:17:57 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../binarywang/wxpay/service/impl/WxPayServiceImpl.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java index 5d442df9..0e4dd28d 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java @@ -196,10 +196,9 @@ public class WxPayServiceImpl implements WxPayService { } Map payInfo = new HashMap<>(); - if ("NATIVE".equals(request.getTradeType())) - { + if ("NATIVE".equals(request.getTradeType())) { payInfo.put("codeUrl", unifiedOrderResult.getCodeURL()); - }else if ("APP".equals(request.getTradeType())){ + } else if ("APP".equals(request.getTradeType())) { // APP支付绑定的是微信开放平台上的账号,APPID为开放平台上绑定APP后发放的参数 String appId = getConfig().getAppId(); Map configMap = new HashMap<>(); @@ -219,7 +218,7 @@ public class WxPayServiceImpl implements WxPayService { payInfo.put("packageValue", "Sign=WXPay"); payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); payInfo.put("nonceStr", String.valueOf(System.currentTimeMillis())); - }else if ("JSAPI".equals(request.getTradeType())){ + } else if ("JSAPI".equals(request.getTradeType())) { payInfo.put("appId", unifiedOrderResult.getAppid()); // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 payInfo.put("timeStamp", String.valueOf(System.currentTimeMillis() / 1000)); From a8d443e13be71c791f447828ac8f31e171beed12 Mon Sep 17 00:00:00 2001 From: crskyp Date: Wed, 19 Apr 2017 11:25:50 +0800 Subject: [PATCH 5/7] =?UTF-8?q?pay=E6=A8=A1=E5=9D=97=E4=BD=BF=E7=94=A8jodd?= =?UTF-8?q?-http=E6=96=B9=E5=BC=8F=EF=BC=8C=E5=B7=B2=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=AF=81=E4=B9=A6=E6=96=B9=E5=BC=8F=20(#189)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit weixin-java-tools pay use jodd-http --- .../wxpay/service/impl/WxPayServiceImpl.java | 35 +++++-------------- .../service/impl/WxPayServiceImplTest.java | 8 +++-- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java index 0e4dd28d..1a949bc0 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImpl.java @@ -9,27 +9,19 @@ import com.github.binarywang.wxpay.util.SignUtils; import com.google.common.collect.Maps; import jodd.http.HttpRequest; import jodd.http.HttpResponse; +import jodd.http.net.SSLSocketHttpConnectionProvider; import me.chanjar.weixin.common.bean.result.WxError; import me.chanjar.weixin.common.exception.WxErrorException; import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; -import org.apache.http.Consts; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.conn.ssl.DefaultHostnameVerifier; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.net.ssl.SSLContext; import java.io.File; import java.io.UnsupportedEncodingException; import java.util.HashMap; import java.util.Map; +import javax.net.ssl.SSLContext; /** * Created by Binary Wang on 2016/7/28. @@ -403,7 +395,7 @@ public class WxPayServiceImpl implements WxPayService { } /** - * 由于暂时未找到使用jodd-http实现证书配置的办法,故而暂时使用httpclient + * ecoolper(20170418),修改为jodd-http方式 */ private String postWithKey(String url, String requestStr) throws WxErrorException { try { @@ -412,21 +404,12 @@ public class WxPayServiceImpl implements WxPayService { sslContext = this.getConfig().initSSLContext(); } - SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, - new String[]{"TLSv1"}, null, new DefaultHostnameVerifier()); - - HttpPost httpPost = new HttpPost(url); - - try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build()) { - httpPost.setEntity(new StringEntity(new String(requestStr.getBytes(CharEncoding.UTF_8), CharEncoding.ISO_8859_1))); - try (CloseableHttpResponse response = httpclient.execute(httpPost)) { - String result = EntityUtils.toString(response.getEntity(), Consts.UTF_8); - this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", url, requestStr, result); - return result; - } - } finally { - httpPost.releaseConnection(); - } + HttpRequest request = HttpRequest.post(url).withConnectionProvider(new SSLSocketHttpConnectionProvider(sslContext)); + request.bodyText(requestStr); + HttpResponse response = request.send(); + String result = response.bodyText(); + this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", url, requestStr, result); + return result; } catch (Exception e) { this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[EXCEPTION]: {}", url, requestStr, e.getMessage()); throw new WxErrorException(WxError.newBuilder().setErrorCode(-1).setErrorMsg(e.getMessage()).build(), e); diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImplTest.java index 93b0b8b5..b59bd1eb 100644 --- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImplTest.java +++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/WxPayServiceImplTest.java @@ -1,5 +1,7 @@ package com.github.binarywang.wxpay.service.impl; +import static org.testng.Assert.*; + import com.github.binarywang.utils.qrcode.QrcodeUtils; import com.github.binarywang.wxpay.bean.request.*; import com.github.binarywang.wxpay.bean.result.*; @@ -10,15 +12,14 @@ import com.google.inject.Inject; import me.chanjar.weixin.common.exception.WxErrorException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testng.annotations.*; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; -import static org.testng.Assert.*; - /** * 测试支付相关接口 * Created by Binary Wang on 2016/7/28. @@ -264,4 +265,5 @@ public class WxPayServiceImplTest { this.logger.info(result); } + } From 885db18ebce4ff2569788f4c11ed932d89232bd3 Mon Sep 17 00:00:00 2001 From: crskyp Date: Tue, 25 Apr 2017 10:14:33 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=A3=85=E9=A5=B0?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=EF=BC=8C=E6=94=AF=E6=8C=81apache-http?= =?UTF-8?q?=E5=92=8Cjodd-http=20(#194)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 11 + settings.gradle | 4 +- weixin-java-common/pom.xml | 5 + .../http/MediaDownloadRequestExecutor.java | 120 ++- .../util/http/MediaUploadRequestExecutor.java | 71 +- .../common/util/http/RequestExecutor.java | 10 +- .../weixin/common/util/http/RequestHttp.java | 20 + .../util/http/SimpleGetRequestExecutor.java | 67 +- .../util/http/SimplePostRequestExecutor.java | 87 ++- .../{ => apache}/ApacheHttpClientBuilder.java | 2 +- .../DefaultApacheHttpClientBuilder.java | 2 +- .../InputStreamResponseHandler.java | 2 +- .../{ => apache}/Utf8ResponseHandler.java | 2 +- .../weixin/cp/api/WxCpConfigStorage.java | 2 +- .../cp/api/WxCpInMemoryConfigStorage.java | 2 +- .../weixin/cp/api/WxCpJedisConfigStorage.java | 2 +- .../me/chanjar/weixin/cp/api/WxCpService.java | 3 +- .../{ => impl/apache}/WxCpServiceImpl.java | 21 +- .../cp/api/impl/jodd/WxCpServiceImpl.java | 686 ++++++++++++++++++ .../chanjar/weixin/cp/api/ApiTestModule.java | 1 + .../weixin/cp/api/WxCpDepartAPITest.java | 1 + .../weixin/cp/api/WxCpMediaAPITest.java | 1 + .../chanjar/weixin/cp/api/WxCpTagAPITest.java | 1 + .../weixin/cp/api/WxCpUserAPITest.java | 1 + .../chanjar/weixin/cp/api/WxMenuAPITest.java | 1 + .../{ => impl/apache}/WxCpBaseAPITest.java | 4 +- .../{ => impl/apache}/WxCpBusyRetryTest.java | 7 +- .../{ => impl/apache}/WxCpMessageAPITest.java | 9 +- .../weixin/cp/demo/WxCpDemoServer.java | 2 +- weixin-java-cp/src/test/resources/testng.xml | 6 +- .../weixin/mp/api/WxMpCardService.java | 10 +- .../weixin/mp/api/WxMpConfigStorage.java | 2 +- .../mp/api/WxMpInMemoryConfigStorage.java | 2 +- .../me/chanjar/weixin/mp/api/WxMpService.java | 25 +- .../weixin/mp/api/WxMpStoreService.java | 2 +- .../mp/api/impl/WxMpCardServiceImpl.java | 37 +- .../mp/api/impl/WxMpDeviceServiceImpl.java | 2 +- .../mp/api/impl/WxMpKefuServiceImpl.java | 21 +- .../mp/api/impl/WxMpMaterialServiceImpl.java | 3 - .../mp/api/impl/WxMpUserTagServiceImpl.java | 8 +- .../impl/{ => apache}/WxMpServiceImpl.java | 30 +- .../mp/api/impl/jodd/WxMpServiceImpl.java | 516 +++++++++++++ .../http/MaterialDeleteRequestExecutor.java | 51 +- .../http/MaterialNewsInfoRequestExecutor.java | 49 +- .../http/MaterialUploadRequestExecutor.java | 65 +- .../MaterialVideoInfoRequestExecutor.java | 50 +- ...lVoiceAndImageDownloadRequestExecutor.java | 80 +- .../http/MediaImgUploadRequestExecutor.java | 52 +- .../mp/util/http/QrCodeRequestExecutor.java | 72 +- .../weixin/mp/api/WxMpBusyRetryTest.java | 7 +- .../mp/api/impl/WxMpStoreServiceImplTest.java | 2 +- .../weixin/mp/api/test/ApiTestModule.java | 2 +- .../weixin/mp/demo/WxMpDemoServer.java | 2 +- weixin-java-pay/build.gradle | 12 + 54 files changed, 2084 insertions(+), 171 deletions(-) create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java rename weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/{ => apache}/ApacheHttpClientBuilder.java (95%) rename weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/{ => apache}/DefaultApacheHttpClientBuilder.java (99%) rename weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/{ => apache}/InputStreamResponseHandler.java (94%) rename weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/{ => apache}/Utf8ResponseHandler.java (95%) rename weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/{ => impl/apache}/WxCpServiceImpl.java (97%) create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/jodd/WxCpServiceImpl.java rename weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/{ => impl/apache}/WxCpBaseAPITest.java (85%) rename weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/{ => impl/apache}/WxCpBusyRetryTest.java (90%) rename weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/{ => impl/apache}/WxCpMessageAPITest.java (93%) rename weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/{ => apache}/WxMpServiceImpl.java (95%) create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/jodd/WxMpServiceImpl.java create mode 100644 weixin-java-pay/build.gradle diff --git a/pom.xml b/pom.xml index 57ad67f0..02611543 100644 --- a/pom.xml +++ b/pom.xml @@ -110,6 +110,17 @@ + + org.jodd + jodd-http + 3.7 + + + + com.squareup.okhttp3 + okhttp + 3.7.0 + org.slf4j slf4j-api diff --git a/settings.gradle b/settings.gradle index 1c0e0214..0ff1d35b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,7 +2,9 @@ rootProject.name = 'weixin-java-parent' include ':weixin-java-common' include ':weixin-java-cp' include ':weixin-java-mp' +include ':weixin-java-pay' project(':weixin-java-common').projectDir = "$rootDir/weixin-java-common" as File project(':weixin-java-cp').projectDir = "$rootDir/weixin-java-cp" as File -project(':weixin-java-mp').projectDir = "$rootDir/weixin-java-mp" as File \ No newline at end of file +project(':weixin-java-mp').projectDir = "$rootDir/weixin-java-mp" as File +project(':weixin-java-pay').projectDir = "$rootDir/weixin-java-pay" as File diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index 6c7366b5..e666caab 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -39,6 +39,11 @@ jetty-servlet test + + org.jodd + jodd-http + 3.7 + diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaDownloadRequestExecutor.java index 57024f50..b69438fa 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaDownloadRequestExecutor.java @@ -1,8 +1,14 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.bean.result.WxError; import me.chanjar.weixin.common.exception.WxErrorException; import me.chanjar.weixin.common.util.fs.FileUtils; +import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler; +import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; import org.apache.commons.lang3.StringUtils; import org.apache.http.Header; import org.apache.http.HttpHost; @@ -12,6 +18,7 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.entity.ContentType; import org.apache.http.impl.client.CloseableHttpClient; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -21,21 +28,74 @@ import java.util.regex.Pattern; /** * 下载媒体文件请求执行器,请求的参数是String, 返回的结果是File * 视频文件不支持下载 + * * @author Daniel Qian */ public class MediaDownloadRequestExecutor implements RequestExecutor { private File tmpDirFile; - public MediaDownloadRequestExecutor() { - } - public MediaDownloadRequestExecutor(File tmpDirFile) { this.tmpDirFile = tmpDirFile; } @Override - public File execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException { + public File execute(RequestHttp requestHttp, String uri, String queryParam) throws WxErrorException, IOException { + if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) { + CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient(); + HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy(); + return executeApache(httpClient, httpProxy, uri, queryParam); + } + if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) { + HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient(); + ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy(); + return executeJodd(provider, proxyInfo, uri, queryParam); + } else { + //这里需要抛出异常,需要优化 + return null; + } + } + + private String getFileNameJodd(HttpResponse response) throws WxErrorException { + String content = response.header("Content-disposition"); + if (content == null || content.length() == 0) { + throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); + } + + Pattern p = Pattern.compile(".*filename=\"(.*)\""); + Matcher m = p.matcher(content); + if (m.matches()) { + return m.group(1); + } + throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); + } + + private String getFileNameApache(CloseableHttpResponse response) throws WxErrorException { + Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); + if(contentDispositionHeader == null || contentDispositionHeader.length == 0){ + throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); + } + + Pattern p = Pattern.compile(".*filename=\"(.*)\""); + Matcher m = p.matcher(contentDispositionHeader[0].getValue()); + if(m.matches()){ + return m.group(1); + } + throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build()); + } + + + /** + * apache-http实现方式 + * @param httpclient + * @param httpProxy + * @param uri + * @param queryParam + * @return + * @throws WxErrorException + * @throws IOException + */ + private File executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException { if (queryParam != null) { if (uri.indexOf('?') == -1) { uri += '?'; @@ -50,8 +110,8 @@ public class MediaDownloadRequestExecutor implements RequestExecutor 0) { @@ -62,7 +122,7 @@ public class MediaDownloadRequestExecutor implements RequestExecutor { @Override - public WxMediaUploadResult execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, File file) throws WxErrorException, IOException { + public WxMediaUploadResult execute(RequestHttp requestHttp, String uri, File file) throws WxErrorException, IOException { + if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) { + CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient(); + HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy(); + return executeApache(httpClient, httpProxy, uri, file); + } + if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) { + HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient(); + ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy(); + return executeJodd(provider, proxyInfo, uri, file); + } else { + //这里需要抛出异常,需要优化 + return null; + } + + + } + + /** + * apache-http实现方式 + * @param httpclient + * @param httpProxy + * @param uri + * @param file + * @return + * @throws WxErrorException + * @throws IOException + */ + private WxMediaUploadResult executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, File file) throws WxErrorException, IOException { HttpPost httpPost = new HttpPost(uri); if (httpProxy != null) { RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); @@ -32,10 +65,10 @@ public class MediaUploadRequestExecutor implements RequestExecutor { /** - * @param httpclient 传入的httpClient - * @param httpProxy http代理对象,如果没有配置代理则为空 - * @param uri uri - * @param data 数据 + * @param uri uri + * @param data 数据 * @throws WxErrorException * @throws IOException */ - T execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, E data) throws WxErrorException, IOException; + T execute(RequestHttp requestHttp, String uri, E data) throws WxErrorException, IOException; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java new file mode 100644 index 00000000..02225d46 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.common.util.http; + +/** + * Created by ecoolper on 2017/4/22. + */ +public interface RequestHttp { + + /** + * 返回httpClient + * @return + */ + Object getRequestHttpClient(); + + /** + * 返回httpProxy + * @return + */ + Object getRequestHttpProxy(); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java index ec221e28..cb414d2b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java @@ -1,7 +1,12 @@ package me.chanjar.weixin.common.util.http; +import jodd.http.HttpConnectionProvider; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; +import jodd.http.ProxyInfo; import me.chanjar.weixin.common.bean.result.WxError; import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; @@ -18,7 +23,34 @@ import java.io.IOException; public class SimpleGetRequestExecutor implements RequestExecutor { @Override - public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException { + public String execute(RequestHttp requestHttp, String uri, String queryParam) throws WxErrorException, IOException { + if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) { + CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient(); + HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy(); + return executeApache(httpClient, httpProxy, uri, queryParam); + } + if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) { + HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient(); + ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy(); + return executeJodd(provider, proxyInfo, uri, queryParam); + } else { + //这里需要抛出异常,需要优化 + return null; + } + } + + + /** + * apache-http实现方式 + * @param httpclient + * @param httpProxy + * @param uri + * @param queryParam + * @return + * @throws WxErrorException + * @throws IOException + */ + private String executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException { if (queryParam != null) { if (uri.indexOf('?') == -1) { uri += '?'; @@ -43,4 +75,37 @@ public class SimpleGetRequestExecutor implements RequestExecutor } } + + /** + * jodd-http实现方式 + * @param provider + * @param proxyInfo + * @param uri + * @param queryParam + * @return + * @throws WxErrorException + * @throws IOException + */ + private String executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, String queryParam) throws WxErrorException, IOException { + if (queryParam != null) { + if (uri.indexOf('?') == -1) { + uri += '?'; + } + uri += uri.endsWith("?") ? queryParam : '&' + queryParam; + } + + HttpRequest request = HttpRequest.get(uri); + if (proxyInfo != null) { + provider.useProxy(proxyInfo); + } + request.withConnectionProvider(provider); + HttpResponse response = request.send(); + String responseContent = response.bodyText(); + WxError error = WxError.fromJson(responseContent); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return responseContent; + } + } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java index bdea8573..f6873289 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java @@ -1,7 +1,12 @@ package me.chanjar.weixin.common.util.http; -import java.io.IOException; - +import jodd.http.HttpConnectionProvider; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; +import jodd.http.ProxyInfo; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; import org.apache.http.Consts; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; @@ -10,10 +15,10 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; -import me.chanjar.weixin.common.bean.result.WxError; -import me.chanjar.weixin.common.exception.WxErrorException; +import java.io.IOException; /** + * 用装饰模式实现 * 简单的POST请求执行器,请求的参数是String, 返回的结果也是String * * @author Daniel Qian @@ -21,7 +26,33 @@ import me.chanjar.weixin.common.exception.WxErrorException; public class SimplePostRequestExecutor implements RequestExecutor { @Override - public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String postEntity) throws WxErrorException, IOException { + public String execute(RequestHttp requestHttp, String uri, String postEntity) throws WxErrorException, IOException { + if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) { + CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient(); + HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy(); + return executeApache(httpClient, httpProxy, uri, postEntity); + } + if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) { + HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient(); + ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy(); + return executeJodd(provider, proxyInfo, uri, postEntity); + } else { + //这里需要抛出异常,需要优化 + return null; + } + } + + /** + * apache-http实现方式 + * @param httpclient + * @param httpProxy + * @param uri + * @param postEntity + * @return + * @throws WxErrorException + * @throws IOException + */ + private String executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String postEntity) throws WxErrorException, IOException { HttpPost httpPost = new HttpPost(uri); if (httpProxy != null) { RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); @@ -37,8 +68,8 @@ public class SimplePostRequestExecutor implements RequestExecutor")) { @@ -56,4 +87,46 @@ public class SimplePostRequestExecutor implements RequestExecutor")) { + //xml格式输出直接返回 + return responseContent; + } + + WxError error = WxError.fromJson(responseContent); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return responseContent; + } + + } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/ApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java similarity index 95% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/ApacheHttpClientBuilder.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java index c932445d..87a86dba 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/ApacheHttpClientBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.common.util.http; +package me.chanjar.weixin.common.util.http.apache; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.impl.client.CloseableHttpClient; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/DefaultApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java similarity index 99% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/DefaultApacheHttpClientBuilder.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java index 030fe98f..2bb1f072 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/DefaultApacheHttpClientBuilder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.common.util.http; +package me.chanjar.weixin.common.util.http.apache; import org.apache.commons.lang3.StringUtils; import org.apache.http.annotation.NotThreadSafe; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/InputStreamResponseHandler.java similarity index 94% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamResponseHandler.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/InputStreamResponseHandler.java index 0cc1562b..d4cea91d 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamResponseHandler.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/InputStreamResponseHandler.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.common.util.http; +package me.chanjar.weixin.common.util.http.apache; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/Utf8ResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java similarity index 95% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/Utf8ResponseHandler.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java index 148ef965..697d4695 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/Utf8ResponseHandler.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.common.util.http; +package me.chanjar.weixin.common.util.http.apache; import org.apache.http.Consts; import org.apache.http.HttpEntity; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java index eb1e57ca..3484db3b 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java @@ -1,7 +1,7 @@ package me.chanjar.weixin.cp.api; import me.chanjar.weixin.common.bean.WxAccessToken; -import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import java.io.File; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java index 867dd6bb..b1eeb827 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java @@ -2,7 +2,7 @@ package me.chanjar.weixin.cp.api; import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.util.ToStringUtils; -import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import java.io.File; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpJedisConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpJedisConfigStorage.java index 92513965..07f38ebb 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpJedisConfigStorage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpJedisConfigStorage.java @@ -1,7 +1,7 @@ package me.chanjar.weixin.cp.api; import me.chanjar.weixin.common.bean.WxAccessToken; -import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import redis.clients.jedis.Jedis; import redis.clients.jedis.JedisPool; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java index bd0b322f..faa01d09 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java @@ -6,6 +6,7 @@ import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.exception.WxErrorException; import me.chanjar.weixin.common.session.WxSession; import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.cp.bean.WxCpDepart; import me.chanjar.weixin.cp.bean.WxCpMessage; @@ -469,7 +470,7 @@ public interface WxCpService { *
    * Service没有实现某个API的时候,可以用这个,
    * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
-   * 可以参考,{@link me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor}的实现方法
+   * 可以参考,{@link MediaUploadRequestExecutor}的实现方法
    * 
* * @param executor 执行器 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/apache/WxCpServiceImpl.java similarity index 97% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/apache/WxCpServiceImpl.java index 0105e78d..4369868f 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/apache/WxCpServiceImpl.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.api; +package me.chanjar.weixin.cp.api.impl.apache; import com.google.gson.*; import com.google.gson.reflect.TypeToken; @@ -15,7 +15,11 @@ import me.chanjar.weixin.common.util.RandomUtils; import me.chanjar.weixin.common.util.crypto.SHA1; import me.chanjar.weixin.common.util.fs.FileUtils; 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.GsonHelper; +import me.chanjar.weixin.cp.api.WxCpConfigStorage; +import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.bean.WxCpDepart; import me.chanjar.weixin.cp.bean.WxCpMessage; import me.chanjar.weixin.cp.bean.WxCpTag; @@ -37,7 +41,7 @@ import java.io.InputStream; import java.util.List; import java.util.UUID; -public class WxCpServiceImpl implements WxCpService { +public class WxCpServiceImpl implements WxCpService, RequestHttp { protected final Logger log = LoggerFactory.getLogger(WxCpServiceImpl.class); @@ -570,7 +574,7 @@ public class WxCpServiceImpl implements WxCpService { throw new RuntimeException("微信服务端异常,超出重试次数"); } - protected synchronized T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException { + public synchronized T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException { if (uri.contains("access_token=")) { throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri); } @@ -580,7 +584,7 @@ public class WxCpServiceImpl implements WxCpService { uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; try { - return executor.execute(getHttpclient(), this.httpProxy, uriWithAccessToken, data); + return executor.execute(this, uriWithAccessToken, data); } catch (WxErrorException e) { WxError error = e.getError(); /* @@ -694,4 +698,13 @@ public class WxCpServiceImpl implements WxCpService { } + @Override + public Object getRequestHttpClient() { + return this.httpClient; + } + + @Override + public Object getRequestHttpProxy() { + return this.httpProxy; + } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/jodd/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/jodd/WxCpServiceImpl.java new file mode 100644 index 00000000..8c41f042 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/jodd/WxCpServiceImpl.java @@ -0,0 +1,686 @@ +package me.chanjar.weixin.cp.api.impl.jodd; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; +import jodd.http.*; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.bean.WxJsapiSignature; +import me.chanjar.weixin.common.bean.menu.WxMenu; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.session.StandardSessionManager; +import me.chanjar.weixin.common.session.WxSession; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.common.util.RandomUtils; +import me.chanjar.weixin.common.util.crypto.SHA1; +import me.chanjar.weixin.common.util.fs.FileUtils; +import me.chanjar.weixin.common.util.http.*; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.cp.api.WxCpConfigStorage; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.WxCpDepart; +import me.chanjar.weixin.cp.bean.WxCpMessage; +import me.chanjar.weixin.cp.bean.WxCpTag; +import me.chanjar.weixin.cp.bean.WxCpUser; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.UUID; + +public class WxCpServiceImpl implements WxCpService, RequestHttp { + + protected final Logger log = LoggerFactory.getLogger(WxCpServiceImpl.class); + + /** + * 全局的是否正在刷新access token的锁 + */ + protected final Object globalAccessTokenRefreshLock = new Object(); + + /** + * 全局的是否正在刷新jsapi_ticket的锁 + */ + protected final Object globalJsapiTicketRefreshLock = new Object(); + + protected WxCpConfigStorage configStorage; + + protected HttpConnectionProvider httpClient; + + protected ProxyInfo httpProxy; + protected WxSessionManager sessionManager = new StandardSessionManager(); + /** + * 临时文件目录 + */ + protected File tmpDirFile; + private int retrySleepMillis = 1000; + private int maxRetryTimes = 5; + + @Override + public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { + try { + return SHA1.gen(this.configStorage.getToken(), timestamp, nonce, data) + .equals(msgSignature); + } catch (Exception e) { + return false; + } + } + + @Override + public void userAuthenticated(String userId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?userid=" + userId; + get(url, null); + } + + @Override + public String getAccessToken() throws WxErrorException { + return getAccessToken(false); + } + + @Override + public String getAccessToken(boolean forceRefresh) throws WxErrorException { + if (forceRefresh) { + this.configStorage.expireAccessToken(); + } + if (this.configStorage.isAccessTokenExpired()) { + synchronized (this.globalAccessTokenRefreshLock) { + if (this.configStorage.isAccessTokenExpired()) { + String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" + + "&corpid=" + this.configStorage.getCorpId() + + "&corpsecret=" + this.configStorage.getCorpSecret(); + + HttpRequest request = HttpRequest.get(url); + if (this.httpProxy != null) { + httpClient.useProxy(this.httpProxy); + } + request.withConnectionProvider(httpClient); + HttpResponse response = request.send(); + + String resultContent = response.bodyText(); + WxError error = WxError.fromJson(resultContent); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + this.configStorage.updateAccessToken( + accessToken.getAccessToken(), accessToken.getExpiresIn()); + } + } + } + return this.configStorage.getAccessToken(); + } + + @Override + public String getJsapiTicket() throws WxErrorException { + return getJsapiTicket(false); + } + + @Override + public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { + if (forceRefresh) { + this.configStorage.expireJsapiTicket(); + } + if (this.configStorage.isJsapiTicketExpired()) { + synchronized (this.globalJsapiTicketRefreshLock) { + if (this.configStorage.isJsapiTicketExpired()) { + String url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket"; + String responseContent = execute(new SimpleGetRequestExecutor(), url, null); + JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); + String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); + int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); + this.configStorage.updateJsapiTicket(jsapiTicket, + expiresInSeconds); + } + } + } + return this.configStorage.getJsapiTicket(); + } + + @Override + public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException { + long timestamp = System.currentTimeMillis() / 1000; + String noncestr = RandomUtils.getRandomStr(); + String jsapiTicket = getJsapiTicket(false); + String signature = SHA1.genWithAmple( + "jsapi_ticket=" + jsapiTicket, + "noncestr=" + noncestr, + "timestamp=" + timestamp, + "url=" + url + ); + WxJsapiSignature jsapiSignature = new WxJsapiSignature(); + jsapiSignature.setTimestamp(timestamp); + jsapiSignature.setNonceStr(noncestr); + jsapiSignature.setUrl(url); + jsapiSignature.setSignature(signature); + + // Fixed bug + jsapiSignature.setAppId(this.configStorage.getCorpId()); + + return jsapiSignature; + } + + @Override + public void messageSend(WxCpMessage message) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; + post(url, message.toJson()); + } + + @Override + public void menuCreate(WxMenu menu) throws WxErrorException { + menuCreate(this.configStorage.getAgentId(), menu); + } + + @Override + public void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/create?agentid=" + + this.configStorage.getAgentId(); + post(url, menu.toJson()); + } + + @Override + public void menuDelete() throws WxErrorException { + menuDelete(this.configStorage.getAgentId()); + } + + @Override + public void menuDelete(Integer agentId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/delete?agentid=" + agentId; + get(url, null); + } + + @Override + public WxMenu menuGet() throws WxErrorException { + return menuGet(this.configStorage.getAgentId()); + } + + @Override + public WxMenu menuGet(Integer agentId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/get?agentid=" + agentId; + try { + String resultContent = get(url, null); + return WxMenu.fromJson(resultContent); + } catch (WxErrorException e) { + // 46003 不存在的菜单数据 + if (e.getError().getErrorCode() == 46003) { + return null; + } + throw e; + } + } + + @Override + public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) + throws WxErrorException, IOException { + return mediaUpload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType)); + } + + @Override + public WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType; + return execute(new MediaUploadRequestExecutor(), url, file); + } + + @Override + public File mediaDownload(String mediaId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/media/get"; + return execute( + new MediaDownloadRequestExecutor( + this.configStorage.getTmpDirFile()), + url, "media_id=" + mediaId); + } + + + @Override + public Integer departCreate(WxCpDepart depart) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create"; + String responseContent = execute( + new SimplePostRequestExecutor(), + url, + depart.toJson()); + JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id")); + } + + @Override + public void departUpdate(WxCpDepart group) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/department/update"; + post(url, group.toJson()); + } + + @Override + public void departDelete(Integer departId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/department/delete?id=" + departId; + get(url, null); + } + + @Override + public List departGet() throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/department/list"; + String responseContent = get(url, null); + /* + * 操蛋的微信API,创建时返回的是 { group : { id : ..., name : ...} } + * 查询时返回的是 { groups : [ { id : ..., name : ..., count : ... }, ... ] } + */ + JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + return WxCpGsonBuilder.INSTANCE.create() + .fromJson( + tmpJsonElement.getAsJsonObject().get("department"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public void userCreate(WxCpUser user) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/create"; + post(url, user.toJson()); + } + + @Override + public void userUpdate(WxCpUser user) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/update"; + post(url, user.toJson()); + } + + @Override + public void userDelete(String userid) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/delete?userid=" + userid; + get(url, null); + } + + @Override + public void userDelete(String[] userids) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/batchdelete"; + JsonObject jsonObject = new JsonObject(); + JsonArray jsonArray = new JsonArray(); + for (String userid : userids) { + jsonArray.add(new JsonPrimitive(userid)); + } + jsonObject.add("useridlist", jsonArray); + post(url, jsonObject.toString()); + } + + @Override + public WxCpUser userGet(String userid) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?userid=" + userid; + String responseContent = get(url, null); + return WxCpUser.fromJson(responseContent); + } + + @Override + public List userList(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/list?department_id=" + departId; + String params = ""; + if (fetchChild != null) { + params += "&fetch_child=" + (fetchChild ? "1" : "0"); + } + if (status != null) { + params += "&status=" + status; + } else { + params += "&status=0"; + } + + String responseContent = get(url, params); + JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + return WxCpGsonBuilder.INSTANCE.create() + .fromJson( + tmpJsonElement.getAsJsonObject().get("userlist"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public List departGetUsers(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?department_id=" + departId; + String params = ""; + if (fetchChild != null) { + params += "&fetch_child=" + (fetchChild ? "1" : "0"); + } + if (status != null) { + params += "&status=" + status; + } else { + params += "&status=0"; + } + + String responseContent = get(url, params); + JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + return WxCpGsonBuilder.INSTANCE.create() + .fromJson( + tmpJsonElement.getAsJsonObject().get("userlist"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public String tagCreate(String tagName) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/create"; + JsonObject o = new JsonObject(); + o.addProperty("tagname", tagName); + String responseContent = post(url, o.toString()); + JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + return tmpJsonElement.getAsJsonObject().get("tagid").getAsString(); + } + + @Override + public void tagUpdate(String tagId, String tagName) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/update"; + JsonObject o = new JsonObject(); + o.addProperty("tagid", tagId); + o.addProperty("tagname", tagName); + post(url, o.toString()); + } + + @Override + public void tagDelete(String tagId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?tagid=" + tagId; + get(url, null); + } + + @Override + public List tagGet() throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/list"; + String responseContent = get(url, null); + JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + return WxCpGsonBuilder.INSTANCE.create() + .fromJson( + tmpJsonElement.getAsJsonObject().get("taglist"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public List tagGetUsers(String tagId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/get?tagid=" + tagId; + String responseContent = get(url, null); + JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + return WxCpGsonBuilder.INSTANCE.create() + .fromJson( + tmpJsonElement.getAsJsonObject().get("userlist"), + new TypeToken>() { + }.getType() + ); + } + + @Override + public void tagAddUsers(String tagId, List userIds, List partyIds) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/addtagusers"; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("tagid", tagId); + if (userIds != null) { + JsonArray jsonArray = new JsonArray(); + for (String userId : userIds) { + jsonArray.add(new JsonPrimitive(userId)); + } + jsonObject.add("userlist", jsonArray); + } + if (partyIds != null) { + JsonArray jsonArray = new JsonArray(); + for (String userId : partyIds) { + jsonArray.add(new JsonPrimitive(userId)); + } + jsonObject.add("partylist", jsonArray); + } + post(url, jsonObject.toString()); + } + + @Override + public void tagRemoveUsers(String tagId, List userIds) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/deltagusers"; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("tagid", tagId); + JsonArray jsonArray = new JsonArray(); + for (String userId : userIds) { + jsonArray.add(new JsonPrimitive(userId)); + } + jsonObject.add("userlist", jsonArray); + post(url, jsonObject.toString()); + } + + @Override + public String oauth2buildAuthorizationUrl(String state) { + return this.oauth2buildAuthorizationUrl( + this.configStorage.getOauth2redirectUri(), + state + ); + } + + @Override + public String oauth2buildAuthorizationUrl(String redirectUri, String state) { + String url = "https://open.weixin.qq.com/connect/oauth2/authorize?"; + url += "appid=" + this.configStorage.getCorpId(); + url += "&redirect_uri=" + URIUtil.encodeURIComponent(redirectUri); + url += "&response_type=code"; + url += "&scope=snsapi_base"; + if (state != null) { + url += "&state=" + state; + } + url += "#wechat_redirect"; + return url; + } + + @Override + public String[] oauth2getUserInfo(String code) throws WxErrorException { + return oauth2getUserInfo(this.configStorage.getAgentId(), code); + } + + @Override + public String[] oauth2getUserInfo(Integer agentId, String code) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?" + + "code=" + code + + "&agentid=" + agentId; + String responseText = get(url, null); + JsonElement je = new JsonParser().parse(responseText); + JsonObject jo = je.getAsJsonObject(); + return new String[]{GsonHelper.getString(jo, "UserId"), GsonHelper.getString(jo, "DeviceId"), GsonHelper.getString(jo, "OpenId")}; + } + + @Override + public int invite(String userId, String inviteTips) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/invite/send"; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("userid", userId); + if (StringUtils.isNotEmpty(inviteTips)) { + jsonObject.addProperty("invite_tips", inviteTips); + } + String responseContent = post(url, jsonObject.toString()); + JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + return tmpJsonElement.getAsJsonObject().get("type").getAsInt(); + } + + @Override + public String[] getCallbackIp() throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip"; + String responseContent = get(url, null); + JsonElement tmpJsonElement = new JsonParser().parse(responseContent); + JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray(); + String[] ips = new String[jsonArray.size()]; + for (int i = 0; i < jsonArray.size(); i++) { + ips[i] = jsonArray.get(i).getAsString(); + } + return ips; + } + + @Override + public String get(String url, String queryParam) throws WxErrorException { + return execute(new SimpleGetRequestExecutor(), url, queryParam); + } + + @Override + public String post(String url, String postData) throws WxErrorException { + return execute(new SimplePostRequestExecutor(), url, postData); + } + + /** + * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 + */ + @Override + public T execute(RequestExecutor executor, String uri, E data) throws WxErrorException { + int retryTimes = 0; + do { + try { + T result = this.executeInternal(executor, uri, data); + this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", uri, data, result); + return result; + } catch (WxErrorException e) { + if (retryTimes + 1 > this.maxRetryTimes) { + this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes); + //最后一次重试失败后,直接抛出异常,不再等待 + throw new RuntimeException("微信服务端异常,超出重试次数"); + } + + WxError error = e.getError(); + /* + * -1 系统繁忙, 1000ms后重试 + */ + if (error.getErrorCode() == -1) { + int sleepMillis = this.retrySleepMillis * (1 << retryTimes); + try { + this.log.debug("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1); + Thread.sleep(sleepMillis); + } catch (InterruptedException e1) { + throw new RuntimeException(e1); + } + } else { + throw e; + } + } + } while (retryTimes++ < this.maxRetryTimes); + + this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes); + throw new RuntimeException("微信服务端异常,超出重试次数"); + } + + + public synchronized T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException { + if (uri.contains("access_token=")) { + throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri); + } + String accessToken = getAccessToken(false); + + String uriWithAccessToken = uri; + uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; + + try { + return executor.execute(this, uriWithAccessToken, data); + } catch (WxErrorException e) { + WxError error = e.getError(); + /* + * 发生以下情况时尝试刷新access_token + * 40001 获取access_token时AppSecret错误,或者access_token无效 + * 42001 access_token超时 + */ + if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { + // 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token + this.configStorage.expireAccessToken(); + return execute(executor, uri, data); + } + + if (error.getErrorCode() != 0) { + this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", uri, data, error); + throw new WxErrorException(error); + } + return null; + } catch (IOException e) { + this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[EXCEPTION]: {}", uri, data, e.getMessage()); + throw new RuntimeException(e); + } + } + + protected HttpConnectionProvider getHttpclient() { + return this.httpClient; + } + + @Override + public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) { + this.configStorage = wxConfigProvider; + + if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) { + httpProxy = new ProxyInfo(ProxyInfo.ProxyType.HTTP, configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword()); + } + + httpClient = JoddHttp.httpConnectionProvider; + } + + @Override + public void setRetrySleepMillis(int retrySleepMillis) { + this.retrySleepMillis = retrySleepMillis; + } + + + @Override + public void setMaxRetryTimes(int maxRetryTimes) { + this.maxRetryTimes = maxRetryTimes; + } + + @Override + public WxSession getSession(String id) { + if (this.sessionManager == null) { + return null; + } + return this.sessionManager.getSession(id); + } + + @Override + public WxSession getSession(String id, boolean create) { + if (this.sessionManager == null) { + return null; + } + return this.sessionManager.getSession(id, create); + } + + + @Override + public void setSessionManager(WxSessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + @Override + public String replaceParty(String mediaId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty"; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("media_id", mediaId); + return post(url, jsonObject.toString()); + } + + @Override + public String replaceUser(String mediaId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser"; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("media_id", mediaId); + return post(url, jsonObject.toString()); + } + + @Override + public String getTaskResult(String joinId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid=" + joinId; + return get(url, null); + } + + public File getTmpDirFile() { + return this.tmpDirFile; + } + + public void setTmpDirFile(File tmpDirFile) { + this.tmpDirFile = tmpDirFile; + } + + + @Override + public Object getRequestHttpClient() { + return this.httpClient; + } + + @Override + public Object getRequestHttpProxy() { + return this.httpProxy; + } +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java index 2a694ff3..80265168 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java @@ -9,6 +9,7 @@ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import me.chanjar.weixin.common.util.xml.XStreamInitializer; +import me.chanjar.weixin.cp.api.impl.apache.WxCpServiceImpl; public class ApiTestModule implements Module { diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java index c0cc3251..1a3fb795 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java @@ -2,6 +2,7 @@ package me.chanjar.weixin.cp.api; import java.util.List; +import me.chanjar.weixin.cp.api.impl.apache.WxCpServiceImpl; import org.testng.Assert; import org.testng.annotations.Guice; import org.testng.annotations.Test; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java index d9fb2605..3f297968 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java @@ -5,6 +5,7 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; +import me.chanjar.weixin.cp.api.impl.apache.WxCpServiceImpl; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Guice; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpTagAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpTagAPITest.java index aa26e962..d5ff3a3a 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpTagAPITest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpTagAPITest.java @@ -1,6 +1,7 @@ package me.chanjar.weixin.cp.api; import com.google.inject.Inject; +import me.chanjar.weixin.cp.api.impl.apache.WxCpServiceImpl; import me.chanjar.weixin.cp.bean.WxCpTag; import me.chanjar.weixin.cp.bean.WxCpUser; import org.testng.Assert; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java index 4d5a7a91..450cefa2 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java @@ -2,6 +2,7 @@ package me.chanjar.weixin.cp.api; import com.google.inject.Inject; import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.cp.api.impl.apache.WxCpServiceImpl; import me.chanjar.weixin.cp.bean.WxCpDepart; import me.chanjar.weixin.cp.bean.WxCpUser; import org.testng.Assert; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java index c837e51e..a6fdfa9d 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java @@ -5,6 +5,7 @@ import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.bean.menu.WxMenu; import me.chanjar.weixin.common.bean.menu.WxMenuButton; import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.cp.api.impl.apache.WxCpServiceImpl; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Guice; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpBaseAPITest.java similarity index 85% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpBaseAPITest.java index 894bb3b6..6fecf6e7 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpBaseAPITest.java @@ -1,7 +1,9 @@ -package me.chanjar.weixin.cp.api; +package me.chanjar.weixin.cp.api.impl.apache; import com.google.inject.Inject; import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.cp.api.ApiTestModule; +import me.chanjar.weixin.cp.api.WxCpConfigStorage; import org.apache.commons.lang3.StringUtils; import org.testng.Assert; import org.testng.annotations.Guice; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpBusyRetryTest.java similarity index 90% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpBusyRetryTest.java index e1b005bd..724368e8 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpBusyRetryTest.java @@ -1,8 +1,9 @@ -package me.chanjar.weixin.cp.api; +package me.chanjar.weixin.cp.api.impl.apache; import me.chanjar.weixin.common.bean.result.WxError; import me.chanjar.weixin.common.exception.WxErrorException; import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.cp.api.WxCpService; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -19,8 +20,8 @@ public class WxCpBusyRetryTest { WxCpService service = new WxCpServiceImpl() { @Override - protected synchronized T executeInternal( - RequestExecutor executor, String uri, E data) + public synchronized T executeInternal( + RequestExecutor executor, String uri, E data) throws WxErrorException { this.log.info("Executed"); WxError error = new WxError(); diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpMessageAPITest.java similarity index 93% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpMessageAPITest.java index b7bcf7a3..968cfea9 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpMessageAPITest.java @@ -1,13 +1,12 @@ -package me.chanjar.weixin.cp.api; - -import org.testng.annotations.Guice; -import org.testng.annotations.Test; +package me.chanjar.weixin.cp.api.impl.apache; import com.google.inject.Inject; - import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.cp.api.ApiTestModule; import me.chanjar.weixin.cp.bean.WxCpMessage; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; /*** * 测试发送消息 diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java index dc667d88..eb78d667 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java @@ -13,7 +13,7 @@ import me.chanjar.weixin.cp.api.WxCpConfigStorage; import me.chanjar.weixin.cp.api.WxCpMessageHandler; import me.chanjar.weixin.cp.api.WxCpMessageRouter; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.api.WxCpServiceImpl; +import me.chanjar.weixin.cp.api.impl.apache.WxCpServiceImpl; import me.chanjar.weixin.cp.bean.WxCpXmlMessage; import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage; diff --git a/weixin-java-cp/src/test/resources/testng.xml b/weixin-java-cp/src/test/resources/testng.xml index ba92bc0c..04f960ed 100644 --- a/weixin-java-cp/src/test/resources/testng.xml +++ b/weixin-java-cp/src/test/resources/testng.xml @@ -3,9 +3,9 @@ - - - + + + diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java index d4a36c65..35a23f70 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java @@ -6,9 +6,15 @@ import me.chanjar.weixin.mp.bean.result.WxMpCardResult; /** * 卡券相关接口 - * @author YuJian(mgcnrx11@hotmail.com) on 01/11/2016 + * @author YuJian(mgcnrx11@hotmail.com) on 01/11/2016 */ -public interface WxMpCardService { +public interface WxMpCardService { + + /** + * 得到WxMpService + * @return + */ + WxMpService getWxMpService(); /** * 获得卡券api_ticket,不强制刷新卡券api_ticket diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java index f4053899..dbff2a7a 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java @@ -1,7 +1,7 @@ package me.chanjar.weixin.mp.api; import me.chanjar.weixin.common.bean.WxAccessToken; -import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import java.io.File; import java.util.concurrent.locks.Lock; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java index 4bd1daec..9181a401 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java @@ -2,7 +2,7 @@ package me.chanjar.weixin.mp.api; import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.util.ToStringUtils; -import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import java.io.File; import java.util.concurrent.locks.Lock; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java index cf1401a5..0b968594 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java @@ -2,10 +2,10 @@ package me.chanjar.weixin.mp.api; import me.chanjar.weixin.common.bean.WxJsapiSignature; import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.mp.bean.*; import me.chanjar.weixin.mp.bean.result.*; -import org.apache.http.HttpHost; /** * 微信API的Service @@ -133,7 +133,6 @@ public interface WxMpService { * 长链接转短链接接口 * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=长链接转短链接接口 * - * */ String shortUrl(String long_url) throws WxErrorException; @@ -153,8 +152,8 @@ public interface WxMpService { * * * @param redirectURI 用户授权完成后的重定向链接,无需urlencode, 方法内会进行encode - * @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可 - * @param state 非必填,用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验 + * @param scope 应用授权作用域,拥有多个作用域用逗号(,)分隔,网页应用目前仅填写snsapi_login即可 + * @param state 非必填,用于保持请求和回调的状态,授权请求后原样带回给第三方。该参数可用于防止csrf攻击(跨站请求伪造攻击),建议第三方带上该参数,可设置为简单的随机数加session进行校验 * @return url */ String buildQrConnectUrl(String redirectURI, String scope, String state); @@ -190,7 +189,7 @@ public interface WxMpService { * 用oauth2获取用户信息, 当前面引导授权时的scope是snsapi_userinfo的时候才可以 * * - * @param lang zh_CN, zh_TW, en + * @param lang zh_CN, zh_TW, en */ WxMpUser oauth2getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException; @@ -198,7 +197,6 @@ public interface WxMpService { *
    * 验证oauth2的access token是否有效
    * 
- * */ boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken); @@ -224,7 +222,7 @@ public interface WxMpService { *
    * Service没有实现某个API的时候,可以用这个,
    * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
-   * 可以参考,{@link me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor}的实现方法
+   * 可以参考,{@link MediaUploadRequestExecutor}的实现方法
    * 
*/ T execute(RequestExecutor executor, String uri, E data) throws WxErrorException; @@ -232,7 +230,7 @@ public interface WxMpService { /** * 获取代理对象 */ - HttpHost getHttpProxy(); + //HttpHost getRequestHttpProxy(); /** * 注入 {@link WxMpConfigStorage} 的实现 @@ -345,4 +343,15 @@ public interface WxMpService { * @return WxMpDeviceService */ WxMpDeviceService getDeviceService(); + + /** + * @return + */ + //Object getHttpclient(); + + /** + * @return + */ + //Object getHttpProxy(); + } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpStoreService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpStoreService.java index c214c46d..38dde803 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpStoreService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpStoreService.java @@ -12,7 +12,7 @@ import java.util.List; * @author binarywang(Binary Wang) * Created by Binary Wang on 2016-09-23. */ -public interface WxMpStoreService { +public interface WxMpStoreService { /** *
    * 创建门店
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
index 2e6bf88e..9e64d043 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
@@ -1,17 +1,10 @@
 package me.chanjar.weixin.mp.api.impl;
 
-import java.util.Arrays;
-import java.util.concurrent.locks.Lock;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.google.gson.JsonPrimitive;
 import com.google.gson.reflect.TypeToken;
-
 import me.chanjar.weixin.common.bean.WxCardApiSignature;
 import me.chanjar.weixin.common.bean.result.WxError;
 import me.chanjar.weixin.common.exception.WxErrorException;
@@ -22,11 +15,18 @@ import me.chanjar.weixin.mp.api.WxMpCardService;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.bean.result.WxMpCardResult;
 import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
+import org.apache.http.HttpHost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Arrays;
+import java.util.concurrent.locks.Lock;
 
 /**
  * Created by Binary Wang on 2016/7/27.
  */
-public class WxMpCardServiceImpl implements WxMpCardService {
+public class WxMpCardServiceImpl implements WxMpCardService {
 
   private final Logger log = LoggerFactory.getLogger(WxMpCardServiceImpl.class);
 
@@ -36,6 +36,15 @@ public class WxMpCardServiceImpl implements WxMpCardService {
     this.wxMpService = wxMpService;
   }
 
+  /**
+   * 得到WxMpService
+   * @return
+   */
+  @Override
+  public WxMpService getWxMpService(){
+      return this.wxMpService;
+  }
+
   /**
    * 获得卡券api_ticket,不强制刷新卡券api_ticket
    *
@@ -62,27 +71,27 @@ public class WxMpCardServiceImpl implements WxMpCardService {
    */
   @Override
   public String getCardApiTicket(boolean forceRefresh) throws WxErrorException {
-    Lock lock = wxMpService.getWxMpConfigStorage().getCardApiTicketLock();
+    Lock lock = getWxMpService().getWxMpConfigStorage().getCardApiTicketLock();
     try {
       lock.lock();
 
       if (forceRefresh) {
-        this.wxMpService.getWxMpConfigStorage().expireCardApiTicket();
+        this.getWxMpService().getWxMpConfigStorage().expireCardApiTicket();
       }
 
-      if (this.wxMpService.getWxMpConfigStorage().isCardApiTicketExpired()) {
+      if (this.getWxMpService().getWxMpConfigStorage().isCardApiTicketExpired()) {
         String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=wx_card";
         String responseContent = this.wxMpService.execute(new SimpleGetRequestExecutor(), url, null);
         JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
         JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
         String cardApiTicket = tmpJsonObject.get("ticket").getAsString();
         int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
-        this.wxMpService.getWxMpConfigStorage().updateCardApiTicket(cardApiTicket, expiresInSeconds);
+        this.getWxMpService().getWxMpConfigStorage().updateCardApiTicket(cardApiTicket, expiresInSeconds);
       }
     } finally {
       lock.unlock();
     }
-    return this.wxMpService.getWxMpConfigStorage().getCardApiTicket();
+    return this.getWxMpService().getWxMpConfigStorage().getCardApiTicket();
   }
 
   /**
@@ -210,7 +219,7 @@ public class WxMpCardServiceImpl implements WxMpCardService {
     param.addProperty("card_id", cardId);
     param.addProperty("openid", openId);
     param.addProperty("is_mark", isMark);
-    String responseContent = this.wxMpService.post(url, param.toString());
+    String responseContent = this.getWxMpService().post(url, param.toString());
     JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
     WxMpCardResult cardResult = WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement,
             new TypeToken() { }.getType());
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpDeviceServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpDeviceServiceImpl.java
index 5c942365..3f7378e3 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpDeviceServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpDeviceServiceImpl.java
@@ -16,7 +16,7 @@ public class WxMpDeviceServiceImpl implements WxMpDeviceService {
 
   private WxMpService wxMpService;
 
-  WxMpDeviceServiceImpl(WxMpService wxMpService) {
+  public WxMpDeviceServiceImpl(WxMpService wxMpService) {
     this.wxMpService = wxMpService;
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java
index 09a68adf..4667f327 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java
@@ -1,28 +1,21 @@
 package me.chanjar.weixin.mp.api.impl;
 
-import java.io.File;
-import java.util.Date;
-
-import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.google.gson.JsonObject;
-
 import me.chanjar.weixin.common.bean.result.WxError;
 import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
 import me.chanjar.weixin.common.exception.WxErrorException;
 import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
 import me.chanjar.weixin.mp.api.WxMpKefuService;
 import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
 import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfAccountRequest;
 import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfSessionRequest;
-import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfList;
-import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfMsgList;
-import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfOnlineList;
-import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionGetResult;
-import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionList;
-import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionWaitCaseList;
+import me.chanjar.weixin.mp.bean.kefu.result.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.Date;
 
 /**
  *
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImpl.java
index af9fa54c..4242063b 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpMaterialServiceImpl.java
@@ -10,9 +10,6 @@ import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import me.chanjar.weixin.mp.api.WxMpMaterialService;
 import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.mp.bean.material.WxMpMaterial;
-import me.chanjar.weixin.mp.bean.material.WxMpMaterialArticleUpdate;
-import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews;
 import me.chanjar.weixin.mp.bean.material.*;
 import me.chanjar.weixin.mp.util.http.*;
 import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java
index d1bdae1e..a1fee937 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpUserTagServiceImpl.java
@@ -1,14 +1,9 @@
 package me.chanjar.weixin.mp.api.impl;
 
-import java.util.List;
-
-import org.apache.commons.lang3.StringUtils;
-
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.google.gson.reflect.TypeToken;
-
 import me.chanjar.weixin.common.bean.result.WxError;
 import me.chanjar.weixin.common.exception.WxErrorException;
 import me.chanjar.weixin.mp.api.WxMpService;
@@ -16,6 +11,9 @@ import me.chanjar.weixin.mp.api.WxMpUserTagService;
 import me.chanjar.weixin.mp.bean.tag.WxTagListUser;
 import me.chanjar.weixin.mp.bean.tag.WxUserTag;
 import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.List;
 
 /**
  *
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/apache/WxMpServiceImpl.java
similarity index 95%
rename from weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/apache/WxMpServiceImpl.java
index 406e8712..5e6f863a 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/apache/WxMpServiceImpl.java
@@ -1,4 +1,4 @@
-package me.chanjar.weixin.mp.api.impl;
+package me.chanjar.weixin.mp.api.impl.apache;
 
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
@@ -13,7 +13,10 @@ import me.chanjar.weixin.common.session.WxSessionManager;
 import me.chanjar.weixin.common.util.RandomUtils;
 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.mp.api.*;
+import me.chanjar.weixin.mp.api.impl.*;
 import me.chanjar.weixin.mp.bean.*;
 import me.chanjar.weixin.mp.bean.result.*;
 import org.apache.http.HttpHost;
@@ -28,7 +31,7 @@ import org.slf4j.LoggerFactory;
 import java.io.IOException;
 import java.util.concurrent.locks.Lock;
 
-public class WxMpServiceImpl implements WxMpService {
+public class WxMpServiceImpl implements WxMpService,RequestHttp {
 
   private static final JsonParser JSON_PARSER = new JsonParser();
 
@@ -243,7 +246,7 @@ public class WxMpServiceImpl implements WxMpService {
   private WxMpOAuth2AccessToken getOAuth2AccessToken(StringBuilder url) throws WxErrorException {
     try {
       RequestExecutor executor = new SimpleGetRequestExecutor();
-      String responseText = executor.execute(this.getHttpclient(), this.httpProxy, url.toString(), null);
+      String responseText = executor.execute(this, url.toString(), null);
       return WxMpOAuth2AccessToken.fromJson(responseText);
     } catch (IOException e) {
       throw new RuntimeException(e);
@@ -287,7 +290,7 @@ public class WxMpServiceImpl implements WxMpService {
 
     try {
       RequestExecutor executor = new SimpleGetRequestExecutor();
-      String responseText = executor.execute(getHttpclient(), this.httpProxy, url.toString(), null);
+      String responseText = executor.execute(this, url.toString(), null);
       return WxMpUser.fromJson(responseText);
     } catch (IOException e) {
       throw new RuntimeException(e);
@@ -303,7 +306,7 @@ public class WxMpServiceImpl implements WxMpService {
 
     try {
       RequestExecutor executor = new SimpleGetRequestExecutor();
-      executor.execute(getHttpclient(), this.httpProxy, url.toString(), null);
+      executor.execute(this, url.toString(), null);
     } catch (IOException e) {
       throw new RuntimeException(e);
     } catch (WxErrorException e) {
@@ -373,7 +376,7 @@ public class WxMpServiceImpl implements WxMpService {
     throw new RuntimeException("微信服务端异常,超出重试次数");
   }
 
-  protected synchronized  T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException {
+  public synchronized  T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException {
     if (uri.indexOf("access_token=") != -1) {
       throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
     }
@@ -383,7 +386,7 @@ public class WxMpServiceImpl implements WxMpService {
     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
 
     try {
-      return executor.execute(getHttpclient(), this.httpProxy, uriWithAccessToken, data);
+      return executor.execute(this, uriWithAccessToken, data);
     } catch (WxErrorException e) {
       WxError error = e.getError();
       /*
@@ -410,11 +413,12 @@ public class WxMpServiceImpl implements WxMpService {
     }
   }
 
-  @Override
+  //@Override
   public HttpHost getHttpProxy() {
     return this.httpProxy;
   }
 
+  //@Override
   public CloseableHttpClient getHttpclient() {
     return this.httpClient;
   }
@@ -518,4 +522,14 @@ public class WxMpServiceImpl implements WxMpService {
   public WxMpDeviceService getDeviceService() {
     return this.deviceService;
   }
+
+  @Override
+  public Object getRequestHttpClient() {
+    return this.httpClient;
+  }
+
+  @Override
+  public Object getRequestHttpProxy() {
+    return this.httpProxy;
+  }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/jodd/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/jodd/WxMpServiceImpl.java
new file mode 100644
index 00000000..fa342f66
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/jodd/WxMpServiceImpl.java
@@ -0,0 +1,516 @@
+package me.chanjar.weixin.mp.api.impl.jodd;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import jodd.http.*;
+import jodd.http.net.SocketHttpConnectionProvider;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.bean.WxJsapiSignature;
+import me.chanjar.weixin.common.bean.result.WxError;
+import me.chanjar.weixin.common.exception.WxErrorException;
+import me.chanjar.weixin.common.session.StandardSessionManager;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.common.util.RandomUtils;
+import me.chanjar.weixin.common.util.crypto.SHA1;
+import me.chanjar.weixin.common.util.http.*;
+import me.chanjar.weixin.mp.api.*;
+import me.chanjar.weixin.mp.api.impl.*;
+import me.chanjar.weixin.mp.bean.*;
+import me.chanjar.weixin.mp.bean.result.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.concurrent.locks.Lock;
+
+public class WxMpServiceImpl implements WxMpService,RequestHttp {
+
+  private static final JsonParser JSON_PARSER = new JsonParser();
+
+  protected final Logger log = LoggerFactory.getLogger(this.getClass());
+  protected WxSessionManager sessionManager = new StandardSessionManager();
+  private WxMpConfigStorage wxMpConfigStorage;
+  private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this);
+  private WxMpMaterialService materialService = new WxMpMaterialServiceImpl(this);
+  private WxMpMenuService menuService = new WxMpMenuServiceImpl(this);
+  private WxMpUserService userService = new WxMpUserServiceImpl(this);
+  private WxMpUserTagService tagService = new WxMpUserTagServiceImpl(this);
+  private WxMpQrcodeService qrCodeService = new WxMpQrcodeServiceImpl(this);
+  private WxMpCardService cardService = new WxMpCardServiceImpl(this);
+  private WxMpStoreService storeService = new WxMpStoreServiceImpl(this);
+  private WxMpDataCubeService dataCubeService = new WxMpDataCubeServiceImpl(this);
+  private WxMpUserBlacklistService blackListService = new WxMpUserBlacklistServiceImpl(this);
+  private WxMpTemplateMsgService templateMsgService = new WxMpTemplateMsgServiceImpl(this);
+  private WxMpDeviceService deviceService = new WxMpDeviceServiceImpl(this);
+
+  private HttpConnectionProvider httpClient;
+  private ProxyInfo httpProxy;
+  private int retrySleepMillis = 1000;
+  private int maxRetryTimes = 5;
+
+  private void initHttpClient() {
+    WxMpConfigStorage configStorage = this.getWxMpConfigStorage();
+
+    if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
+      httpProxy = new ProxyInfo(ProxyInfo.ProxyType.HTTP, configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword());
+    }
+
+    httpClient = JoddHttp.httpConnectionProvider;
+  }
+
+  @Override
+  public boolean checkSignature(String timestamp, String nonce, String signature) {
+    try {
+      return SHA1.gen(this.getWxMpConfigStorage().getToken(), timestamp, nonce)
+        .equals(signature);
+    } catch (Exception e) {
+      return false;
+    }
+  }
+
+  @Override
+  public String getAccessToken() throws WxErrorException {
+    return getAccessToken(false);
+  }
+
+  @Override
+  public String getAccessToken(boolean forceRefresh) throws WxErrorException {
+    Lock lock = this.getWxMpConfigStorage().getAccessTokenLock();
+    try {
+      lock.lock();
+
+      if (forceRefresh) {
+        this.getWxMpConfigStorage().expireAccessToken();
+      }
+
+      if (this.getWxMpConfigStorage().isAccessTokenExpired()) {
+        String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
+          "&appid=" + this.getWxMpConfigStorage().getAppId() + "&secret="
+          + this.getWxMpConfigStorage().getSecret();
+
+        HttpRequest request = HttpRequest.get(url);
+        if (this.httpProxy != null) {
+          SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider();
+          provider.useProxy(httpProxy);
+          request.withConnectionProvider(provider);
+        }
+        HttpResponse response = request.send();
+        String resultContent = response.bodyText();
+        WxError error = WxError.fromJson(resultContent);
+        if (error.getErrorCode() != 0) {
+          throw new WxErrorException(error);
+        }
+        WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
+        this.getWxMpConfigStorage().updateAccessToken(accessToken.getAccessToken(),
+          accessToken.getExpiresIn());
+      }
+    } finally {
+      lock.unlock();
+    }
+    return this.getWxMpConfigStorage().getAccessToken();
+  }
+
+  @Override
+  public String getJsapiTicket() throws WxErrorException {
+    return getJsapiTicket(false);
+  }
+
+  @Override
+  public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
+    Lock lock = this.getWxMpConfigStorage().getJsapiTicketLock();
+    try {
+      lock.lock();
+
+      if (forceRefresh) {
+        this.getWxMpConfigStorage().expireJsapiTicket();
+      }
+
+      if (this.getWxMpConfigStorage().isJsapiTicketExpired()) {
+        String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi";
+        String responseContent = execute(new SimpleGetRequestExecutor(), url, null);
+        JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent);
+        JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
+        String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
+        int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
+        this.getWxMpConfigStorage().updateJsapiTicket(jsapiTicket, expiresInSeconds);
+      }
+    } finally {
+      lock.unlock();
+    }
+    return this.getWxMpConfigStorage().getJsapiTicket();
+  }
+
+  @Override
+  public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException {
+    long timestamp = System.currentTimeMillis() / 1000;
+    String noncestr = RandomUtils.getRandomStr();
+    String jsapiTicket = getJsapiTicket(false);
+    String signature = SHA1.genWithAmple("jsapi_ticket=" + jsapiTicket,
+      "noncestr=" + noncestr, "timestamp=" + timestamp, "url=" + url);
+    WxJsapiSignature jsapiSignature = new WxJsapiSignature();
+    jsapiSignature.setAppId(this.getWxMpConfigStorage().getAppId());
+    jsapiSignature.setTimestamp(timestamp);
+    jsapiSignature.setNonceStr(noncestr);
+    jsapiSignature.setUrl(url);
+    jsapiSignature.setSignature(signature);
+    return jsapiSignature;
+  }
+
+  @Override
+  public WxMpMassUploadResult massNewsUpload(WxMpMassNews news) throws WxErrorException {
+    String url = "https://api.weixin.qq.com/cgi-bin/media/uploadnews";
+    String responseContent = this.post(url, news.toJson());
+    return WxMpMassUploadResult.fromJson(responseContent);
+  }
+
+  @Override
+  public WxMpMassUploadResult massVideoUpload(WxMpMassVideo video) throws WxErrorException {
+    String url = "https://api.weixin.qq.com/cgi-bin/media/uploadvideo";
+    String responseContent = this.post(url, video.toJson());
+    return WxMpMassUploadResult.fromJson(responseContent);
+  }
+
+  @Override
+  public WxMpMassSendResult massGroupMessageSend(WxMpMassTagMessage message) throws WxErrorException {
+    String url = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall";
+    String responseContent = this.post(url, message.toJson());
+    return WxMpMassSendResult.fromJson(responseContent);
+  }
+
+  @Override
+  public WxMpMassSendResult massOpenIdsMessageSend(WxMpMassOpenIdsMessage message) throws WxErrorException {
+    String url = "https://api.weixin.qq.com/cgi-bin/message/mass/send";
+    String responseContent = this.post(url, message.toJson());
+    return WxMpMassSendResult.fromJson(responseContent);
+  }
+
+  @Override
+  public WxMpMassSendResult massMessagePreview(WxMpMassPreviewMessage wxMpMassPreviewMessage) throws Exception {
+    String url = "https://api.weixin.qq.com/cgi-bin/message/mass/preview";
+    String responseContent = this.post(url, wxMpMassPreviewMessage.toJson());
+    return WxMpMassSendResult.fromJson(responseContent);
+  }
+
+  @Override
+  public String shortUrl(String long_url) throws WxErrorException {
+    String url = "https://api.weixin.qq.com/cgi-bin/shorturl";
+    JsonObject o = new JsonObject();
+    o.addProperty("action", "long2short");
+    o.addProperty("long_url", long_url);
+    String responseContent = this.post(url, o.toString());
+    JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent);
+    return tmpJsonElement.getAsJsonObject().get("short_url").getAsString();
+  }
+
+  @Override
+  public WxMpSemanticQueryResult semanticQuery(WxMpSemanticQuery semanticQuery) throws WxErrorException {
+    String url = "https://api.weixin.qq.com/semantic/semproxy/search";
+    String responseContent = this.post(url, semanticQuery.toJson());
+    return WxMpSemanticQueryResult.fromJson(responseContent);
+  }
+
+  @Override
+  public String oauth2buildAuthorizationUrl(String redirectURI, String scope, String state) {
+    StringBuilder url = new StringBuilder();
+    url.append("https://open.weixin.qq.com/connect/oauth2/authorize?");
+    url.append("appid=").append(this.getWxMpConfigStorage().getAppId());
+    url.append("&redirect_uri=").append(URIUtil.encodeURIComponent(redirectURI));
+    url.append("&response_type=code");
+    url.append("&scope=").append(scope);
+    if (state != null) {
+      url.append("&state=").append(state);
+    }
+    url.append("#wechat_redirect");
+    return url.toString();
+  }
+
+  @Override
+  public String buildQrConnectUrl(String redirectURI, String scope,
+                                  String state) {
+    StringBuilder url = new StringBuilder();
+    url.append("https://open.weixin.qq.com/connect/qrconnect?");
+    url.append("appid=").append(this.getWxMpConfigStorage().getAppId());
+    url.append("&redirect_uri=").append(URIUtil.encodeURIComponent(redirectURI));
+    url.append("&response_type=code");
+    url.append("&scope=").append(scope);
+    if (state != null) {
+      url.append("&state=").append(state);
+    }
+
+    url.append("#wechat_redirect");
+    return url.toString();
+  }
+
+  private WxMpOAuth2AccessToken getOAuth2AccessToken(StringBuilder url) throws WxErrorException {
+    try {
+      RequestExecutor executor = new SimpleGetRequestExecutor();
+      String responseText = executor.execute(this, url.toString(), null);
+      return WxMpOAuth2AccessToken.fromJson(responseText);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException {
+    StringBuilder url = new StringBuilder();
+    url.append("https://api.weixin.qq.com/sns/oauth2/access_token?");
+    url.append("appid=").append(this.getWxMpConfigStorage().getAppId());
+    url.append("&secret=").append(this.getWxMpConfigStorage().getSecret());
+    url.append("&code=").append(code);
+    url.append("&grant_type=authorization_code");
+
+    return this.getOAuth2AccessToken(url);
+  }
+
+  @Override
+  public WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException {
+    StringBuilder url = new StringBuilder();
+    url.append("https://api.weixin.qq.com/sns/oauth2/refresh_token?");
+    url.append("appid=").append(this.getWxMpConfigStorage().getAppId());
+    url.append("&grant_type=refresh_token");
+    url.append("&refresh_token=").append(refreshToken);
+
+    return this.getOAuth2AccessToken(url);
+  }
+
+  @Override
+  public WxMpUser oauth2getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException {
+    StringBuilder url = new StringBuilder();
+    url.append("https://api.weixin.qq.com/sns/userinfo?");
+    url.append("access_token=").append(oAuth2AccessToken.getAccessToken());
+    url.append("&openid=").append(oAuth2AccessToken.getOpenId());
+    if (lang == null) {
+      url.append("&lang=zh_CN");
+    } else {
+      url.append("&lang=").append(lang);
+    }
+
+    try {
+      RequestExecutor executor = new SimpleGetRequestExecutor();
+      String responseText = executor.execute(this, url.toString(), null);
+      return WxMpUser.fromJson(responseText);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken) {
+    StringBuilder url = new StringBuilder();
+    url.append("https://api.weixin.qq.com/sns/auth?");
+    url.append("access_token=").append(oAuth2AccessToken.getAccessToken());
+    url.append("&openid=").append(oAuth2AccessToken.getOpenId());
+
+    try {
+      RequestExecutor executor = new SimpleGetRequestExecutor();
+      executor.execute(this, url.toString(), null);
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    } catch (WxErrorException e) {
+      return false;
+    }
+    return true;
+  }
+
+  @Override
+  public String[] getCallbackIP() throws WxErrorException {
+    String url = "https://api.weixin.qq.com/cgi-bin/getcallbackip";
+    String responseContent = get(url, null);
+    JsonElement tmpJsonElement = JSON_PARSER.parse(responseContent);
+    JsonArray ipList = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray();
+    String[] ipArray = new String[ipList.size()];
+    for (int i = 0; i < ipList.size(); i++) {
+      ipArray[i] = ipList.get(i).getAsString();
+    }
+    return ipArray;
+  }
+
+  @Override
+  public String get(String url, String queryParam) throws WxErrorException {
+    return execute(new SimpleGetRequestExecutor(), url, queryParam);
+  }
+
+  @Override
+  public String post(String url, String postData) throws WxErrorException {
+    return execute(new SimplePostRequestExecutor(), url, postData);
+  }
+
+  //@Override
+  public HttpConnectionProvider getHttpclient() {
+    return this.httpClient;
+  }
+
+  //@Override
+  public Object getHttpProxy() {
+    return null;
+  }
+
+  /**
+   * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
+   */
+  public  T execute(RequestExecutor executor, String uri, E data) throws WxErrorException {
+    int retryTimes = 0;
+    do {
+      try {
+        T result = executeInternal(executor, uri, data);
+        this.log.debug("\n[URL]:  {}\n[PARAMS]: {}\n[RESPONSE]: {}", uri, data, result);
+        return result;
+      } catch (WxErrorException e) {
+        if (retryTimes + 1 > this.maxRetryTimes) {
+          this.log.warn("重试达到最大次数【{}】", maxRetryTimes);
+          //最后一次重试失败后,直接抛出异常,不再等待
+          throw new RuntimeException("微信服务端异常,超出重试次数");
+        }
+
+        WxError error = e.getError();
+        // -1 系统繁忙, 1000ms后重试
+        if (error.getErrorCode() == -1) {
+          int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
+          try {
+            this.log.warn("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
+            Thread.sleep(sleepMillis);
+          } catch (InterruptedException e1) {
+            throw new RuntimeException(e1);
+          }
+        } else {
+          throw e;
+        }
+      }
+    } while (retryTimes++ < this.maxRetryTimes);
+
+    this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
+    throw new RuntimeException("微信服务端异常,超出重试次数");
+  }
+
+  public synchronized  T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException {
+    if (uri.indexOf("access_token=") != -1) {
+      throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
+    }
+    String accessToken = getAccessToken(false);
+
+    String uriWithAccessToken = uri;
+    uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
+
+    try {
+      return executor.execute(this, uriWithAccessToken, data);
+    } catch (WxErrorException e) {
+      WxError error = e.getError();
+      /*
+       * 发生以下情况时尝试刷新access_token
+       * 40001 获取access_token时AppSecret错误,或者access_token无效
+       * 42001 access_token超时
+       */
+      if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
+        // 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
+        this.getWxMpConfigStorage().expireAccessToken();
+        if (this.getWxMpConfigStorage().autoRefreshToken()) {
+          return this.execute(executor, uri, data);
+        }
+      }
+
+      if (error.getErrorCode() != 0) {
+        this.log.error("\n[URL]:  {}\n[PARAMS]: {}\n[RESPONSE]: {}", uri, data, error);
+        throw new WxErrorException(error);
+      }
+      return null;
+    } catch (IOException e) {
+      this.log.error("\n[URL]:  {}\n[PARAMS]: {}\n[EXCEPTION]: {}", uri, data, e.getMessage());
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public Object getRequestHttpClient() {
+    return this.httpClient;
+  }
+
+  @Override
+  public Object getRequestHttpProxy() {
+    return this.httpProxy;
+  }
+
+
+  @Override
+  public WxMpConfigStorage getWxMpConfigStorage() {
+    return this.wxMpConfigStorage;
+  }
+
+  @Override
+  public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) {
+    this.wxMpConfigStorage = wxConfigProvider;
+    this.initHttpClient();
+  }
+
+  @Override
+  public void setRetrySleepMillis(int retrySleepMillis) {
+    this.retrySleepMillis = retrySleepMillis;
+  }
+
+  @Override
+  public void setMaxRetryTimes(int maxRetryTimes) {
+    this.maxRetryTimes = maxRetryTimes;
+  }
+
+  @Override
+  public WxMpKefuService getKefuService() {
+    return this.kefuService;
+  }
+
+  @Override
+  public WxMpMaterialService getMaterialService() {
+    return this.materialService;
+  }
+
+  @Override
+  public WxMpMenuService getMenuService() {
+    return this.menuService;
+  }
+
+  @Override
+  public WxMpUserService getUserService() {
+    return this.userService;
+  }
+
+  @Override
+  public WxMpUserTagService getUserTagService() {
+    return this.tagService;
+  }
+
+  @Override
+  public WxMpQrcodeService getQrcodeService() {
+    return this.qrCodeService;
+  }
+
+  @Override
+  public WxMpCardService getCardService() {
+    return this.cardService;
+  }
+
+  @Override
+  public WxMpDataCubeService getDataCubeService() {
+    return this.dataCubeService;
+  }
+
+  @Override
+  public WxMpUserBlacklistService getBlackListService() {
+    return this.blackListService;
+  }
+
+  @Override
+  public WxMpStoreService getStoreService() {
+    return this.storeService;
+  }
+
+  @Override
+  public WxMpTemplateMsgService getTemplateMsgService() {
+    return this.templateMsgService;
+  }
+
+  @Override
+  public WxMpDeviceService getDeviceService() {
+    return this.deviceService;
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialDeleteRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialDeleteRequestExecutor.java
index 9502afe6..ef3f31b6 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialDeleteRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialDeleteRequestExecutor.java
@@ -1,9 +1,14 @@
 package me.chanjar.weixin.mp.util.http;
 
+import jodd.http.HttpConnectionProvider;
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.bean.result.WxError;
 import me.chanjar.weixin.common.exception.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
-import me.chanjar.weixin.common.util.http.Utf8ResponseHandler;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler;
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
@@ -24,7 +29,44 @@ public class MaterialDeleteRequestExecutor implements RequestExecutor params = new HashMap<>();
     params.put("media_id", materialId);
     httpPost.setEntity(new StringEntity(WxGsonBuilder.create().toJson(params)));
-    try(CloseableHttpResponse response = httpclient.execute(httpPost)){
+    try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
       String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
       WxError error = WxError.fromJson(responseContent);
       if (error.getErrorCode() != 0) {
@@ -42,9 +84,10 @@ public class MaterialDeleteRequestExecutor implements RequestExecutor params = new HashMap<>();
     params.put("media_id", materialId);
     httpPost.setEntity(new StringEntity(WxGsonBuilder.create().toJson(params)));
-    try(CloseableHttpResponse response = httpclient.execute(httpPost)){
+    try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
       String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
       WxError error = WxError.fromJson(responseContent);
       if (error.getErrorCode() != 0) {
@@ -43,10 +65,29 @@ public class MaterialNewsInfoRequestExecutor implements RequestExecutor {
 
   @Override
-  public WxMpMaterialUploadResult execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, WxMpMaterial material) throws WxErrorException, IOException {
+  public WxMpMaterialUploadResult execute(RequestHttp requestHttp, String uri, WxMpMaterial material) throws WxErrorException, IOException {
+    if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) {
+      CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient();
+      HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy();
+      return executeApache(httpClient, httpProxy, uri, material);
+    }
+    if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) {
+      HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient();
+      ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy();
+      return executeJodd(provider, proxyInfo, uri, material);
+    } else {
+      //这里需要抛出异常,需要优化
+      return null;
+    }
+  }
+
+  private WxMpMaterialUploadResult executeJodd(HttpConnectionProvider provider, ProxyInfo httpProxy, String uri, WxMpMaterial material) throws WxErrorException, IOException {
+    HttpRequest request = HttpRequest.post(uri);
+    if (httpProxy != null) {
+      provider.useProxy(httpProxy);
+    }
+    request.withConnectionProvider(provider);
+
+    if (material == null) {
+      throw new WxErrorException(WxError.newBuilder().setErrorMsg("非法请求,material参数为空").build());
+    }
+
+    File file = material.getFile();
+    if (file == null || !file.exists()) {
+      throw new FileNotFoundException();
+    }
+    request.form("media", file);
+    Map form = material.getForm();
+    if (material.getForm() != null) {
+      request.form("description", WxGsonBuilder.create().toJson(form));
+    }
+
+    HttpResponse response = request.send();
+    String responseContent = response.bodyText();
+    WxError error = WxError.fromJson(responseContent);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    } else {
+      return WxMpMaterialUploadResult.fromJson(responseContent);
+    }
+  }
+
+  private WxMpMaterialUploadResult executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri,
+                                                 WxMpMaterial material) throws WxErrorException, IOException {
     HttpPost httpPost = new HttpPost(uri);
     if (httpProxy != null) {
       RequestConfig response = RequestConfig.custom().setProxy(httpProxy).build();
@@ -40,8 +95,8 @@ public class MaterialUploadRequestExecutor implements RequestExecutor form = material.getForm();
     if (material.getForm() != null) {
       multipartEntityBuilder.addTextBody("description", WxGsonBuilder.create().toJson(form));
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialVideoInfoRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialVideoInfoRequestExecutor.java
index 4eb15e46..fb581892 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialVideoInfoRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialVideoInfoRequestExecutor.java
@@ -1,9 +1,14 @@
 package me.chanjar.weixin.mp.util.http;
 
+import jodd.http.HttpConnectionProvider;
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.bean.result.WxError;
 import me.chanjar.weixin.common.exception.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
-import me.chanjar.weixin.common.util.http.Utf8ResponseHandler;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler;
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import me.chanjar.weixin.mp.bean.material.WxMpMaterialVideoInfoResult;
 import org.apache.http.HttpHost;
@@ -24,7 +29,43 @@ public class MaterialVideoInfoRequestExecutor implements RequestExecutor params = new HashMap<>();
     params.put("media_id", materialId);
     httpPost.setEntity(new StringEntity(WxGsonBuilder.create().toJson(params)));
-    try(CloseableHttpResponse response = httpclient.execute(httpPost)){
+    try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
       String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
       WxError error = WxError.fromJson(responseContent);
       if (error.getErrorCode() != 0) {
@@ -42,9 +83,10 @@ public class MaterialVideoInfoRequestExecutor implements RequestExecutor {
 
@@ -33,7 +37,52 @@ public class MaterialVoiceAndImageDownloadRequestExecutor implements RequestExec
   }
 
   @Override
-  public InputStream execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String materialId) throws WxErrorException, IOException {
+  public InputStream execute(RequestHttp requestHttp, String uri, String materialId) throws WxErrorException, IOException {
+    if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) {
+      CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient();
+      HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy();
+      return executeApache(httpClient, httpProxy, uri, materialId);
+    }
+    if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) {
+      HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient();
+      ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy();
+      return executeJodd(provider, proxyInfo, uri, materialId);
+    } else {
+      //这里需要抛出异常,需要优化
+      return null;
+    }
+  }
+
+  private InputStream executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, String materialId) throws WxErrorException, IOException {
+    HttpRequest request = HttpRequest.post(uri);
+    if (proxyInfo != null) {
+      provider.useProxy(proxyInfo);
+    }
+    request.withConnectionProvider(provider);
+
+    request.query("media_id", materialId);
+    HttpResponse response = request.send();
+    try (InputStream inputStream = new ByteArrayInputStream(response.bodyBytes())) {
+      // 下载媒体文件出错
+      byte[] responseContent = IOUtils.toByteArray(inputStream);
+      String responseContentString = new String(responseContent, "UTF-8");
+      if (responseContentString.length() < 100) {
+        try {
+          WxError wxError = WxGsonBuilder.create().fromJson(responseContentString, WxError.class);
+          if (wxError.getErrorCode() != 0) {
+            throw new WxErrorException(wxError);
+          }
+        } catch (com.google.gson.JsonSyntaxException ex) {
+          return new ByteArrayInputStream(responseContent);
+        }
+      }
+      return new ByteArrayInputStream(responseContent);
+    }
+
+  }
+
+  private InputStream executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri,
+                                    String materialId) throws WxErrorException, IOException {
     HttpPost httpPost = new HttpPost(uri);
     if (httpProxy != null) {
       RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
@@ -44,7 +93,7 @@ public class MaterialVoiceAndImageDownloadRequestExecutor implements RequestExec
     params.put("media_id", materialId);
     httpPost.setEntity(new StringEntity(WxGsonBuilder.create().toJson(params)));
     try (CloseableHttpResponse response = httpclient.execute(httpPost);
-        InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response);){
+         InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response);) {
       // 下载媒体文件出错
       byte[] responseContent = IOUtils.toByteArray(inputStream);
       String responseContentString = new String(responseContent, "UTF-8");
@@ -59,9 +108,10 @@ public class MaterialVoiceAndImageDownloadRequestExecutor implements RequestExec
         }
       }
       return new ByteArrayInputStream(responseContent);
-    }finally {
+    } finally {
       httpPost.releaseConnection();
     }
   }
 
+
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MediaImgUploadRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MediaImgUploadRequestExecutor.java
index 2129e8a7..c22bc310 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MediaImgUploadRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MediaImgUploadRequestExecutor.java
@@ -1,9 +1,14 @@
 package me.chanjar.weixin.mp.util.http;
 
+import jodd.http.HttpConnectionProvider;
+import jodd.http.HttpRequest;
+import jodd.http.HttpResponse;
+import jodd.http.ProxyInfo;
 import me.chanjar.weixin.common.bean.result.WxError;
 import me.chanjar.weixin.common.exception.WxErrorException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
-import me.chanjar.weixin.common.util.http.Utf8ResponseHandler;
+import me.chanjar.weixin.common.util.http.RequestHttp;
+import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler;
 import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult;
 import org.apache.http.HttpEntity;
 import org.apache.http.HttpHost;
@@ -22,8 +27,50 @@ import java.io.IOException;
  * @author miller
  */
 public class MediaImgUploadRequestExecutor implements RequestExecutor {
+
   @Override
-  public WxMediaImgUploadResult execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, File data) throws WxErrorException, IOException {
+  public WxMediaImgUploadResult execute(RequestHttp requestHttp, String uri, File data) throws WxErrorException, IOException {
+    if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) {
+      CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient();
+      HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy();
+      return executeApache(httpClient, httpProxy, uri, data);
+    }
+    if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) {
+      HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient();
+      ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy();
+      return executeJodd(provider, proxyInfo, uri, data);
+    } else {
+      //这里需要抛出异常,需要优化
+      return null;
+    }
+
+  }
+
+
+  private WxMediaImgUploadResult executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, File data) throws WxErrorException, IOException {
+    if (data == null) {
+      throw new WxErrorException(WxError.newBuilder().setErrorMsg("文件对象为空").build());
+    }
+
+    HttpRequest request = HttpRequest.post(uri);
+    if (proxyInfo != null) {
+      provider.useProxy(proxyInfo);
+    }
+    request.withConnectionProvider(provider);
+
+    request.form("media", data);
+    HttpResponse response = request.send();
+    String responseContent = response.bodyText();
+    WxError error = WxError.fromJson(responseContent);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    }
+
+    return WxMediaImgUploadResult.fromJson(responseContent);
+  }
+
+  private WxMediaImgUploadResult executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri,
+                                               File data) throws WxErrorException, IOException {
     if (data == null) {
       throw new WxErrorException(WxError.newBuilder().setErrorMsg("文件对象为空").build());
     }
@@ -52,4 +99,5 @@ public class MediaImgUploadRequestExecutor implements RequestExecutor {
 
   @Override
-  public File execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, 
-      WxMpQrCodeTicket ticket) throws WxErrorException, IOException {
+  public File execute(RequestHttp requestHttp, String uri,
+                      WxMpQrCodeTicket ticket) throws WxErrorException, IOException {
+    if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) {
+      CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient();
+      HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy();
+      return executeApache(httpClient, httpProxy, uri, ticket);
+    }
+    if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) {
+      HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient();
+      ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy();
+      return executeJodd(provider, proxyInfo, uri, ticket);
+    } else {
+      //这里需要抛出异常,需要优化
+      return null;
+    }
+  }
+
+  private File executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, WxMpQrCodeTicket ticket) throws WxErrorException, IOException {
     if (ticket != null) {
       if (uri.indexOf('?') == -1) {
         uri += '?';
       }
-      uri += uri.endsWith("?") 
-          ? "ticket=" + URLEncoder.encode(ticket.getTicket(), "UTF-8") 
-          : "&ticket=" + URLEncoder.encode(ticket.getTicket(), "UTF-8");
+      uri += uri.endsWith("?")
+        ? "ticket=" + URLEncoder.encode(ticket.getTicket(), "UTF-8")
+        : "&ticket=" + URLEncoder.encode(ticket.getTicket(), "UTF-8");
     }
-    
+
+    HttpRequest request = HttpRequest.get(uri);
+    if (proxyInfo != null) {
+      provider.useProxy(proxyInfo);
+    }
+    request.withConnectionProvider(provider);
+
+    HttpResponse response = request.send();
+    String contentTypeHeader = response.header("Content-Type");
+    if (MimeTypes.MIME_TEXT_PLAIN.equals(contentTypeHeader)) {
+      String responseContent = response.bodyText();
+      throw new WxErrorException(WxError.fromJson(responseContent));
+    }
+    try (InputStream inputStream = new ByteArrayInputStream(response.bodyBytes())) {
+      return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg");
+    }
+  }
+
+  private File executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri,
+                             WxMpQrCodeTicket ticket) throws WxErrorException, IOException {
+    if (ticket != null) {
+      if (uri.indexOf('?') == -1) {
+        uri += '?';
+      }
+      uri += uri.endsWith("?")
+        ? "ticket=" + URLEncoder.encode(ticket.getTicket(), "UTF-8")
+        : "&ticket=" + URLEncoder.encode(ticket.getTicket(), "UTF-8");
+    }
+
     HttpGet httpGet = new HttpGet(uri);
     if (httpProxy != null) {
       RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
@@ -47,7 +98,7 @@ public class QrCodeRequestExecutor implements RequestExecutor 0) {
         // 出错
@@ -60,7 +111,6 @@ public class QrCodeRequestExecutor implements RequestExecutor T executeInternal(
+      public synchronized  T executeInternal(
         RequestExecutor executor, String uri, E data)
         throws WxErrorException {
         this.log.info("Executed");
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpStoreServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpStoreServiceImplTest.java
index e4edca74..baa34443 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpStoreServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpStoreServiceImplTest.java
@@ -25,7 +25,7 @@ public class WxMpStoreServiceImplTest {
   private WxMpService wxMpService;
 
   /**
-   * Test method for {@link me.chanjar.weixin.mp.api.impl.WxMpStoreServiceImpl#add(me.chanjar.weixin.mp.bean.store.WxMpStoreBaseInfo)}.
+   * Test method for {@link WxMpStoreServiceImpl#add(me.chanjar.weixin.mp.bean.store.WxMpStoreBaseInfo)}.
    *
    * @throws WxErrorException
    */
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java
index 31ad47b6..2cfaec54 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java
@@ -6,7 +6,7 @@ import com.thoughtworks.xstream.XStream;
 import me.chanjar.weixin.common.util.xml.XStreamInitializer;
 import me.chanjar.weixin.mp.api.WxMpConfigStorage;
 import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
+import me.chanjar.weixin.mp.api.impl.apache.WxMpServiceImpl;
 
 import java.io.IOException;
 import java.io.InputStream;
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java
index 1c189376..e2c75f83 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java
@@ -5,7 +5,7 @@ import me.chanjar.weixin.mp.api.WxMpConfigStorage;
 import me.chanjar.weixin.mp.api.WxMpMessageHandler;
 import me.chanjar.weixin.mp.api.WxMpMessageRouter;
 import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
+import me.chanjar.weixin.mp.api.impl.apache.WxMpServiceImpl;
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.servlet.ServletHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
diff --git a/weixin-java-pay/build.gradle b/weixin-java-pay/build.gradle
new file mode 100644
index 00000000..2f0c7e9a
--- /dev/null
+++ b/weixin-java-pay/build.gradle
@@ -0,0 +1,12 @@
+description = 'WeiXin Java Tools - PAY'
+dependencies {
+  compile project(':weixin-java-common')
+  testCompile group: 'junit', name: 'junit', version: '4.11'
+  testCompile group: 'org.testng', name: 'testng', version: '6.8.7'
+  testCompile group: 'org.mockito', name: 'mockito-all', version: '1.9.5'
+  testCompile group: 'com.google.inject', name: 'guice', version: '3.0'
+  testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version: '9.3.0.RC0'
+  testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version: '9.3.0.RC0'
+  testCompile group: 'joda-time', name: 'joda-time', version: '2.9.4'
+}
+test.useTestNG()

From 2029cd0bb7e6f0d3a30bb21396857bd9ffd4a1d9 Mon Sep 17 00:00:00 2001
From: Binary Wang 
Date: Wed, 26 Apr 2017 23:19:12 +0800
Subject: [PATCH 7/7] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BC=81=E4=B8=9A?=
 =?UTF-8?q?=E5=8F=B7=E4=B8=AD=20WxCpMessage=E8=BD=ACjson=E6=97=B6=E4=B8=A2?=
 =?UTF-8?q?=E5=A4=B1safe=E5=B1=9E=E6=80=A7=E7=9A=84=E9=97=AE=E9=A2=98#196?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../chanjar/weixin/cp/util/json/WxCpMessageGsonAdapter.java   | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMessageGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMessageGsonAdapter.java
index 949709ab..1cf2d312 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMessageGsonAdapter.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMessageGsonAdapter.java
@@ -61,6 +61,10 @@ public class WxCpMessageGsonAdapter implements JsonSerializer {
       messageJson.add("voice", voice);
     }
 
+    if (StringUtils.isNotBlank(message.getSafe())) {
+      messageJson.addProperty("safe", message.getSafe());
+    }
+
     if (WxConsts.CUSTOM_MSG_VIDEO.equals(message.getMsgType())) {
       JsonObject video = new JsonObject();
       video.addProperty("media_id", message.getMediaId());