From 48d6f10cb2888464728f0dfba8a2ce4daa1182d9 Mon Sep 17 00:00:00 2001 From: crskyp Date: Thu, 27 Apr 2017 23:16:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E5=8F=96=E5=85=AC=E5=85=B1=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E3=80=81=E5=AE=9E=E7=8E=B0okhttp=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E6=96=B9=E5=BC=8F=20(#199)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、提取了公共代码,添加AbstractWxMPService、AbstractWxCPService类 2、实现了okhttp请求方式 --- pom.xml | 5 +- .../util/http/AbstractRequestExecutor.java | 43 ++ .../http/MediaDownloadRequestExecutor.java | 116 +++- .../util/http/MediaUploadRequestExecutor.java | 77 ++- .../common/util/http/RequestExecutor.java | 44 ++ .../weixin/common/util/http/RequestHttp.java | 6 +- .../util/http/SimpleGetRequestExecutor.java | 81 ++- .../util/http/SimplePostRequestExecutor.java | 76 ++- .../util/http/okhttp/OkhttpProxyInfo.java | 117 ++++ .../me/chanjar/weixin/cp/api/WxCpService.java | 12 + .../cp/api/impl/AbstractWxCpService.java | 632 +++++++++++++++++ .../cp/api/impl/apache/WxCpServiceImpl.java | 634 +----------------- .../cp/api/impl/jodd/WxCpServiceImpl.java | 633 +---------------- .../cp/api/impl/okhttp/WxCpServiceImpl.java | 95 +++ .../cp/api/impl/apache/WxCpBaseAPITest.java | 2 +- .../api/impl/apache/WxCpMessageAPITest.java | 2 +- .../me/chanjar/weixin/mp/api/WxMpService.java | 9 +- .../mp/api/impl/AbstractWxMpService.java | 445 ++++++++++++ .../mp/api/impl/apache/WxMpServiceImpl.java | 518 ++------------ .../mp/api/impl/jodd/WxMpServiceImpl.java | 474 +------------ .../mp/api/impl/okhttp/WxMpServiceImpl.java | 96 +++ .../http/MaterialDeleteRequestExecutor.java | 93 ++- .../http/MaterialNewsInfoRequestExecutor.java | 62 +- .../http/MaterialUploadRequestExecutor.java | 78 ++- .../MaterialVideoInfoRequestExecutor.java | 65 +- ...lVoiceAndImageDownloadRequestExecutor.java | 76 ++- .../http/MediaImgUploadRequestExecutor.java | 76 ++- .../mp/util/http/QrCodeRequestExecutor.java | 69 +- .../weixin/mp/api/WxMpBusyRetryTest.java | 2 + .../weixin/mp/api/test/ApiTestModule.java | 1 + .../weixin/mp/demo/WxMpDemoServer.java | 1 + weixin-java-pay/pom.xml | 7 - 32 files changed, 2199 insertions(+), 2448 deletions(-) create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/AbstractRequestExecutor.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkhttpProxyInfo.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/AbstractWxCpService.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/okhttp/WxCpServiceImpl.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/AbstractWxMpService.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/okhttp/WxMpServiceImpl.java diff --git a/pom.xml b/pom.xml index 02611543..5e219eb6 100644 --- a/pom.xml +++ b/pom.xml @@ -107,14 +107,15 @@ 1.10 9.3.0.RC0 2.9.0 + + 3.7 org.jodd jodd-http - 3.7 - + ${jodd-http.version} com.squareup.okhttp3 diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/AbstractRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/AbstractRequestExecutor.java new file mode 100644 index 00000000..23881e66 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/AbstractRequestExecutor.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.common.util.http; + +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.impl.client.CloseableHttpClient; + +import jodd.http.HttpConnectionProvider; +import jodd.http.ProxyInfo; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.http.okhttp.OkhttpProxyInfo; +import okhttp3.ConnectionPool; + +/** + * Created by ecoolper on 2017/4/27. + */ +public abstract class AbstractRequestExecutor implements RequestExecutor { + + @Override + public T execute(RequestHttp requestHttp, String uri, E data) throws WxErrorException, IOException{ + if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) { + //apache-http请求 + CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient(); + HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy(); + return executeApache(httpClient, httpProxy, uri, data); + } + if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) { + //jodd-http请求 + HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient(); + ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy(); + return executeJodd(provider, proxyInfo, uri, data); + } else if (requestHttp.getRequestHttpClient() instanceof ConnectionPool) { + //okhttp请求 + ConnectionPool pool = (ConnectionPool) requestHttp.getRequestHttpClient(); + OkhttpProxyInfo proxyInfo = (OkhttpProxyInfo) requestHttp.getRequestHttpProxy(); + return executeOkhttp(pool, proxyInfo, uri, data); + } else { + //TODO 这里需要抛出异常,需要优化 + return null; + } + } + +} 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 b69438fa..e95f02de 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 @@ -5,10 +5,14 @@ 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.bean.result.WxMediaUploadResult; 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 me.chanjar.weixin.common.util.http.okhttp.OkhttpProxyInfo; +import okhttp3.*; + import org.apache.commons.lang3.StringUtils; import org.apache.http.Header; import org.apache.http.HttpHost; @@ -22,6 +26,8 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -31,7 +37,7 @@ import java.util.regex.Pattern; * * @author Daniel Qian */ -public class MediaDownloadRequestExecutor implements RequestExecutor { +public class MediaDownloadRequestExecutor extends AbstractRequestExecutor { private File tmpDirFile; @@ -39,24 +45,7 @@ public class MediaDownloadRequestExecutor implements RequestExecutor 0) { if (contentTypeHeader[0].getValue().startsWith(ContentType.APPLICATION_JSON.getMimeType())) { @@ -122,7 +111,7 @@ public class MediaDownloadRequestExecutor implements RequestExecutor { - - @Override - 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; - } - - - } +public class MediaUploadRequestExecutor extends AbstractRequestExecutor { /** * apache-http实现方式 + * * @param httpclient * @param httpProxy * @param uri @@ -57,7 +45,7 @@ public class MediaUploadRequestExecutor implements RequestExecutor { */ T execute(RequestHttp requestHttp, String uri, E data) throws WxErrorException, IOException; + /** + * apache-http实现方式 + * @param httpclient + * @param httpProxy + * @param uri + * @param data + * @return + * @throws WxErrorException + * @throws IOException + */ + T executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, E data) throws WxErrorException, IOException; + + /** + * jodd-http实现方式 + * @param provider + * @param proxyInfo + * @param uri + * @param data + * @return + * @throws WxErrorException + * @throws IOException + */ + T executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, E data) throws WxErrorException, IOException; + + + /** okhttp实现方式 + * @param pool + * @param proxyInfo + * @param uri + * @param data + * @return + * @throws WxErrorException + * @throws IOException + */ + T executeOkhttp(ConnectionPool pool, final OkhttpProxyInfo proxyInfo, 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 index 02225d46..5af68e07 100644 --- 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 @@ -3,18 +3,18 @@ package me.chanjar.weixin.common.util.http; /** * Created by ecoolper on 2017/4/22. */ -public interface RequestHttp { +public interface RequestHttp { /** * 返回httpClient * @return */ - Object getRequestHttpClient(); + H getRequestHttpClient(); /** * 返回httpProxy * @return */ - Object getRequestHttpProxy(); + P 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 cb414d2b..5edb70f3 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 @@ -7,6 +7,10 @@ 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 me.chanjar.weixin.common.util.http.okhttp.OkhttpProxyInfo; +import okhttp3.*; + import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; @@ -14,34 +18,21 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.util.concurrent.TimeUnit; /** * 简单的GET请求执行器,请求的参数是String, 返回的结果也是String * * @author Daniel Qian */ -public class SimpleGetRequestExecutor implements RequestExecutor { - - @Override - 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; - } - } +public class SimpleGetRequestExecutor extends AbstractRequestExecutor { /** * apache-http实现方式 + * * @param httpclient * @param httpProxy * @param uri @@ -50,7 +41,7 @@ public class SimpleGetRequestExecutor implements RequestExecutor * @throws WxErrorException * @throws IOException */ - private String executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException { + public String executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException { if (queryParam != null) { if (uri.indexOf('?') == -1) { uri += '?'; @@ -78,6 +69,7 @@ public class SimpleGetRequestExecutor implements RequestExecutor /** * jodd-http实现方式 + * * @param provider * @param proxyInfo * @param uri @@ -86,7 +78,7 @@ public class SimpleGetRequestExecutor implements RequestExecutor * @throws WxErrorException * @throws IOException */ - private String executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, String queryParam) throws WxErrorException, IOException { + public String executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, String queryParam) throws WxErrorException, IOException { if (queryParam != null) { if (uri.indexOf('?') == -1) { uri += '?'; @@ -108,4 +100,53 @@ public class SimpleGetRequestExecutor implements RequestExecutor return responseContent; } + + /** + * okHttp实现方式 + * + * @param pool + * @param proxyInfo + * @param uri + * @param queryParam + * @return + * @throws WxErrorException + * @throws IOException + */ + public String executeOkhttp(ConnectionPool pool, final OkhttpProxyInfo proxyInfo, String uri, String queryParam) throws WxErrorException, IOException { + if (queryParam != null) { + if (uri.indexOf('?') == -1) { + uri += '?'; + } + uri += uri.endsWith("?") ? queryParam : '&' + queryParam; + } + + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(pool); + //设置代理 + if (proxyInfo != null) { + clientBuilder.proxy(proxyInfo.getProxy()); + } + //设置授权 + clientBuilder.authenticator(new Authenticator() { + @Override + public Request authenticate(Route route, Response response) throws IOException { + String credential = Credentials.basic(proxyInfo.getProxyUsername(), proxyInfo.getProxyPassword()); + return response.request().newBuilder() + .header("Authorization", credential) + .build(); + } + }); + //得到httpClient + OkHttpClient client =clientBuilder.build(); + + Request request = new Request.Builder().url(uri).build(); + + Response response = client.newCall(request).execute(); + String responseContent = response.body().toString(); + 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 f6873289..5517ab63 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 @@ -7,6 +7,10 @@ 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 me.chanjar.weixin.common.util.http.okhttp.OkhttpProxyInfo; +import okhttp3.*; + import org.apache.http.Consts; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; @@ -23,27 +27,11 @@ import java.io.IOException; * * @author Daniel Qian */ -public class SimplePostRequestExecutor implements RequestExecutor { - - @Override - 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; - } - } +public class SimplePostRequestExecutor extends AbstractRequestExecutor { /** * apache-http实现方式 + * * @param httpclient * @param httpProxy * @param uri @@ -52,7 +40,8 @@ public class SimplePostRequestExecutor implements RequestExecutornull if + * no authentication required. + */ + public String getProxyUsername() { + return proxyUsername; + } + + /** + * Returns proxy password or null. + */ + public String getProxyPassword() { + return proxyPassword; + } + + /** + * 返回 java.net.Proxy + * @return + */ + public Proxy getProxy() { + Proxy proxy = null; + if (getProxyType().equals(ProxyType.SOCKS5)) { + proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(getProxyAddress(), getProxyPort())); + } else if (getProxyType().equals(ProxyType.SOCKS4)) { + proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(getProxyAddress(), getProxyPort())); + } else if (getProxyType().equals(ProxyType.HTTP)) { + proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getProxyAddress(), getProxyPort())); + } else if (getProxyType().equals(ProxyType.NONE)) { + proxy = new Proxy(Proxy.Type.DIRECT, new InetSocketAddress(getProxyAddress(), getProxyPort())); + } + return proxy; + } +} 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 faa01d09..f02b7589 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 @@ -551,4 +551,16 @@ public interface WxCpService { * 获取异步任务结果 */ String getTaskResult(String joinId) throws WxErrorException; + + /** + * 初始化http请求对象 + */ + void initHttp(); + + /** + * 获取WxMpConfigStorage 对象 + * + * @return WxMpConfigStorage + */ + WxCpConfigStorage getWxCpConfigStorage(); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/AbstractWxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/AbstractWxCpService.java new file mode 100644 index 00000000..82fcdef9 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/AbstractWxCpService.java @@ -0,0 +1,632 @@ +package me.chanjar.weixin.cp.api.impl; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.UUID; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.*; +import com.google.gson.reflect.TypeToken; + +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; + +public abstract class AbstractWxCpService implements WxCpService, RequestHttp { + + protected final Logger log = LoggerFactory.getLogger(AbstractWxCpService.class); + + /** + * 全局的是否正在刷新access token的锁 + */ + protected final Object globalAccessTokenRefreshLock = new Object(); + + /** + * 全局的是否正在刷新jsapi_ticket的锁 + */ + protected final Object globalJsapiTicketRefreshLock = new Object(); + + protected WxCpConfigStorage configStorage; + + + 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 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); + } + } + + @Override + public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) { + this.configStorage = wxConfigProvider; + this.initHttp(); + } + + @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; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/apache/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/apache/WxCpServiceImpl.java index 4369868f..cff7e13d 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/apache/WxCpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/apache/WxCpServiceImpl.java @@ -1,92 +1,35 @@ package me.chanjar.weixin.cp.api.impl.apache; -import com.google.gson.*; -import com.google.gson.reflect.TypeToken; + 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.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; -import me.chanjar.weixin.cp.bean.WxCpUser; -import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; -import org.apache.commons.lang3.StringUtils; +import me.chanjar.weixin.cp.api.impl.AbstractWxCpService; + import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.CloseableHttpClient; -import 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; +public class WxCpServiceImpl extends AbstractWxCpService { protected CloseableHttpClient httpClient; - protected HttpHost 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; - } + public CloseableHttpClient getRequestHttpClient() { + return httpClient; } @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); + public HttpHost getRequestHttpProxy() { + return httpProxy; } @Override @@ -108,7 +51,7 @@ public class WxCpServiceImpl implements WxCpService, RequestHttp { httpGet.setConfig(config); } String resultContent = null; - try (CloseableHttpClient httpclient = getHttpclient(); + try (CloseableHttpClient httpclient = getRequestHttpClient(); CloseableHttpResponse response = httpclient.execute(httpGet)) { resultContent = new BasicResponseHandler().handleResponse(response); } finally { @@ -131,491 +74,7 @@ public class WxCpServiceImpl implements WxCpService, RequestHttp { } @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 CloseableHttpClient getHttpclient() { - return this.httpClient; - } - - @Override - public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) { - this.configStorage = wxConfigProvider; + public void initHttp() { ApacheHttpClientBuilder apacheHttpClientBuilder = this.configStorage .getApacheHttpClientBuilder(); if (null == apacheHttpClientBuilder) { @@ -635,76 +94,7 @@ public class WxCpServiceImpl implements WxCpService, RequestHttp { } @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; + public WxCpConfigStorage getWxCpConfigStorage() { + return this.configStorage; } } 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 index 8c41f042..d569f262 100644 --- 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 @@ -1,85 +1,25 @@ 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; +import me.chanjar.weixin.cp.api.impl.AbstractWxCpService; +public class WxCpServiceImpl extends AbstractWxCpService { 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); + public HttpConnectionProvider getRequestHttpClient() { + return httpClient; } @Override - public String getAccessToken() throws WxErrorException { - return getAccessToken(false); + public ProxyInfo getRequestHttpProxy() { + return httpProxy; } @Override @@ -116,493 +56,7 @@ public class WxCpServiceImpl implements WxCpService, RequestHttp { } @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; - + public void initHttp() { if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) { httpProxy = new ProxyInfo(ProxyInfo.ProxyType.HTTP, configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword()); } @@ -611,76 +65,7 @@ public class WxCpServiceImpl implements WxCpService, RequestHttp { } @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; + public WxCpConfigStorage getWxCpConfigStorage() { + return this.configStorage; } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/okhttp/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/okhttp/WxCpServiceImpl.java new file mode 100644 index 00000000..1a9c2ac8 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/okhttp/WxCpServiceImpl.java @@ -0,0 +1,95 @@ +package me.chanjar.weixin.cp.api.impl.okhttp; + +import java.io.IOException; + +import jodd.http.*; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.http.okhttp.OkhttpProxyInfo; +import me.chanjar.weixin.cp.api.WxCpConfigStorage; +import me.chanjar.weixin.cp.api.impl.AbstractWxCpService; +import okhttp3.*; + +public class WxCpServiceImpl extends AbstractWxCpService { + protected ConnectionPool httpClient; + protected OkhttpProxyInfo httpProxy; + + + @Override + public ConnectionPool getRequestHttpClient() { + return httpClient; + } + + @Override + public OkhttpProxyInfo getRequestHttpProxy() { + return httpProxy; + } + + @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(); + + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(httpClient); + //设置代理 + if (httpProxy != null) { + clientBuilder.proxy(getRequestHttpProxy().getProxy()); + } + //设置授权 + clientBuilder.authenticator(new Authenticator() { + @Override + public Request authenticate(Route route, Response response) throws IOException { + String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword()); + return response.request().newBuilder() + .header("Authorization", credential) + .build(); + } + }); + //得到httpClient + OkHttpClient client = clientBuilder.build(); + //请求的request + Request request = new Request.Builder().url(url).get().build(); + Response response = null; + try { + response = client.newCall(request).execute(); + } catch (IOException e) { + e.printStackTrace(); + } + String resultContent = response.body().toString(); + 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 void initHttp() { + WxCpConfigStorage configStorage = this.configStorage; + + if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) { + httpProxy = new OkhttpProxyInfo(OkhttpProxyInfo.ProxyType.SOCKS5, configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword()); + } + + httpClient = new ConnectionPool(); + } + + @Override + public WxCpConfigStorage getWxCpConfigStorage() { + return this.configStorage; + } +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpBaseAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpBaseAPITest.java index 6fecf6e7..7ed63b77 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpBaseAPITest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpBaseAPITest.java @@ -22,7 +22,7 @@ public class WxCpBaseAPITest { protected WxCpServiceImpl wxService; public void testRefreshAccessToken() throws WxErrorException { - WxCpConfigStorage configStorage = this.wxService.configStorage; + WxCpConfigStorage configStorage = this.wxService.getWxCpConfigStorage(); String before = configStorage.getAccessToken(); this.wxService.getAccessToken(false); diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpMessageAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpMessageAPITest.java index 968cfea9..470323f2 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpMessageAPITest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/apache/WxCpMessageAPITest.java @@ -21,7 +21,7 @@ public class WxCpMessageAPITest { protected WxCpServiceImpl wxService; public void testSendCustomMessage() throws WxErrorException { - ApiTestModule.WxXmlCpInMemoryConfigStorage configStorage = (ApiTestModule.WxXmlCpInMemoryConfigStorage) this.wxService.configStorage; + ApiTestModule.WxXmlCpInMemoryConfigStorage configStorage = (ApiTestModule.WxXmlCpInMemoryConfigStorage) this.wxService.getWxCpConfigStorage(); WxCpMessage message1 = new WxCpMessage(); message1.setAgentId(configStorage.getAgentId()); message1.setMsgType(WxConsts.CUSTOM_MSG_TEXT); 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 0b968594..20cb3064 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 @@ -344,14 +344,11 @@ public interface WxMpService { */ WxMpDeviceService getDeviceService(); - /** - * @return - */ - //Object getHttpclient(); /** - * @return + * 初始化http请求对象 */ - //Object getHttpProxy(); + void initHttp(); + } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/AbstractWxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/AbstractWxMpService.java new file mode 100644 index 00000000..5c6b58eb --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/AbstractWxMpService.java @@ -0,0 +1,445 @@ +package me.chanjar.weixin.mp.api.impl; + +import java.io.IOException; +import java.util.concurrent.locks.Lock; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +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.bean.*; +import me.chanjar.weixin.mp.bean.result.*; + +public abstract class AbstractWxMpService 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 int retrySleepMillis = 1000; + private int maxRetryTimes = 5; + + + @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 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 String getAccessToken() throws WxErrorException { + return getAccessToken(false); + } + + @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); + } + + /** + * 向微信端发送请求,在这里执行的策略是当发生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 WxMpConfigStorage getWxMpConfigStorage() { + return this.wxMpConfigStorage; + } + + @Override + public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) { + this.wxMpConfigStorage = wxConfigProvider; + this.initHttp(); + } + + @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/api/impl/apache/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/apache/WxMpServiceImpl.java index 5e6f863a..8c8085da 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/apache/WxMpServiceImpl.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/apache/WxMpServiceImpl.java @@ -1,73 +1,58 @@ package me.chanjar.weixin.mp.api.impl.apache; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -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.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 java.io.IOException; +import java.util.concurrent.locks.Lock; + import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.BasicResponseHandler; import org.apache.http.impl.client.CloseableHttpClient; -import 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(); +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; +import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; +import me.chanjar.weixin.mp.api.WxMpConfigStorage; +import me.chanjar.weixin.mp.api.impl.AbstractWxMpService; - 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); +/** + * apache-http方式实现 + */ +public class WxMpServiceImpl extends AbstractWxMpService { private CloseableHttpClient httpClient; private HttpHost httpProxy; - private int retrySleepMillis = 1000; - private int maxRetryTimes = 5; @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; - } + public CloseableHttpClient getRequestHttpClient() { + return httpClient; } @Override - public String getAccessToken() throws WxErrorException { - return getAccessToken(false); + public HttpHost getRequestHttpProxy() { + return httpProxy; + } + + @Override + public void initHttp() { + WxMpConfigStorage configStorage = this.getWxMpConfigStorage(); + ApacheHttpClientBuilder apacheHttpClientBuilder = configStorage.getApacheHttpClientBuilder(); + if (null == apacheHttpClientBuilder) { + apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get(); + } + + apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost()) + .httpProxyPort(configStorage.getHttpProxyPort()) + .httpProxyUsername(configStorage.getHttpProxyUsername()) + .httpProxyPassword(configStorage.getHttpProxyPassword()); + + if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) { + this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort()); + } + + this.httpClient = apacheHttpClientBuilder.build(); } @Override @@ -86,11 +71,11 @@ public class WxMpServiceImpl implements WxMpService,RequestHttp { + this.getWxMpConfigStorage().getSecret(); try { HttpGet httpGet = new HttpGet(url); - if (this.httpProxy != null) { - RequestConfig config = RequestConfig.custom().setProxy(this.httpProxy).build(); + if (this.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build(); httpGet.setConfig(config); } - try (CloseableHttpResponse response = getHttpclient().execute(httpGet)) { + try (CloseableHttpResponse response = getRequestHttpClient().execute(httpGet)) { String resultContent = new BasicResponseHandler().handleResponse(response); WxError error = WxError.fromJson(resultContent); if (error.getErrorCode() != 0) { @@ -111,425 +96,4 @@ public class WxMpServiceImpl implements WxMpService,RequestHttp { } 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); - } - - /** - * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 - */ - @Override - 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 HttpHost getHttpProxy() { - return this.httpProxy; - } - - //@Override - public CloseableHttpClient getHttpclient() { - return this.httpClient; - } - - private void initHttpClient() { - WxMpConfigStorage configStorage = this.getWxMpConfigStorage(); - ApacheHttpClientBuilder apacheHttpClientBuilder = configStorage.getApacheHttpClientBuilder(); - if (null == apacheHttpClientBuilder) { - apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get(); - } - - apacheHttpClientBuilder.httpProxyHost(configStorage.getHttpProxyHost()) - .httpProxyPort(configStorage.getHttpProxyPort()) - .httpProxyUsername(configStorage.getHttpProxyUsername()) - .httpProxyPassword(configStorage.getHttpProxyPassword()); - - if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) { - this.httpProxy = new HttpHost(configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort()); - } - - this.httpClient = apacheHttpClientBuilder.build(); - } - - @Override - public 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; - } - - @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 index fa342f66..30b10be6 100644 --- 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 @@ -1,56 +1,35 @@ 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 { +/** + * jodd-http方式实现 + */ +public class WxMpServiceImpl extends AbstractWxMpService { + private HttpConnectionProvider httpClient; + private ProxyInfo httpProxy; - private static final JsonParser JSON_PARSER = new JsonParser(); + @Override + public HttpConnectionProvider getRequestHttpClient() { + return httpClient; + } - 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); + @Override + public ProxyInfo getRequestHttpProxy() { + return httpProxy; + } - private HttpConnectionProvider httpClient; - private ProxyInfo httpProxy; - private int retrySleepMillis = 1000; - private int maxRetryTimes = 5; + @Override + public void initHttp() { - private void initHttpClient() { WxMpConfigStorage configStorage = this.getWxMpConfigStorage(); if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) { @@ -60,20 +39,6 @@ public class WxMpServiceImpl implements WxMpService,RequestHttp { 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 { @@ -91,9 +56,11 @@ public class WxMpServiceImpl implements WxMpService,RequestHttp { + this.getWxMpConfigStorage().getSecret(); HttpRequest request = HttpRequest.get(url); - if (this.httpProxy != null) { + + if (this.getRequestHttpProxy() != null) { SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider(); - provider.useProxy(httpProxy); + provider.useProxy(getRequestHttpProxy()); + request.withConnectionProvider(provider); } HttpResponse response = request.send(); @@ -112,405 +79,4 @@ public class WxMpServiceImpl implements WxMpService,RequestHttp { 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/api/impl/okhttp/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/okhttp/WxMpServiceImpl.java new file mode 100644 index 00000000..2eebb5ff --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/okhttp/WxMpServiceImpl.java @@ -0,0 +1,96 @@ +package me.chanjar.weixin.mp.api.impl.okhttp; + +import java.io.IOException; +import java.util.concurrent.locks.Lock; + +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.http.okhttp.OkhttpProxyInfo; +import me.chanjar.weixin.mp.api.*; +import me.chanjar.weixin.mp.api.impl.*; +import okhttp3.*; + +public class WxMpServiceImpl extends AbstractWxMpService { + + + private ConnectionPool httpClient; + private OkhttpProxyInfo httpProxy; + + + @Override + public ConnectionPool getRequestHttpClient() { + return httpClient; + } + + @Override + public OkhttpProxyInfo getRequestHttpProxy() { + return httpProxy; + } + + + @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(); + + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(httpClient); + //设置代理 + if (httpProxy != null) { + clientBuilder.proxy(getRequestHttpProxy().getProxy()); + } + //设置授权 + clientBuilder.authenticator(new Authenticator() { + @Override + public Request authenticate(Route route, Response response) throws IOException { + String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword()); + return response.request().newBuilder() + .header("Authorization", credential) + .build(); + } + }); + //得到httpClient + OkHttpClient client = clientBuilder.build(); + + Request request =new Request.Builder().url(url).get().build(); + Response response =client.newCall(request).execute(); + String resultContent = response.body().toString(); + 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()); + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + lock.unlock(); + } + return this.getWxMpConfigStorage().getAccessToken(); + } + + @Override + public void initHttp() { + WxMpConfigStorage configStorage = this.getWxMpConfigStorage(); + + if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) { + httpProxy = new OkhttpProxyInfo(OkhttpProxyInfo.ProxyType.SOCKS5, configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword()); + } + + httpClient = new ConnectionPool(); + } + + +} 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 ef3f31b6..15e636bb 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 @@ -6,10 +6,16 @@ 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.AbstractRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; + +import me.chanjar.weixin.common.util.http.okhttp.OkhttpProxyInfo; + import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import okhttp3.*; + import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; @@ -21,7 +27,7 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -public class MaterialDeleteRequestExecutor implements RequestExecutor { +public class MaterialDeleteRequestExecutor extends AbstractRequestExecutor { public MaterialDeleteRequestExecutor() { @@ -29,25 +35,33 @@ 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)) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + WxError error = WxError.fromJson(responseContent); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } else { + return true; + } + } finally { + httpPost.releaseConnection(); + } } - private Boolean executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, String materialId) throws WxErrorException, IOException { + + @Override + public Boolean executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, String materialId) throws WxErrorException, IOException { HttpRequest request = HttpRequest.post(uri); if (proxyInfo != null) { provider.useProxy(proxyInfo); @@ -65,29 +79,36 @@ 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)) { - String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); - WxError error = WxError.fromJson(responseContent); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } else { - return true; + //设置授权 + clientBuilder.authenticator(new Authenticator() { + @Override + public Request authenticate(Route route, Response response) throws IOException { + String credential = Credentials.basic(proxyInfo.getProxyUsername(), proxyInfo.getProxyPassword()); + return response.request().newBuilder() + .header("Authorization", credential) + .build(); } - } finally { - httpPost.releaseConnection(); + }); + //得到httpClient + OkHttpClient client = clientBuilder.build(); + + RequestBody requestBody = new FormBody.Builder().add("media_id", materialId).build(); + Request request = new Request.Builder().url(uri).post(requestBody).build(); + Response response = client.newCall(request).execute(); + String responseContent = response.body().toString(); + WxError error = WxError.fromJson(responseContent); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } else { + return true; } } - } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialNewsInfoRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialNewsInfoRequestExecutor.java index 87180067..58e63a92 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialNewsInfoRequestExecutor.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/MaterialNewsInfoRequestExecutor.java @@ -6,12 +6,18 @@ 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.AbstractRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; + +import me.chanjar.weixin.common.util.http.okhttp.OkhttpProxyInfo; + import me.chanjar.weixin.common.util.json.WxGsonBuilder; import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews; import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; +import okhttp3.*; + import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; @@ -23,30 +29,13 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -public class MaterialNewsInfoRequestExecutor implements RequestExecutor { +public class MaterialNewsInfoRequestExecutor extends AbstractRequestExecutor { public MaterialNewsInfoRequestExecutor() { super(); } @Override - public WxMpMaterialNews 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; - } - } - public WxMpMaterialNews executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String materialId) throws WxErrorException, IOException { HttpPost httpPost = new HttpPost(uri); if (httpProxy != null) { @@ -71,6 +60,7 @@ public class MaterialNewsInfoRequestExecutor implements RequestExecutor { +public class MaterialUploadRequestExecutor extends AbstractRequestExecutor { @Override - 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 { + public WxMpMaterialUploadResult executeJodd(HttpConnectionProvider provider, ProxyInfo httpProxy, String uri, WxMpMaterial material) throws WxErrorException, IOException { HttpRequest request = HttpRequest.post(uri); if (httpProxy != null) { provider.useProxy(httpProxy); @@ -76,8 +66,56 @@ public class MaterialUploadRequestExecutor implements RequestExecutor form = material.getForm(); + if (material.getForm() != null) { + bodyBuilder.addFormDataPart("description", WxGsonBuilder.create().toJson(form)); + } + RequestBody body =bodyBuilder.build(); + Request request = new Request.Builder().url(uri).post(body).build(); + Response response = client.newCall(request).execute(); + String responseContent = response.body().toString(); + WxError error = WxError.fromJson(responseContent); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } else { + return WxMpMaterialUploadResult.fromJson(responseContent); + } + } + + @Override + public 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(); 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 fb581892..a6af76eb 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 @@ -6,11 +6,18 @@ 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.AbstractRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; + +import me.chanjar.weixin.common.util.http.okhttp.OkhttpProxyInfo; + import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult; import me.chanjar.weixin.mp.bean.material.WxMpMaterialVideoInfoResult; +import okhttp3.*; + import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; @@ -22,31 +29,14 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -public class MaterialVideoInfoRequestExecutor implements RequestExecutor { +public class MaterialVideoInfoRequestExecutor extends AbstractRequestExecutor { public MaterialVideoInfoRequestExecutor() { super(); } @Override - public WxMpMaterialVideoInfoResult execute(RequestHttp requestHttp, String uri, String materialId) throws WxErrorException, IOException { - if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) { - CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient(); - HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy(); - return executeApache(httpClient, httpProxy, uri, materialId); - } - if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) { - HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient(); - ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy(); - return executeJodd(provider, proxyInfo, uri, materialId); - } else { - //这里需要抛出异常,需要优化 - return null; - } - } - - - private WxMpMaterialVideoInfoResult executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, String materialId) throws WxErrorException, IOException { + public WxMpMaterialVideoInfoResult executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, String materialId) throws WxErrorException, IOException { HttpRequest request = HttpRequest.post(uri); if (proxyInfo != null) { provider.useProxy(proxyInfo); @@ -64,8 +54,41 @@ public class MaterialVideoInfoRequestExecutor implements RequestExecutor { - +public class MaterialVoiceAndImageDownloadRequestExecutor extends AbstractRequestExecutor { public MaterialVoiceAndImageDownloadRequestExecutor() { super(); @@ -36,24 +41,9 @@ public class MaterialVoiceAndImageDownloadRequestExecutor implements RequestExec super(); } - @Override - public InputStream execute(RequestHttp requestHttp, String uri, String materialId) throws WxErrorException, IOException { - if (requestHttp.getRequestHttpClient() instanceof CloseableHttpClient) { - CloseableHttpClient httpClient = (CloseableHttpClient) requestHttp.getRequestHttpClient(); - HttpHost httpProxy = (HttpHost) requestHttp.getRequestHttpProxy(); - return executeApache(httpClient, httpProxy, uri, materialId); - } - if (requestHttp.getRequestHttpClient() instanceof HttpConnectionProvider) { - HttpConnectionProvider provider = (HttpConnectionProvider) requestHttp.getRequestHttpClient(); - ProxyInfo proxyInfo = (ProxyInfo) requestHttp.getRequestHttpProxy(); - return executeJodd(provider, proxyInfo, uri, materialId); - } else { - //这里需要抛出异常,需要优化 - return null; - } - } - private InputStream executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, String materialId) throws WxErrorException, IOException { + @Override + public InputStream executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, String materialId) throws WxErrorException, IOException { HttpRequest request = HttpRequest.post(uri); if (proxyInfo != null) { provider.useProxy(proxyInfo); @@ -81,8 +71,52 @@ public class MaterialVoiceAndImageDownloadRequestExecutor implements RequestExec } - private InputStream executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, - String materialId) throws WxErrorException, IOException { + @Override + public InputStream executeOkhttp(ConnectionPool pool, final OkhttpProxyInfo proxyInfo, String uri, String materialId) throws WxErrorException, IOException { + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(pool); + //设置代理 + if (proxyInfo != null) { + clientBuilder.proxy(proxyInfo.getProxy()); + } + //设置授权 + clientBuilder.authenticator(new Authenticator() { + @Override + public Request authenticate(Route route, Response response) throws IOException { + String credential = Credentials.basic(proxyInfo.getProxyUsername(), proxyInfo.getProxyPassword()); + return response.request().newBuilder() + .header("Authorization", credential) + .build(); + } + }); + //得到httpClient + OkHttpClient client = clientBuilder.build(); + + RequestBody requestBody = new FormBody.Builder().add("media_id", materialId).build(); + Request request = new Request.Builder().url(uri).get().post(requestBody).build(); + Response response = client.newCall(request).execute(); + + try (InputStream inputStream = new ByteArrayInputStream(response.body().bytes())) { + + // 下载媒体文件出错 + 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); + } + } + + @Override + public 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(); 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 c22bc310..b0db70dc 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 @@ -4,12 +4,23 @@ import jodd.http.HttpConnectionProvider; import jodd.http.HttpRequest; import jodd.http.HttpResponse; import jodd.http.ProxyInfo; + +import jodd.util.MimeTypes; + 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.fs.FileUtils; +import me.chanjar.weixin.common.util.http.AbstractRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; + +import me.chanjar.weixin.common.util.http.okhttp.OkhttpProxyInfo; + import me.chanjar.weixin.mp.bean.material.WxMediaImgUploadResult; +import okhttp3.*; + import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.client.config.RequestConfig; @@ -20,34 +31,19 @@ import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.impl.client.CloseableHttpClient; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; /** * @author miller */ -public class MediaImgUploadRequestExecutor implements RequestExecutor { +public class MediaImgUploadRequestExecutor extends AbstractRequestExecutor { @Override - 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 { + public WxMediaImgUploadResult executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, File data) throws WxErrorException, IOException { if (data == null) { throw new WxErrorException(WxError.newBuilder().setErrorMsg("文件对象为空").build()); } @@ -69,8 +65,44 @@ public class MediaImgUploadRequestExecutor implements RequestExecutor { - - @Override - 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; - } - } +public class QrCodeRequestExecutor extends AbstractRequestExecutor { - private File executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, WxMpQrCodeTicket ticket) throws WxErrorException, IOException { +@Override + public File executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, WxMpQrCodeTicket ticket) throws WxErrorException, IOException { if (ticket != null) { if (uri.indexOf('?') == -1) { uri += '?'; @@ -80,8 +68,42 @@ public class QrCodeRequestExecutor implements RequestExecutorweixin-java-common ${project.version} - - - org.jodd - jodd-http - 3.7 - - com.github.binarywang qrcode-utils