diff --git a/pom.xml b/pom.xml index 57ad67f08..026115437 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 1c0e0214c..0ff1d35b7 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 6c7366b5b..e666caab3 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 57024f500..b69438fa0 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 000000000..02225d46d --- /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 ec221e28b..cb414d2bc 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 bdea8573c..f68732893 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 c932445d3..87a86dbae 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 030fe98fe..2bb1f0724 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 0cc1562b5..d4cea91da 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 148ef9650..697d4695e 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 eb1e57caf..3484db3b8 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 867dd6bb6..b1eeb8274 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 925139656..07f38ebbf 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 bd0b322f7..faa01d09e 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 0105e78d5..4369868f1 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 000000000..8c41f0421 --- /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 2a694ff32..80265168d 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 c0cc3251a..1a3fb7955 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 d9fb2605d..3f297968f 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 aa26e9620..d5ff3a3a8 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 4d5a7a91f..450cefa26 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 c837e51e1..a6fdfa9d8 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 894bb3b67..6fecf6e7e 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 e1b005bd8..724368e80 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 b7bcf7a37..968cfea94 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 dc667d88b..eb78d6671 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 ba92bc0c0..04f960ed6 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 d4a36c65b..35a23f709 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 f4053899e..dbff2a7ab 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 4bd1daec7..9181a401e 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 cf1401a57..0b9685941 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 c214c46d9..38dde803a 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 2e6bf88e5..9e64d0431 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 5c9423651..3f7378e3d 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 09a68adf8..4667f3279 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 af9fa54c3..4242063bd 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 d1bdae1e2..a1fee9370 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 406e87126..5e6f863a9 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 000000000..fa342f66d
--- /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 9502afe66..ef3f31b6e 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 4eb15e46a..fb5818926 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,18 +37,32 @@ public class MaterialVoiceAndImageDownloadRequestExecutor implements RequestExec
   }
 
   @Override
-  public InputStream execute(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();
-      httpPost.setConfig(config);
+  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;
+    }
+  }
 
-    Map params = new HashMap<>();
-    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);){
+  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");
@@ -59,9 +77,41 @@ public class MaterialVoiceAndImageDownloadRequestExecutor implements RequestExec
         }
       }
       return new ByteArrayInputStream(responseContent);
-    }finally {
+    }
+
+  }
+
+  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();
+      httpPost.setConfig(config);
+    }
+
+    Map params = new HashMap<>();
+    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);) {
+      // 下载媒体文件出错
+      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);
+    } 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 2129e8a7a..c22bc3109 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 e4edca749..baa34443f 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 31ad47b6f..2cfaec543 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 1c1893762..e2c75f83d 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 000000000..2f0c7e9a8
--- /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()