@@ -5,7 +5,7 @@ | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>me.chanjar</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>1.0.3</version> | |||
<version>1.0.4</version> | |||
<packaging>pom</packaging> | |||
<name>WeiXin Java Tools - Parent</name> | |||
<description>微信公众号、企业号上级POM</description> | |||
@@ -6,7 +6,7 @@ | |||
<parent> | |||
<groupId>me.chanjar</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>1.0.3</version> | |||
<version>1.0.4</version> | |||
</parent> | |||
<artifactId>weixin-java-common</artifactId> | |||
@@ -88,6 +88,7 @@ public class WxConsts { | |||
public static final String EVT_PIC_PHOTO_OR_ALBUM = "pic_photo_or_album"; | |||
public static final String EVT_PIC_WEIXIN = "pic_weixin"; | |||
public static final String EVT_LOCATION_SELECT = "location_select"; | |||
public static final String EVT_TEMPLATESENDJOBFINISH = "TEMPLATESENDJOBFINISH"; | |||
/////////////////////// | |||
// 上传多媒体文件的类型 | |||
@@ -8,10 +8,13 @@ import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.common.util.http.Utf8ResponseHandler; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.http.Header; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.client.ClientProtocolException; | |||
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.entity.ContentType; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import java.io.File; | |||
import java.io.IOException; | |||
@@ -27,7 +30,7 @@ import java.util.regex.Pattern; | |||
public class MediaDownloadRequestExecutor implements RequestExecutor<File, String> { | |||
@Override | |||
public File execute(String uri, String queryParam) throws WxErrorException, ClientProtocolException, IOException { | |||
public File execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, ClientProtocolException, IOException { | |||
if (queryParam != null) { | |||
if (uri.indexOf('?') == -1) { | |||
uri += '?'; | |||
@@ -36,6 +39,11 @@ public class MediaDownloadRequestExecutor implements RequestExecutor<File, Strin | |||
} | |||
HttpGet httpGet = new HttpGet(uri); | |||
if (httpProxy != null) { | |||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||
httpGet.setConfig(config); | |||
} | |||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||
Header[] contentTypeHeader = response.getHeaders("Content-Type"); | |||
@@ -5,7 +5,9 @@ import java.io.IOException; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import org.apache.http.HttpEntity; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.client.ClientProtocolException; | |||
import org.apache.http.client.config.RequestConfig; | |||
import org.apache.http.client.methods.CloseableHttpResponse; | |||
import org.apache.http.client.methods.HttpPost; | |||
import org.apache.http.entity.ContentType; | |||
@@ -13,6 +15,7 @@ import org.apache.http.entity.mime.MultipartEntityBuilder; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
/** | |||
* 上传媒体文件请求执行器,请求的参数是File, 返回的结果是String | |||
@@ -22,8 +25,12 @@ import me.chanjar.weixin.common.exception.WxErrorException; | |||
public class MediaUploadRequestExecutor implements RequestExecutor<WxMediaUploadResult, File> { | |||
@Override | |||
public WxMediaUploadResult execute(String uri, File file) throws WxErrorException, ClientProtocolException, IOException { | |||
public WxMediaUploadResult execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, File file) throws WxErrorException, ClientProtocolException, IOException { | |||
HttpPost httpPost = new HttpPost(uri); | |||
if (httpProxy != null) { | |||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||
httpPost.setConfig(config); | |||
} | |||
if (file != null) { | |||
HttpEntity entity = MultipartEntityBuilder | |||
.create() | |||
@@ -2,6 +2,7 @@ package me.chanjar.weixin.common.util.http; | |||
import java.io.IOException; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.client.ClientProtocolException; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import org.apache.http.impl.client.HttpClients; | |||
@@ -10,15 +11,23 @@ import me.chanjar.weixin.common.exception.WxErrorException; | |||
/** | |||
* http请求执行器 | |||
* @author Daniel Qian | |||
* | |||
* @param <T> 返回值类型 | |||
* @param <E> 请求参数类型 | |||
*/ | |||
public interface RequestExecutor<T, E> { | |||
public static final CloseableHttpClient httpclient = HttpClients.createDefault(); | |||
/** | |||
* | |||
* @param httpclient 传入的httpClient | |||
* @param httpProxy http代理对象,如果没有配置代理则为空 | |||
* @param uri uri | |||
* @param data 数据 | |||
* @return | |||
* @throws WxErrorException | |||
* @throws ClientProtocolException | |||
* @throws IOException | |||
*/ | |||
public T execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, E data) throws WxErrorException, ClientProtocolException, IOException; | |||
public T execute(String uri, E data) throws WxErrorException, ClientProtocolException, IOException; | |||
} |
@@ -2,12 +2,15 @@ package me.chanjar.weixin.common.util.http; | |||
import java.io.IOException; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.client.ClientProtocolException; | |||
import org.apache.http.client.config.RequestConfig; | |||
import org.apache.http.client.methods.CloseableHttpResponse; | |||
import org.apache.http.client.methods.HttpGet; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
/** | |||
* 简单的GET请求执行器,请求的参数是String, 返回的结果也是String | |||
@@ -17,7 +20,7 @@ import me.chanjar.weixin.common.exception.WxErrorException; | |||
public class SimpleGetRequestExecutor implements RequestExecutor<String, String> { | |||
@Override | |||
public String execute(String uri, String queryParam) throws WxErrorException, ClientProtocolException, IOException { | |||
public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, ClientProtocolException, IOException { | |||
if (queryParam != null) { | |||
if (uri.indexOf('?') == -1) { | |||
uri += '?'; | |||
@@ -25,6 +28,11 @@ public class SimpleGetRequestExecutor implements RequestExecutor<String, String> | |||
uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||
} | |||
HttpGet httpGet = new HttpGet(uri); | |||
if (httpProxy != null) { | |||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||
httpGet.setConfig(config); | |||
} | |||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||
WxError error = WxError.fromJson(responseContent); | |||
@@ -5,10 +5,20 @@ import java.io.IOException; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import org.apache.http.Consts; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.auth.AuthScope; | |||
import org.apache.http.auth.UsernamePasswordCredentials; | |||
import org.apache.http.client.ClientProtocolException; | |||
import org.apache.http.client.CredentialsProvider; | |||
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.client.methods.HttpPost; | |||
import org.apache.http.entity.StringEntity; | |||
import org.apache.http.impl.client.BasicCredentialsProvider; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import org.apache.http.impl.client.HttpClients; | |||
import org.apache.http.util.EntityUtils; | |||
/** | |||
* 简单的POST请求执行器,请求的参数是String, 返回的结果也是String | |||
@@ -18,12 +28,18 @@ import org.apache.http.entity.StringEntity; | |||
public class SimplePostRequestExecutor implements RequestExecutor<String, String> { | |||
@Override | |||
public String execute(String uri, String postEntity) throws WxErrorException, ClientProtocolException, IOException { | |||
public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String postEntity) throws WxErrorException, ClientProtocolException, IOException { | |||
HttpPost httpPost = new HttpPost(uri); | |||
if (httpProxy != null) { | |||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||
httpPost.setConfig(config); | |||
} | |||
if (postEntity != null) { | |||
StringEntity entity = new StringEntity(postEntity, Consts.UTF_8); | |||
httpPost.setEntity(entity); | |||
} | |||
CloseableHttpResponse response = httpclient.execute(httpPost); | |||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||
WxError error = WxError.fromJson(responseContent); | |||
@@ -6,7 +6,7 @@ | |||
<parent> | |||
<groupId>me.chanjar</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>1.0.3</version> | |||
<version>1.0.4</version> | |||
</parent> | |||
<artifactId>weixin-java-cp</artifactId> | |||
@@ -27,4 +27,12 @@ public interface WxCpConfigStorage { | |||
public int getExpiresIn(); | |||
public String getHttp_proxy_host(); | |||
public int getHttp_proxy_port(); | |||
public String getHttp_proxy_username(); | |||
public String getHttp_proxy_password(); | |||
} |
@@ -18,6 +18,11 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
protected String agentId; | |||
protected int expiresIn; | |||
protected String http_proxy_host; | |||
protected int http_proxy_port; | |||
protected String http_proxy_username; | |||
protected String http_proxy_password; | |||
public void updateAccessToken(WxAccessToken accessToken) { | |||
updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
} | |||
@@ -83,16 +88,53 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
this.agentId = agentId; | |||
} | |||
public String getHttp_proxy_host() { | |||
return http_proxy_host; | |||
} | |||
public void setHttp_proxy_host(String http_proxy_host) { | |||
this.http_proxy_host = http_proxy_host; | |||
} | |||
public int getHttp_proxy_port() { | |||
return http_proxy_port; | |||
} | |||
public void setHttp_proxy_port(int http_proxy_port) { | |||
this.http_proxy_port = http_proxy_port; | |||
} | |||
public String getHttp_proxy_username() { | |||
return http_proxy_username; | |||
} | |||
public void setHttp_proxy_username(String http_proxy_username) { | |||
this.http_proxy_username = http_proxy_username; | |||
} | |||
public String getHttp_proxy_password() { | |||
return http_proxy_password; | |||
} | |||
public void setHttp_proxy_password(String http_proxy_password) { | |||
this.http_proxy_password = http_proxy_password; | |||
} | |||
@Override | |||
public String toString() { | |||
return "WxInMemoryCpConfigStorage{" + | |||
"appidOrCorpid='" + corpId + '\'' + | |||
return "WxCpInMemoryConfigStorage{" + | |||
"corpId='" + corpId + '\'' + | |||
", corpSecret='" + corpSecret + '\'' + | |||
", token='" + token + '\'' + | |||
", accessToken='" + accessToken + '\'' + | |||
", aesKey='" + aesKey + '\'' + | |||
", agentId='" + agentId + '\'' + | |||
", expiresIn=" + expiresIn + | |||
", http_proxy_host='" + http_proxy_host + '\'' + | |||
", http_proxy_port=" + http_proxy_port + | |||
", http_proxy_username='" + http_proxy_username + '\'' + | |||
", http_proxy_password='" + http_proxy_password + '\'' + | |||
'}'; | |||
} | |||
} |
@@ -7,6 +7,7 @@ import java.util.List; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.cp.bean.*; | |||
import me.chanjar.weixin.cp.bean.WxCpDepart; | |||
import me.chanjar.weixin.cp.bean.WxCpUser; | |||
@@ -65,8 +66,8 @@ public interface WxCpService { | |||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||
* </pre> | |||
* | |||
* @param mediaType 媒体类型, 请看{@link WxConsts} | |||
* @param fileType 文件类型,请看{@link WxConsts} | |||
* @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | |||
* @param fileType 文件类型,请看{@link me.chanjar.weixin.common.api.WxConsts} | |||
* @param inputStream 输入流 | |||
* @throws WxErrorException | |||
*/ | |||
@@ -281,6 +282,40 @@ public interface WxCpService { | |||
*/ | |||
public void tagRemoveUsers(String tagId, List<String> userIds) throws WxErrorException; | |||
/** | |||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求 | |||
* @param url | |||
* @param queryParam | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
String get(String url, String queryParam) throws WxErrorException; | |||
/** | |||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求 | |||
* @param url | |||
* @param postData | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
String post(String url, String postData) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* Service没有实现某个API的时候,可以用这个, | |||
* 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。 | |||
* 可以参考,{@link me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor}的实现方法 | |||
* </pre> | |||
* @param executor | |||
* @param uri | |||
* @param data | |||
* @param <T> | |||
* @param <E> | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException; | |||
/** | |||
* 注入 {@link WxCpConfigStorage} 的实现 | |||
* | |||
@@ -20,9 +20,15 @@ import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.auth.AuthScope; | |||
import org.apache.http.auth.UsernamePasswordCredentials; | |||
import org.apache.http.client.ClientProtocolException; | |||
import org.apache.http.client.CredentialsProvider; | |||
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.BasicCredentialsProvider; | |||
import org.apache.http.impl.client.BasicResponseHandler; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import org.apache.http.impl.client.HttpClients; | |||
@@ -51,12 +57,14 @@ public class WxCpServiceImpl implements WxCpService { | |||
*/ | |||
protected static final AtomicBoolean GLOBAL_ACCESS_TOKEN_REFRESH_FLAG = new AtomicBoolean(false); | |||
protected static final CloseableHttpClient httpclient = HttpClients.createDefault(); | |||
protected WxCpConfigStorage wxCpConfigStorage; | |||
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>(); | |||
protected CloseableHttpClient httpClient; | |||
protected HttpHost httpProxy; | |||
public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | |||
try { | |||
return SHA1.gen(wxCpConfigStorage.getToken(), timestamp, nonce, data).equals(msgSignature); | |||
@@ -78,6 +86,11 @@ public class WxCpServiceImpl implements WxCpService { | |||
+ "&corpsecret=" + wxCpConfigStorage.getCorpSecret(); | |||
try { | |||
HttpGet httpGet = new HttpGet(url); | |||
if (httpProxy != null) { | |||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||
httpGet.setConfig(config); | |||
} | |||
CloseableHttpClient httpclient = getHttpclient(); | |||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||
String resultContent = new BasicResponseHandler().handleResponse(response); | |||
WxError error = WxError.fromJson(resultContent); | |||
@@ -308,6 +321,14 @@ public class WxCpServiceImpl implements WxCpService { | |||
execute(new SimplePostRequestExecutor(), url, jsonObject.toString()); | |||
} | |||
public String get(String url, String queryParam) throws WxErrorException { | |||
return execute(new SimpleGetRequestExecutor(), url, queryParam); | |||
} | |||
public String post(String url, String postData) throws WxErrorException { | |||
return execute(new SimplePostRequestExecutor(), url, postData); | |||
} | |||
/** | |||
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | |||
* | |||
@@ -327,7 +348,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; | |||
try { | |||
return executor.execute(uriWithAccessToken, data); | |||
return executor.execute(getHttpclient(), httpProxy, uriWithAccessToken, data); | |||
} catch (WxErrorException e) { | |||
WxError error = e.getError(); | |||
/* | |||
@@ -371,8 +392,38 @@ public class WxCpServiceImpl implements WxCpService { | |||
} | |||
} | |||
protected CloseableHttpClient getHttpclient() { | |||
return httpClient; | |||
} | |||
public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) { | |||
this.wxCpConfigStorage = wxConfigProvider; | |||
String http_proxy_host = wxCpConfigStorage.getHttp_proxy_host(); | |||
int http_proxy_port = wxCpConfigStorage.getHttp_proxy_port(); | |||
String http_proxy_username = wxCpConfigStorage.getHttp_proxy_username(); | |||
String http_proxy_password = wxCpConfigStorage.getHttp_proxy_password(); | |||
if(StringUtils.isNotBlank(http_proxy_host)) { | |||
// 使用代理服务器 | |||
if(StringUtils.isNotBlank(http_proxy_username)) { | |||
// 需要用户认证的代理服务器 | |||
CredentialsProvider credsProvider = new BasicCredentialsProvider(); | |||
credsProvider.setCredentials( | |||
new AuthScope(http_proxy_host, http_proxy_port), | |||
new UsernamePasswordCredentials(http_proxy_username, http_proxy_password)); | |||
httpClient = HttpClients | |||
.custom() | |||
.setDefaultCredentialsProvider(credsProvider) | |||
.build(); | |||
} else { | |||
// 无需用户认证的代理服务器 | |||
httpClient = HttpClients.createDefault(); | |||
} | |||
httpProxy = new HttpHost(http_proxy_host, http_proxy_port); | |||
} else { | |||
httpClient = HttpClients.createDefault(); | |||
} | |||
} | |||
} |
@@ -5,6 +5,7 @@ import java.io.InputStream; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import org.testng.Assert; | |||
import org.testng.annotations.DataProvider; | |||
@@ -1,5 +1,6 @@ | |||
package me.chanjar.weixin.cp.api; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
import org.testng.annotations.Guice; | |||
import org.testng.annotations.Test; | |||
@@ -2,6 +2,7 @@ package me.chanjar.weixin.cp.api; | |||
import java.util.Map; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
import org.testng.Assert; | |||
@@ -2,6 +2,7 @@ package me.chanjar.weixin.cp.api; | |||
import javax.xml.bind.JAXBException; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import org.testng.Assert; | |||
import org.testng.annotations.DataProvider; | |||
@@ -6,7 +6,7 @@ | |||
<parent> | |||
<groupId>me.chanjar</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>1.0.3</version> | |||
<version>1.0.4</version> | |||
</parent> | |||
<artifactId>weixin-java-mp</artifactId> | |||
<name>WeiXin Java Tools - MP</name> | |||
@@ -25,4 +25,12 @@ public interface WxMpConfigStorage { | |||
public int getExpiresIn(); | |||
public String getHttp_proxy_host(); | |||
public int getHttp_proxy_port(); | |||
public String getHttp_proxy_username(); | |||
public String getHttp_proxy_password(); | |||
} |
@@ -16,6 +16,11 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
protected String aesKey; | |||
protected int expiresIn; | |||
protected String http_proxy_host; | |||
protected int http_proxy_port; | |||
protected String http_proxy_username; | |||
protected String http_proxy_password; | |||
public void updateAccessToken(WxAccessToken accessToken) { | |||
updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
} | |||
@@ -73,5 +78,52 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
this.expiresIn = expiresIn; | |||
} | |||
public String getHttp_proxy_host() { | |||
return http_proxy_host; | |||
} | |||
public void setHttp_proxy_host(String http_proxy_host) { | |||
this.http_proxy_host = http_proxy_host; | |||
} | |||
public int getHttp_proxy_port() { | |||
return http_proxy_port; | |||
} | |||
public void setHttp_proxy_port(int http_proxy_port) { | |||
this.http_proxy_port = http_proxy_port; | |||
} | |||
public String getHttp_proxy_username() { | |||
return http_proxy_username; | |||
} | |||
public void setHttp_proxy_username(String http_proxy_username) { | |||
this.http_proxy_username = http_proxy_username; | |||
} | |||
public String getHttp_proxy_password() { | |||
return http_proxy_password; | |||
} | |||
public void setHttp_proxy_password(String http_proxy_password) { | |||
this.http_proxy_password = http_proxy_password; | |||
} | |||
@Override | |||
public String toString() { | |||
return "WxMpInMemoryConfigStorage{" + | |||
"appId='" + appId + '\'' + | |||
", secret='" + secret + '\'' + | |||
", token='" + token + '\'' + | |||
", accessToken='" + accessToken + '\'' + | |||
", aesKey='" + aesKey + '\'' + | |||
", expiresIn=" + expiresIn + | |||
", http_proxy_host='" + http_proxy_host + '\'' + | |||
", http_proxy_port=" + http_proxy_port + | |||
", http_proxy_username='" + http_proxy_username + '\'' + | |||
", http_proxy_password='" + http_proxy_password + '\'' + | |||
'}'; | |||
} | |||
} |
@@ -2,6 +2,7 @@ package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.mp.bean.*; | |||
import me.chanjar.weixin.mp.bean.result.*; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
@@ -306,10 +307,65 @@ public interface WxMpService { | |||
* @throws WxErrorException | |||
*/ | |||
public String shortUrl(String long_url) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 发送模板消息 | |||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=模板消息接口 | |||
* </pre> | |||
* @param templateMessage | |||
* @throws WxErrorException | |||
*/ | |||
public void templateSend(WxMpTemplateMessage templateMessage) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 语义查询接口 | |||
* 详情请见:http://mp.weixin.qq.com/wiki/index.php?title=语义理解 | |||
* </pre> | |||
* @param semanticQuery | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
WxMpSemanticQueryResult semanticQuery(WxMpSemanticQuery semanticQuery) throws WxErrorException; | |||
/** | |||
* 注入 {@link WxMpConfigStorage} 的实现 | |||
* @param wxConfigProvider | |||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求 | |||
* @param url | |||
* @param queryParam | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
String get(String url, String queryParam) throws WxErrorException; | |||
/** | |||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求 | |||
* @param url | |||
* @param postData | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
String post(String url, String postData) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* Service没有实现某个API的时候,可以用这个, | |||
* 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。 | |||
* 可以参考,{@link me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor}的实现方法 | |||
* </pre> | |||
* @param executor | |||
* @param uri | |||
* @param data | |||
* @param <T> | |||
* @param <E> | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException; | |||
/** | |||
* 注入 {@link WxMpConfigStorage} 的实现 | |||
* @param wxConfigProvider | |||
*/ | |||
public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider); | |||
} |
@@ -19,9 +19,16 @@ import me.chanjar.weixin.mp.bean.result.*; | |||
import me.chanjar.weixin.mp.util.http.QrCodeRequestExecutor; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.auth.AuthScope; | |||
import org.apache.http.auth.UsernamePasswordCredentials; | |||
import org.apache.http.client.ClientProtocolException; | |||
import org.apache.http.client.CredentialsProvider; | |||
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.client.methods.HttpPost; | |||
import org.apache.http.impl.client.BasicCredentialsProvider; | |||
import org.apache.http.impl.client.BasicResponseHandler; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import org.apache.http.impl.client.HttpClients; | |||
@@ -31,6 +38,7 @@ import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.StringReader; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.UUID; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
@@ -43,12 +51,14 @@ public class WxMpServiceImpl implements WxMpService { | |||
*/ | |||
protected static final AtomicBoolean GLOBAL_ACCESS_TOKEN_REFRESH_FLAG = new AtomicBoolean(false); | |||
protected static final CloseableHttpClient httpclient = HttpClients.createDefault(); | |||
protected WxMpConfigStorage wxMpConfigStorage; | |||
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>(); | |||
protected CloseableHttpClient httpClient; | |||
protected HttpHost httpProxy; | |||
public boolean checkSignature(String timestamp, String nonce, String signature) { | |||
try { | |||
return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature); | |||
@@ -66,6 +76,11 @@ public class WxMpServiceImpl implements WxMpService { | |||
; | |||
try { | |||
HttpGet httpGet = new HttpGet(url); | |||
if (httpProxy != null) { | |||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||
httpGet.setConfig(config); | |||
} | |||
CloseableHttpClient httpclient = getHttpclient(); | |||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||
String resultContent = new BasicResponseHandler().handleResponse(response); | |||
WxError error = WxError.fromJson(resultContent); | |||
@@ -272,8 +287,27 @@ public class WxMpServiceImpl implements WxMpService { | |||
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
return tmpJsonElement.getAsJsonObject().get("short_url").getAsString(); | |||
} | |||
/** | |||
public void templateSend(WxMpTemplateMessage templateMessage) throws WxErrorException { | |||
String url = "https://api.weixin.qq.com/cgi-bin/message/template/send"; | |||
execute(new SimplePostRequestExecutor(), url, templateMessage.toJson()); | |||
} | |||
public WxMpSemanticQueryResult semanticQuery(WxMpSemanticQuery semanticQuery) throws WxErrorException { | |||
String url = "https://api.weixin.qq.com/semantic/semproxy/search"; | |||
String responseContent = execute(new SimplePostRequestExecutor(), url, semanticQuery.toJson()); | |||
return WxMpSemanticQueryResult.fromJson(responseContent); | |||
} | |||
public String get(String url, String queryParam) throws WxErrorException { | |||
return execute(new SimpleGetRequestExecutor(), url, queryParam); | |||
} | |||
public String post(String url, String postData) throws WxErrorException { | |||
return execute(new SimplePostRequestExecutor(), url, postData); | |||
} | |||
/** | |||
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | |||
* @param executor | |||
* @param uri | |||
@@ -291,7 +325,7 @@ public class WxMpServiceImpl implements WxMpService { | |||
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; | |||
try { | |||
return executor.execute(uriWithAccessToken, data); | |||
return executor.execute(getHttpclient(), httpProxy, uriWithAccessToken, data); | |||
} catch (WxErrorException e) { | |||
WxError error = e.getError(); | |||
/* | |||
@@ -334,9 +368,39 @@ public class WxMpServiceImpl implements WxMpService { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
protected CloseableHttpClient getHttpclient() { | |||
return httpClient; | |||
} | |||
public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) { | |||
this.wxMpConfigStorage = wxConfigProvider; | |||
String http_proxy_host = wxMpConfigStorage.getHttp_proxy_host(); | |||
int http_proxy_port = wxMpConfigStorage.getHttp_proxy_port(); | |||
String http_proxy_username = wxMpConfigStorage.getHttp_proxy_username(); | |||
String http_proxy_password = wxMpConfigStorage.getHttp_proxy_password(); | |||
if(StringUtils.isNotBlank(http_proxy_host)) { | |||
// 使用代理服务器 | |||
if(StringUtils.isNotBlank(http_proxy_username)) { | |||
// 需要用户认证的代理服务器 | |||
CredentialsProvider credsProvider = new BasicCredentialsProvider(); | |||
credsProvider.setCredentials( | |||
new AuthScope(http_proxy_host, http_proxy_port), | |||
new UsernamePasswordCredentials(http_proxy_username, http_proxy_password)); | |||
httpClient = HttpClients | |||
.custom() | |||
.setDefaultCredentialsProvider(credsProvider) | |||
.build(); | |||
} else { | |||
// 无需用户认证的代理服务器 | |||
httpClient = HttpClients.createDefault(); | |||
} | |||
httpProxy = new HttpHost(http_proxy_host, http_proxy_port); | |||
} else { | |||
httpClient = HttpClients.createDefault(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,91 @@ | |||
package me.chanjar.weixin.mp.bean; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
/** | |||
* 语义理解查询用对象 | |||
* | |||
* http://mp.weixin.qq.com/wiki/index.php?title=语义理解 | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public class WxMpSemanticQuery { | |||
private String query; | |||
private String category; | |||
private Float latitude; | |||
private Float longitude; | |||
private String city; | |||
private String region; | |||
private String appid; | |||
private String uid; | |||
public String getQuery() { | |||
return query; | |||
} | |||
public void setQuery(String query) { | |||
this.query = query; | |||
} | |||
public String getCategory() { | |||
return category; | |||
} | |||
public void setCategory(String category) { | |||
this.category = category; | |||
} | |||
public Float getLatitude() { | |||
return latitude; | |||
} | |||
public void setLatitude(Float latitude) { | |||
this.latitude = latitude; | |||
} | |||
public Float getLongitude() { | |||
return longitude; | |||
} | |||
public void setLongitude(Float longitude) { | |||
this.longitude = longitude; | |||
} | |||
public String getCity() { | |||
return city; | |||
} | |||
public void setCity(String city) { | |||
this.city = city; | |||
} | |||
public String getRegion() { | |||
return region; | |||
} | |||
public void setRegion(String region) { | |||
this.region = region; | |||
} | |||
public String getAppid() { | |||
return appid; | |||
} | |||
public void setAppid(String appid) { | |||
this.appid = appid; | |||
} | |||
public String getUid() { | |||
return uid; | |||
} | |||
public void setUid(String uid) { | |||
this.uid = uid; | |||
} | |||
public String toJson() { | |||
return WxMpGsonBuilder.create().toJson(this); | |||
} | |||
} |
@@ -0,0 +1,49 @@ | |||
package me.chanjar.weixin.mp.bean; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
public class WxMpTemplateData { | |||
private String name; | |||
private String value; | |||
private String color; | |||
public WxMpTemplateData() { | |||
} | |||
public WxMpTemplateData(String name, String value) { | |||
this.name = name; | |||
this.value = value; | |||
} | |||
public WxMpTemplateData(String name, String value, String color) { | |||
this.name = name; | |||
this.value = value; | |||
this.color = color; | |||
} | |||
public String getName() { | |||
return name; | |||
} | |||
public void setName(String name) { | |||
this.name = name; | |||
} | |||
public String getValue() { | |||
return value; | |||
} | |||
public void setValue(String value) { | |||
this.value = value; | |||
} | |||
public String getColor() { | |||
return color; | |||
} | |||
public void setColor(String color) { | |||
this.color = color; | |||
} | |||
} |
@@ -0,0 +1,62 @@ | |||
package me.chanjar.weixin.mp.bean; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
/** | |||
* Created by qianjia on 14/11/1. | |||
*/ | |||
public class WxMpTemplateMessage { | |||
private String toUser; | |||
private String templateId; | |||
private String url; | |||
private String topColor; | |||
private List<WxMpTemplateData> datas = new ArrayList<WxMpTemplateData>(); | |||
public String getToUser() { | |||
return toUser; | |||
} | |||
public void setToUser(String toUser) { | |||
this.toUser = toUser; | |||
} | |||
public String getTemplateId() { | |||
return templateId; | |||
} | |||
public void setTemplateId(String templateId) { | |||
this.templateId = templateId; | |||
} | |||
public String getUrl() { | |||
return url; | |||
} | |||
public void setUrl(String url) { | |||
this.url = url; | |||
} | |||
public String getTopColor() { | |||
return topColor; | |||
} | |||
public void setTopColor(String topColor) { | |||
this.topColor = topColor; | |||
} | |||
public List<WxMpTemplateData> getDatas() { | |||
return datas; | |||
} | |||
public void setDatas(List<WxMpTemplateData> datas) { | |||
this.datas = datas; | |||
} | |||
public String toJson() { | |||
return WxMpGsonBuilder.INSTANCE.create().toJson(this); | |||
} | |||
} |
@@ -0,0 +1,73 @@ | |||
package me.chanjar.weixin.mp.bean.result; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
/** | |||
* 语义理解查询结果对象 | |||
* | |||
* http://mp.weixin.qq.com/wiki/index.php?title=语义理解 | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public class WxMpSemanticQueryResult { | |||
private String query; | |||
private String type; | |||
private String semantic; | |||
private String result; | |||
private String answer; | |||
private String text; | |||
public String getQuery() { | |||
return query; | |||
} | |||
public void setQuery(String query) { | |||
this.query = query; | |||
} | |||
public String getType() { | |||
return type; | |||
} | |||
public void setType(String type) { | |||
this.type = type; | |||
} | |||
public String getSemantic() { | |||
return semantic; | |||
} | |||
public void setSemantic(String semantic) { | |||
this.semantic = semantic; | |||
} | |||
public String getResult() { | |||
return result; | |||
} | |||
public void setResult(String result) { | |||
this.result = result; | |||
} | |||
public String getAnswer() { | |||
return answer; | |||
} | |||
public void setAnswer(String answer) { | |||
this.answer = answer; | |||
} | |||
public String getText() { | |||
return text; | |||
} | |||
public void setText(String text) { | |||
this.text = text; | |||
} | |||
public static WxMpSemanticQueryResult fromJson(String json) { | |||
return WxMpGsonBuilder.create().fromJson(json, WxMpSemanticQueryResult.class); | |||
} | |||
} |
@@ -8,10 +8,13 @@ import me.chanjar.weixin.common.util.http.Utf8ResponseHandler; | |||
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import org.apache.http.Header; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.client.ClientProtocolException; | |||
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.entity.ContentType; | |||
import org.apache.http.impl.client.CloseableHttpClient; | |||
import java.io.File; | |||
import java.io.IOException; | |||
@@ -27,7 +30,7 @@ import java.util.UUID; | |||
public class QrCodeRequestExecutor implements RequestExecutor<File, WxMpQrCodeTicket> { | |||
@Override | |||
public File execute(String uri, WxMpQrCodeTicket ticket) throws WxErrorException, ClientProtocolException, IOException { | |||
public File execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, WxMpQrCodeTicket ticket) throws WxErrorException, ClientProtocolException, IOException { | |||
if (ticket != null) { | |||
if (uri.indexOf('?') == -1) { | |||
uri += '?'; | |||
@@ -39,6 +42,11 @@ public class QrCodeRequestExecutor implements RequestExecutor<File, WxMpQrCodeTi | |||
} | |||
HttpGet httpGet = new HttpGet(uri); | |||
if (httpProxy != null) { | |||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||
httpGet.setConfig(config); | |||
} | |||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||
Header[] contentTypeHeader = response.getHeaders("Content-Type"); | |||
@@ -22,6 +22,8 @@ public class WxMpGsonBuilder { | |||
INSTANCE.registerTypeAdapter(WxMpMassSendResult.class, new WxMpMassSendResultAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpMassUploadResult.class, new WxMpMassUploadResultAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpQrCodeTicket.class, new WxQrCodeTicketAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpTemplateMessage.class, new WxMpTemplateMessageGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpSemanticQueryResult.class, new WxMpSemanticQueryResultAdapter()); | |||
} | |||
public static Gson create() { | |||
@@ -0,0 +1,51 @@ | |||
/* | |||
* KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved. | |||
* | |||
* This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended | |||
* only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction | |||
* arose from modification of the original source, or other redistribution of this source | |||
* is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | |||
*/ | |||
package me.chanjar.weixin.mp.util.json; | |||
import com.google.gson.*; | |||
import me.chanjar.weixin.common.util.json.GsonHelper; | |||
import me.chanjar.weixin.mp.bean.result.WxMpMassSendResult; | |||
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; | |||
import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult; | |||
import java.lang.reflect.Type; | |||
/** | |||
* | |||
* @author Daniel Qian | |||
* | |||
*/ | |||
public class WxMpSemanticQueryResultAdapter implements JsonDeserializer<WxMpSemanticQueryResult> { | |||
public WxMpSemanticQueryResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||
WxMpSemanticQueryResult result = new WxMpSemanticQueryResult(); | |||
JsonObject resultJsonObject = json.getAsJsonObject(); | |||
if (GsonHelper.getString(resultJsonObject, "query") != null) { | |||
result.setQuery(GsonHelper.getString(resultJsonObject, "query")); | |||
} | |||
if (GsonHelper.getString(resultJsonObject, "type") != null) { | |||
result.setType(GsonHelper.getString(resultJsonObject, "type")); | |||
} | |||
if (resultJsonObject.get("semantic") != null) { | |||
result.setSemantic(resultJsonObject.get("semantic").toString()); | |||
} | |||
if (resultJsonObject.get("result") != null) { | |||
result.setResult(resultJsonObject.get("result").toString()); | |||
} | |||
if (GsonHelper.getString(resultJsonObject, "answer") != null) { | |||
result.setAnswer(GsonHelper.getString(resultJsonObject, "answer")); | |||
} | |||
if (GsonHelper.getString(resultJsonObject, "text") != null) { | |||
result.setText(GsonHelper.getString(resultJsonObject, "text")); | |||
} | |||
return result; | |||
} | |||
} |
@@ -0,0 +1,50 @@ | |||
/* | |||
* KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved. | |||
* | |||
* This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended | |||
* only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction | |||
* arose from modification of the original source, or other redistribution of this source | |||
* is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | |||
*/ | |||
package me.chanjar.weixin.mp.util.json; | |||
import com.google.gson.*; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.mp.bean.WxMpCustomMessage; | |||
import me.chanjar.weixin.mp.bean.WxMpTemplateData; | |||
import me.chanjar.weixin.mp.bean.WxMpTemplateMessage; | |||
import java.lang.reflect.Type; | |||
/** | |||
* @author qianjia | |||
*/ | |||
public class WxMpTemplateMessageGsonAdapter implements JsonSerializer<WxMpTemplateMessage> { | |||
public JsonElement serialize(WxMpTemplateMessage message, Type typeOfSrc, JsonSerializationContext context) { | |||
JsonObject messageJson = new JsonObject(); | |||
messageJson.addProperty("touser", message.getToUser()); | |||
messageJson.addProperty("template_id", message.getTemplateId()); | |||
if (message.getUrl() != null) { | |||
messageJson.addProperty("url", message.getUrl()); | |||
} | |||
if (message.getTopColor() != null) { | |||
messageJson.addProperty("topcolor", message.getTopColor()); | |||
} | |||
JsonObject datas = new JsonObject(); | |||
messageJson.add("data", datas); | |||
for (WxMpTemplateData data : message.getDatas()) { | |||
JsonObject dataJson = new JsonObject(); | |||
dataJson.addProperty("value", data.getValue()); | |||
if (data.getColor() != null) { | |||
dataJson.addProperty("color", data.getColor()); | |||
} | |||
datas.add(data.getName(), dataJson); | |||
} | |||
return messageJson; | |||
} | |||
} |