@@ -0,0 +1,13 @@ | |||
language: java | |||
jdk: | |||
- oraclejdk7 | |||
script: "mvn clean install -Dmaven.test.skip=true" | |||
branches: | |||
only: | |||
- develop | |||
notifications: | |||
email: | |||
- chanjarster@gmail.com |
@@ -1,4 +1,8 @@ | |||
weixin-java-tools | |||
[![Build Status](https://travis-ci.org/chanjarster/weixin-java-tools.svg?branch=develop)](https://travis-ci.org/chanjarster/weixin-java-tools) | |||
![Maven Central](https://img.shields.io/maven-central/v/me.chanjar/weixin-java-parent.svg) | |||
=========== | |||
微信公众号、企业号Java SDK。 | |||
@@ -5,7 +5,7 @@ | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>me.chanjar</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>1.1.1</version> | |||
<version>1.1.2</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.1.1</version> | |||
<version>1.1.2</version> | |||
</parent> | |||
<artifactId>weixin-java-common</artifactId> | |||
@@ -1,4 +1,4 @@ | |||
package me.chanjar.weixin.common.util; | |||
package me.chanjar.weixin.common.api; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
@@ -1,4 +1,4 @@ | |||
package me.chanjar.weixin.common.util; | |||
package me.chanjar.weixin.common.api; | |||
/** | |||
* <pre> |
@@ -1,4 +1,4 @@ | |||
package me.chanjar.weixin.common.util; | |||
package me.chanjar.weixin.common.api; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; |
@@ -2,7 +2,9 @@ package me.chanjar.weixin.common.bean; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
public class WxAccessToken { | |||
import java.io.Serializable; | |||
public class WxAccessToken implements Serializable { | |||
private String accessToken; | |||
@@ -1,9 +1,11 @@ | |||
package me.chanjar.weixin.mp.bean.result; | |||
package me.chanjar.weixin.common.bean; | |||
import java.io.Serializable; | |||
/** | |||
* jspai signature | |||
*/ | |||
public class WxMpJsapiSignature { | |||
public class WxJsapiSignature implements Serializable { | |||
private String noncestr; | |||
@@ -2,6 +2,7 @@ package me.chanjar.weixin.common.bean; | |||
import java.io.InputStream; | |||
import java.io.InputStreamReader; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -12,7 +13,7 @@ import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
* @author Daniel Qian | |||
* | |||
*/ | |||
public class WxMenu { | |||
public class WxMenu implements Serializable { | |||
private List<WxMenuButton> buttons = new ArrayList<WxMenuButton>(); | |||
@@ -2,13 +2,15 @@ package me.chanjar.weixin.common.bean.result; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* 微信错误码说明 | |||
* http://mp.weixin.qq.com/wiki/index.php?title=全局返回码说明 | |||
* @author Daniel Qian | |||
* | |||
*/ | |||
public class WxError { | |||
public class WxError implements Serializable { | |||
private int errorCode; | |||
@@ -2,7 +2,9 @@ package me.chanjar.weixin.common.bean.result; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
public class WxMediaUploadResult { | |||
import java.io.Serializable; | |||
public class WxMediaUploadResult implements Serializable { | |||
private String type; | |||
private String mediaId; | |||
@@ -1,5 +1,6 @@ | |||
package me.chanjar.weixin.common.util; | |||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import org.slf4j.Logger; | |||
import org.slf4j.LoggerFactory; | |||
@@ -0,0 +1,17 @@ | |||
package me.chanjar.weixin.common.util; | |||
public class RandomUtils { | |||
private static final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | |||
private static final java.util.Random RANDOM = new java.util.Random(); | |||
public static String getRandomStr() { | |||
StringBuilder sb = new StringBuilder(); | |||
for (int i = 0; i < 16; i++) { | |||
sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length()))); | |||
} | |||
return sb.toString(); | |||
} | |||
} |
@@ -1,5 +1,6 @@ | |||
package me.chanjar.weixin.common.util; | |||
import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
import org.testng.Assert; | |||
import org.testng.annotations.Test; | |||
@@ -6,7 +6,7 @@ | |||
<parent> | |||
<groupId>me.chanjar</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>1.1.1</version> | |||
<version>1.1.2</version> | |||
</parent> | |||
<artifactId>weixin-java-cp</artifactId> | |||
@@ -22,6 +22,20 @@ public interface WxCpConfigStorage { | |||
public void updateAccessToken(String accessToken, int expiresIn); | |||
public String getJsapiTicket(); | |||
public boolean isJsapiTicketExpired(); | |||
/** | |||
* 强制将jsapi ticket过期掉 | |||
*/ | |||
public void expireJsapiTicket(); | |||
/** | |||
* 应该是线程安全的 | |||
* @param jsapiTicket | |||
*/ | |||
public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | |||
public String getCorpId(); | |||
@@ -25,6 +25,9 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
protected volatile String http_proxy_username; | |||
protected volatile String http_proxy_password; | |||
protected volatile String jsapiTicket; | |||
protected volatile long jsapiTicketExpiresTime; | |||
public String getAccessToken() { | |||
return this.accessToken; | |||
} | |||
@@ -46,6 +49,37 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||
} | |||
@Override | |||
public String getJsapiTicket() { | |||
return jsapiTicket; | |||
} | |||
public void setJsapiTicket(String jsapiTicket) { | |||
this.jsapiTicket = jsapiTicket; | |||
} | |||
public long getJsapiTicketExpiresTime() { | |||
return jsapiTicketExpiresTime; | |||
} | |||
public void setJsapiTicketExpiresTime(long jsapiTicketExpiresTime) { | |||
this.jsapiTicketExpiresTime = jsapiTicketExpiresTime; | |||
} | |||
public boolean isJsapiTicketExpired() { | |||
return System.currentTimeMillis() > this.jsapiTicketExpiresTime; | |||
} | |||
public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||
this.jsapiTicket = jsapiTicket; | |||
// 预留200秒的时间 | |||
this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||
} | |||
public void expireJsapiTicket() { | |||
this.jsapiTicketExpiresTime = 0; | |||
} | |||
public String getCorpId() { | |||
return this.corpId; | |||
} | |||
@@ -153,6 +187,8 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
", http_proxy_port=" + http_proxy_port + | |||
", http_proxy_username='" + http_proxy_username + '\'' + | |||
", http_proxy_password='" + http_proxy_password + '\'' + | |||
", jsapiTicket='" + jsapiTicket + '\'' + | |||
", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' + | |||
'}'; | |||
} | |||
@@ -5,9 +5,9 @@ import me.chanjar.weixin.common.session.InternalSessionManager; | |||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.LogExceptionHandler; | |||
import me.chanjar.weixin.common.util.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.util.WxMessageDuplicateChecker; | |||
import me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker; | |||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | |||
import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
import org.slf4j.Logger; | |||
@@ -87,8 +87,8 @@ public class WxCpMessageRouter { | |||
/** | |||
* <pre> | |||
* 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker} | |||
* 设置自定义的 {@link me.chanjar.weixin.common.api.WxMessageDuplicateChecker} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker} | |||
* </pre> | |||
* @param messageDuplicateChecker | |||
*/ | |||
@@ -109,7 +109,7 @@ public class WxCpMessageRouter { | |||
/** | |||
* <pre> | |||
* 设置自定义的{@link me.chanjar.weixin.common.util.WxErrorExceptionHandler} | |||
* 设置自定义的{@link me.chanjar.weixin.common.api.WxErrorExceptionHandler} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.LogExceptionHandler} | |||
* </pre> | |||
* @param exceptionHandler | |||
@@ -204,7 +204,8 @@ public class WxCpMessageRouter { | |||
messageId = String.valueOf(wxMessage.getCreateTime()) | |||
+ "-" +String.valueOf(wxMessage.getAgentId() == null ? "" : wxMessage.getAgentId()) | |||
+ "-" + wxMessage.getFromUserName() | |||
+ "-" + String.valueOf(wxMessage.getEventKey() == null ? "" : wxMessage.getEvent()); | |||
+ "-" + String.valueOf(wxMessage.getEventKey() == null ? "" : wxMessage.getEventKey()) | |||
+ "-" + String.valueOf(wxMessage.getEvent() == null ? "" : wxMessage.getEvent()) | |||
; | |||
} else { | |||
messageId = String.valueOf(wxMessage.getMsgId()); | |||
@@ -2,7 +2,7 @@ package me.chanjar.weixin.cp.api; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
@@ -1,5 +1,6 @@ | |||
package me.chanjar.weixin.cp.api; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
@@ -67,6 +68,38 @@ public interface WxCpService { | |||
*/ | |||
String getAccessToken(boolean forceRefresh) throws WxErrorException; | |||
/** | |||
* 获得jsapi_ticket,不强制刷新jsapi_ticket | |||
* @see #getJsapiTicket(boolean) | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
public String getJsapiTicket() throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 获得jsapi_ticket | |||
* 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干 | |||
* | |||
* 详情请见:http://qydev.weixin.qq.com/wiki/index.php?title=微信JS接口#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95 | |||
* </pre> | |||
* @param forceRefresh 强制刷新 | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 创建调用jsapi时所需要的签名 | |||
* | |||
* 详情请见:http://qydev.weixin.qq.com/wiki/index.php?title=微信JS接口#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95 | |||
* </pre> | |||
* @param url url | |||
* @return | |||
*/ | |||
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 上传多媒体文件 | |||
@@ -320,10 +353,11 @@ public interface WxCpService { | |||
* 构造oauth2授权的url连接 | |||
* 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=企业获取code | |||
* </pre> | |||
* @param redirectUri | |||
* @param state | |||
* @return code | |||
*/ | |||
String oauth2buildAuthorizationUrl(String state); | |||
String oauth2buildAuthorizationUrl(String redirectUri, String state); | |||
/** | |||
* <pre> | |||
@@ -8,6 +8,7 @@ import com.google.gson.internal.Streams; | |||
import com.google.gson.reflect.TypeToken; | |||
import com.google.gson.stream.JsonReader; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.result.WxError; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
@@ -15,6 +16,7 @@ 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.StringUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.fs.FileUtils; | |||
@@ -44,6 +46,7 @@ import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.StringReader; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.util.List; | |||
import java.util.UUID; | |||
@@ -54,7 +57,12 @@ public class WxCpServiceImpl implements WxCpService { | |||
/** | |||
* 全局的是否正在刷新access token的锁 | |||
*/ | |||
protected static final Object GLOBAL_ACCESS_TOKEN_REFRESH_LOCK = new Object(); | |||
protected final Object globalAccessTokenRefreshLock = new Object(); | |||
/** | |||
* 全局的是否正在刷新jsapi_ticket的锁 | |||
*/ | |||
protected final Object globalJsapiTicketRefreshLock = new Object(); | |||
protected WxCpConfigStorage wxCpConfigStorage; | |||
@@ -90,7 +98,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
wxCpConfigStorage.expireAccessToken(); | |||
} | |||
if (wxCpConfigStorage.isAccessTokenExpired()) { | |||
synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) { | |||
synchronized (globalAccessTokenRefreshLock) { | |||
if (wxCpConfigStorage.isAccessTokenExpired()) { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||
+ "&corpid=" + wxCpConfigStorage.getCorpId() | |||
@@ -121,6 +129,52 @@ public class WxCpServiceImpl implements WxCpService { | |||
return wxCpConfigStorage.getAccessToken(); | |||
} | |||
public String getJsapiTicket() throws WxErrorException { | |||
return getJsapiTicket(false); | |||
} | |||
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { | |||
if (forceRefresh) { | |||
wxCpConfigStorage.expireJsapiTicket(); | |||
} | |||
if (wxCpConfigStorage.isJsapiTicketExpired()) { | |||
synchronized (globalJsapiTicketRefreshLock) { | |||
if (wxCpConfigStorage.isJsapiTicketExpired()) { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket"; | |||
String responseContent = execute(new SimpleGetRequestExecutor(), url, null); | |||
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||
String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||
wxCpConfigStorage.updateJsapiTicket(jsapiTicket, expiresInSeconds); | |||
} | |||
} | |||
} | |||
return wxCpConfigStorage.getJsapiTicket(); | |||
} | |||
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException { | |||
long timestamp = System.currentTimeMillis() / 1000; | |||
String noncestr = RandomUtils.getRandomStr(); | |||
String jsapiTicket = getJsapiTicket(false); | |||
try { | |||
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); | |||
return jsapiSignature; | |||
} catch (NoSuchAlgorithmException e) { | |||
throw new RuntimeException(e); | |||
} | |||
} | |||
public void messageSend(WxCpMessage message) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; | |||
post(url, message.toJson()); | |||
@@ -358,10 +412,10 @@ public class WxCpServiceImpl implements WxCpService { | |||
} | |||
@Override | |||
public String oauth2buildAuthorizationUrl(String state) { | |||
public String oauth2buildAuthorizationUrl(String redirectUri, String state) { | |||
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?" ; | |||
url += "appid=" + wxCpConfigStorage.getCorpId(); | |||
url += "&redirect_uri=" + URIUtil.encodeURIComponent(wxCpConfigStorage.getOauth2redirectUri()); | |||
url += "&redirect_uri=" + URIUtil.encodeURIComponent(redirectUri); | |||
url += "&response_type=code"; | |||
url += "&scope=snsapi_base"; | |||
if (state != null) { | |||
@@ -373,22 +427,13 @@ public class WxCpServiceImpl implements WxCpService { | |||
@Override | |||
public String[] oauth2getUserInfo(String code) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?"; | |||
url += "access_token=" + wxCpConfigStorage.getAccessToken(); | |||
url += "&code=" + code; | |||
url += "&agendid=" + wxCpConfigStorage.getAgentId(); | |||
try { | |||
RequestExecutor<String, String> executor = new SimpleGetRequestExecutor(); | |||
String responseText = executor.execute(getHttpclient(), httpProxy, url, null); | |||
JsonElement je = Streams.parse(new JsonReader(new StringReader(responseText))); | |||
JsonObject jo = je.getAsJsonObject(); | |||
return new String[] {GsonHelper.getString(jo, "UserId"), GsonHelper.getString(jo, "DeviceId")}; | |||
} catch (ClientProtocolException e) { | |||
throw new RuntimeException(e); | |||
} catch (IOException e) { | |||
throw new RuntimeException(e); | |||
} | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?" | |||
+ "code=" + code | |||
+ "&agendid=" + wxCpConfigStorage.getAgentId(); | |||
String responseText = get(url, null); | |||
JsonElement je = Streams.parse(new JsonReader(new StringReader(responseText))); | |||
JsonObject jo = je.getAsJsonObject(); | |||
return new String[] {GsonHelper.getString(jo, "UserId"), GsonHelper.getString(jo, "DeviceId")}; | |||
} | |||
@Override | |||
@@ -477,7 +522,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
* 42001 access_token超时 | |||
*/ | |||
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | |||
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||
// 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||
wxCpConfigStorage.expireAccessToken(); | |||
return execute(executor, uri, data); | |||
} | |||
@@ -2,12 +2,14 @@ package me.chanjar.weixin.cp.bean; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* 微信部门 | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public class WxCpDepart { | |||
public class WxCpDepart implements Serializable { | |||
private Integer id; | |||
private String name; | |||
@@ -3,6 +3,7 @@ package me.chanjar.weixin.cp.bean; | |||
import me.chanjar.weixin.cp.bean.messagebuilder.*; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -11,7 +12,7 @@ import java.util.List; | |||
* @author Daniel Qian | |||
* | |||
*/ | |||
public class WxCpMessage { | |||
public class WxCpMessage implements Serializable { | |||
private String toUser; | |||
private String toParty; | |||
@@ -2,10 +2,12 @@ package me.chanjar.weixin.cp.bean; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* Created by Daniel Qian | |||
*/ | |||
public class WxCpTag { | |||
public class WxCpTag implements Serializable { | |||
private String id; | |||
@@ -2,6 +2,7 @@ package me.chanjar.weixin.cp.bean; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -10,7 +11,7 @@ import java.util.List; | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public class WxCpUser { | |||
public class WxCpUser implements Serializable { | |||
private String userId; | |||
private String name; | |||
@@ -10,6 +10,7 @@ import org.apache.commons.io.IOUtils; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -25,7 +26,7 @@ import java.util.List; | |||
* @author Daniel Qian | |||
*/ | |||
@XStreamAlias("xml") | |||
public class WxCpXmlMessage { | |||
public class WxCpXmlMessage implements Serializable { | |||
/////////////////////// | |||
// 以下都是微信推送过来的消息的xml的element所对应的属性 | |||
@@ -62,7 +62,7 @@ public class WxCpDemoServer { | |||
@Override | |||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, | |||
WxCpService wxCpService, WxSessionManager sessionManager) { | |||
String href = "<a href=\"" + wxCpService.oauth2buildAuthorizationUrl(null) | |||
String href = "<a href=\"" + wxCpService.oauth2buildAuthorizationUrl(wxCpConfigStorage.getOauth2redirectUri(), null) | |||
+ "\">测试oauth2</a>"; | |||
return WxCpXmlOutMessage | |||
.TEXT() | |||
@@ -6,7 +6,7 @@ | |||
<parent> | |||
<groupId>me.chanjar</groupId> | |||
<artifactId>weixin-java-parent</artifactId> | |||
<version>1.1.1</version> | |||
<version>1.1.2</version> | |||
</parent> | |||
<artifactId>weixin-java-mp</artifactId> | |||
<name>WeiXin Java Tools - MP</name> | |||
@@ -0,0 +1,68 @@ | |||
/** | |||
* Created by qianjia on 15/1/25. | |||
*/ | |||
public class TestNonAtomicLongAssignment { | |||
private static final long HI = 1l << 32; | |||
private static final long LO = 1l; | |||
private static final long TEST_NUMBER = HI | LO; | |||
private static long assignee = 0l; | |||
public static void main(String[] args) { | |||
Thread writer = new Thread(new Runnable() { | |||
@Override | |||
public void run() { | |||
while (true) { | |||
assignee = TEST_NUMBER; | |||
} | |||
} | |||
}); | |||
writer.setDaemon(true); | |||
Thread reader = new Thread(new Runnable() { | |||
@Override | |||
public void run() { | |||
long i = 0; | |||
while (true) { | |||
i++; | |||
long test = assignee; | |||
if (test != TEST_NUMBER) { | |||
System.out.print(i + " times:" + toBin(test)); | |||
break; | |||
} | |||
} | |||
} | |||
}); | |||
// Thread worker = new Thread(new Runnable() { | |||
// @Override | |||
// public void run() { | |||
// double d = 89009808877238948224343435452333323113131313133434434341212323232424243434335354232390490189190420928348910913094983.323334401928d; | |||
// while(true) { | |||
// Math.cbrt(d); | |||
// d = d - 1l; | |||
// } | |||
// } | |||
// }); | |||
// worker.setDaemon(true); | |||
// worker.start(); | |||
writer.start(); | |||
reader.start(); | |||
} | |||
public static String toBin(long n) { | |||
StringBuilder sb = new StringBuilder(Long.toBinaryString(n)); | |||
int padding = 64 - sb.length(); | |||
while (padding > 0) { | |||
sb.insert(0, '0'); | |||
padding--; | |||
} | |||
return sb.toString(); | |||
} | |||
} |
@@ -51,6 +51,18 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
return jsapiTicket; | |||
} | |||
public void setJsapiTicket(String jsapiTicket) { | |||
this.jsapiTicket = jsapiTicket; | |||
} | |||
public long getJsapiTicketExpiresTime() { | |||
return jsapiTicketExpiresTime; | |||
} | |||
public void setJsapiTicketExpiresTime(long jsapiTicketExpiresTime) { | |||
this.jsapiTicketExpiresTime = jsapiTicketExpiresTime; | |||
} | |||
public boolean isJsapiTicketExpired() { | |||
return System.currentTimeMillis() > this.jsapiTicketExpiresTime; | |||
} | |||
@@ -167,5 +179,4 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' + | |||
'}'; | |||
} | |||
} |
@@ -5,9 +5,9 @@ import me.chanjar.weixin.common.session.InternalSessionManager; | |||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.LogExceptionHandler; | |||
import me.chanjar.weixin.common.util.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.util.WxMessageDuplicateChecker; | |||
import me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker; | |||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | |||
import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | |||
import org.slf4j.Logger; | |||
@@ -87,8 +87,8 @@ public class WxMpMessageRouter { | |||
/** | |||
* <pre> | |||
* 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker} | |||
* 设置自定义的 {@link me.chanjar.weixin.common.api.WxMessageDuplicateChecker} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker} | |||
* </pre> | |||
* @param messageDuplicateChecker | |||
*/ | |||
@@ -109,7 +109,7 @@ public class WxMpMessageRouter { | |||
/** | |||
* <pre> | |||
* 设置自定义的{@link me.chanjar.weixin.common.util.WxErrorExceptionHandler} | |||
* 设置自定义的{@link me.chanjar.weixin.common.api.WxErrorExceptionHandler} | |||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.LogExceptionHandler} | |||
* </pre> | |||
* @param exceptionHandler | |||
@@ -203,7 +203,8 @@ public class WxMpMessageRouter { | |||
if (wxMessage.getMsgId() == null) { | |||
messageId = String.valueOf(wxMessage.getCreateTime()) | |||
+ "-" + wxMessage.getFromUserName() | |||
+ "-" + String.valueOf(wxMessage.getEventKey() == null ? "" : wxMessage.getEvent()); | |||
+ "-" + String.valueOf(wxMessage.getEventKey() == null ? "" : wxMessage.getEventKey()) | |||
+ "-" + String.valueOf(wxMessage.getEvent() == null ? "" : wxMessage.getEvent()) | |||
; | |||
} else { | |||
messageId = String.valueOf(wxMessage.getMsgId()); | |||
@@ -2,7 +2,7 @@ package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | |||
@@ -1,10 +1,9 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.common.session.WxSessionManager; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.mp.bean.*; | |||
import me.chanjar.weixin.mp.bean.result.*; | |||
@@ -12,13 +11,16 @@ import me.chanjar.weixin.mp.bean.result.*; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Date; | |||
import java.util.List; | |||
/** | |||
* 微信API的Service | |||
*/ | |||
public interface WxMpService { | |||
public static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); | |||
/** | |||
* <pre> | |||
* 验证推送过来的消息的正确性 | |||
@@ -86,7 +88,7 @@ public interface WxMpService { | |||
* @param url url | |||
* @return | |||
*/ | |||
public WxMpJsapiSignature createJsapiSignature(String url) throws WxErrorException; | |||
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; | |||
/** | |||
* <pre> | |||
@@ -422,6 +424,40 @@ public interface WxMpService { | |||
*/ | |||
public boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken); | |||
/** | |||
* <pre> | |||
* 获取微信服务器IP地址 | |||
* http://mp.weixin.qq.com/wiki/0/2ad4b6bfd29f30f71d39616c2a0fcedc.html | |||
* </pre> | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
String[] getCallbackIP() throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 获取用户增减数据 | |||
* http://mp.weixin.qq.com/wiki/3/ecfed6e1a0a03b5f35e5efac98e864b7.html | |||
* </pre> | |||
* @param beginDate 最大时间跨度7天 | |||
* @param endDate endDate不能早于begingDate | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
List<WxMpUserSummary> getUserSummary(Date beginDate, Date endDate) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 获取累计用户数据 | |||
* http://mp.weixin.qq.com/wiki/3/ecfed6e1a0a03b5f35e5efac98e864b7.html | |||
* </pre> | |||
* @param beginDate 最大时间跨度7天 | |||
* @param endDate endDate不能早于begingDate | |||
* @return | |||
* @throws WxErrorException | |||
*/ | |||
List<WxMpUserCumulate> getUserCumulate(Date beginDate, Date endDate) throws WxErrorException; | |||
/** | |||
* 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求 | |||
* @param url | |||
@@ -1,5 +1,6 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import com.google.gson.JsonArray; | |||
import com.google.gson.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.internal.Streams; | |||
@@ -7,11 +8,13 @@ import com.google.gson.reflect.TypeToken; | |||
import com.google.gson.stream.JsonReader; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.bean.WxMenu; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
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.WxSessionManager; | |||
import me.chanjar.weixin.common.util.RandomUtils; | |||
import me.chanjar.weixin.common.util.StringUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.fs.FileUtils; | |||
@@ -41,16 +44,12 @@ import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.StringReader; | |||
import java.security.NoSuchAlgorithmException; | |||
import java.util.Date; | |||
import java.util.List; | |||
import java.util.Random; | |||
import java.util.UUID; | |||
public class WxMpServiceImpl implements WxMpService { | |||
protected final String RANDOM_STR = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; | |||
protected final Random RANDOM = new Random(); | |||
protected final Logger log = LoggerFactory.getLogger(WxMpServiceImpl.class); | |||
/** | |||
@@ -148,9 +147,9 @@ public class WxMpServiceImpl implements WxMpService { | |||
return wxMpConfigStorage.getJsapiTicket(); | |||
} | |||
public WxMpJsapiSignature createJsapiSignature(String url) throws WxErrorException { | |||
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException { | |||
long timestamp = System.currentTimeMillis() / 1000; | |||
String noncestr = getRandomStr(); | |||
String noncestr = RandomUtils.getRandomStr(); | |||
String jsapiTicket = getJsapiTicket(false); | |||
try { | |||
String signature = SHA1.genWithAmple( | |||
@@ -159,7 +158,7 @@ public class WxMpServiceImpl implements WxMpService { | |||
"timestamp=" + timestamp, | |||
"url=" + url | |||
); | |||
WxMpJsapiSignature jsapiSignature = new WxMpJsapiSignature(); | |||
WxJsapiSignature jsapiSignature = new WxJsapiSignature(); | |||
jsapiSignature.setTimestamp(timestamp); | |||
jsapiSignature.setNoncestr(noncestr); | |||
jsapiSignature.setUrl(url); | |||
@@ -170,14 +169,6 @@ public class WxMpServiceImpl implements WxMpService { | |||
} | |||
} | |||
protected String getRandomStr() { | |||
StringBuilder sb = new StringBuilder(); | |||
for (int i = 0; i < 16; i++) { | |||
sb.append(RANDOM_STR.charAt(RANDOM.nextInt(RANDOM_STR.length()))); | |||
} | |||
return sb.toString(); | |||
} | |||
public void customMessageSend(WxMpCustomMessage message) throws WxErrorException { | |||
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | |||
execute(new SimplePostRequestExecutor(), url, message.toJson()); | |||
@@ -446,7 +437,7 @@ public class WxMpServiceImpl implements WxMpService { | |||
@Override | |||
public boolean oauth2validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken) { | |||
String url = "https://api.weixin.qq.com/sns/auth?"; | |||
url += "access_token=" + oAuth2AccessToken; | |||
url += "access_token=" + oAuth2AccessToken.getAccessToken(); | |||
url += "&openid=" + oAuth2AccessToken.getOpenId(); | |||
try { | |||
@@ -462,6 +453,42 @@ public class WxMpServiceImpl implements WxMpService { | |||
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 = Streams.parse(new JsonReader(new StringReader(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 List<WxMpUserSummary> getUserSummary(Date beginDate, Date endDate) throws WxErrorException { | |||
String url = "https://api.weixin.qq.com/datacube/getusersummary"; | |||
JsonObject param = new JsonObject(); | |||
param.addProperty("begin_date", SIMPLE_DATE_FORMAT.format(beginDate)); | |||
param.addProperty("end_date", SIMPLE_DATE_FORMAT.format(endDate)); | |||
String responseContent = post(url, param.toString()); | |||
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
return WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement.getAsJsonObject().get("list"), new TypeToken<List<WxMpUserSummary>>(){}.getType()); | |||
} | |||
@Override | |||
public List<WxMpUserCumulate> getUserCumulate(Date beginDate, Date endDate) throws WxErrorException { | |||
String url = "https://api.weixin.qq.com/datacube/getusercumulate"; | |||
JsonObject param = new JsonObject(); | |||
param.addProperty("begin_date", SIMPLE_DATE_FORMAT.format(beginDate)); | |||
param.addProperty("end_date", SIMPLE_DATE_FORMAT.format(endDate)); | |||
String responseContent = post(url, param.toString()); | |||
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||
return WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement.getAsJsonObject().get("list"), new TypeToken<List<WxMpUserCumulate>>(){}.getType()); | |||
} | |||
public String get(String url, String queryParam) throws WxErrorException { | |||
return execute(new SimpleGetRequestExecutor(), url, queryParam); | |||
} | |||
@@ -3,6 +3,7 @@ package me.chanjar.weixin.mp.bean; | |||
import me.chanjar.weixin.mp.bean.custombuilder.*; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -11,7 +12,7 @@ import java.util.List; | |||
* @author chanjarster | |||
* | |||
*/ | |||
public class WxMpCustomMessage { | |||
public class WxMpCustomMessage implements Serializable { | |||
private String toUser; | |||
private String msgType; | |||
@@ -2,12 +2,14 @@ package me.chanjar.weixin.mp.bean; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* 微信用户分组 | |||
* @author chanjarster | |||
* | |||
*/ | |||
public class WxMpGroup { | |||
public class WxMpGroup implements Serializable { | |||
private long id = -1; | |||
private String name; | |||
@@ -2,12 +2,14 @@ package me.chanjar.weixin.mp.bean; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* 分组群发的消息 | |||
* | |||
* @author chanjarster | |||
*/ | |||
public class WxMpMassGroupMessage { | |||
public class WxMpMassGroupMessage implements Serializable { | |||
private Long groupId; | |||
private String msgtype; | |||
@@ -2,6 +2,7 @@ package me.chanjar.weixin.mp.bean; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -10,7 +11,7 @@ import java.util.List; | |||
* @author chanjarster | |||
* | |||
*/ | |||
public class WxMpMassNews { | |||
public class WxMpMassNews implements Serializable { | |||
private List<WxMpMassNewsArticle> articles = new ArrayList<WxMpMassNewsArticle>(); | |||
@@ -2,6 +2,7 @@ package me.chanjar.weixin.mp.bean; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -10,7 +11,7 @@ import java.util.List; | |||
* | |||
* @author chanjarster | |||
*/ | |||
public class WxMpMassOpenIdsMessage { | |||
public class WxMpMassOpenIdsMessage implements Serializable { | |||
private List<String> toUsers = new ArrayList<String>(); | |||
private String msgType; | |||
@@ -2,12 +2,14 @@ package me.chanjar.weixin.mp.bean; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* 群发时用到的视频素材 | |||
* | |||
* @author chanjarster | |||
*/ | |||
public class WxMpMassVideo { | |||
public class WxMpMassVideo implements Serializable { | |||
private String mediaId; | |||
private String title; | |||
@@ -2,6 +2,8 @@ package me.chanjar.weixin.mp.bean; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* 语义理解查询用对象 | |||
* | |||
@@ -9,7 +11,7 @@ import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public class WxMpSemanticQuery { | |||
public class WxMpSemanticQuery implements Serializable { | |||
private String query; | |||
private String category; | |||
@@ -1,9 +1,11 @@ | |||
package me.chanjar.weixin.mp.bean; | |||
import java.io.Serializable; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
public class WxMpTemplateData { | |||
public class WxMpTemplateData implements Serializable { | |||
private String name; | |||
private String value; | |||
@@ -2,10 +2,11 @@ package me.chanjar.weixin.mp.bean; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
public class WxMpTemplateMessage { | |||
public class WxMpTemplateMessage implements Serializable { | |||
private String toUser; | |||
private String templateId; | |||
@@ -10,6 +10,7 @@ import org.apache.commons.io.IOUtils; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.Serializable; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
@@ -25,7 +26,7 @@ import java.util.List; | |||
* @author chanjarster | |||
*/ | |||
@XStreamAlias("xml") | |||
public class WxMpXmlMessage { | |||
public class WxMpXmlMessage implements Serializable { | |||
/////////////////////// | |||
// 以下都是微信推送过来的消息的xml的element所对应的属性 | |||
@@ -8,8 +8,10 @@ import me.chanjar.weixin.mp.bean.outxmlbuilder.*; | |||
import me.chanjar.weixin.mp.util.crypto.WxMpCryptUtil; | |||
import me.chanjar.weixin.mp.util.xml.XStreamTransformer; | |||
import java.io.Serializable; | |||
@XStreamAlias("xml") | |||
public abstract class WxMpXmlOutMessage { | |||
public abstract class WxMpXmlOutMessage implements Serializable { | |||
@XStreamAlias("ToUserName") | |||
@XStreamConverter(value=XStreamCDataConverter.class) | |||
@@ -2,6 +2,8 @@ package me.chanjar.weixin.mp.bean.result; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* <pre> | |||
* 群发消息一发送就返回的结果 | |||
@@ -13,7 +15,7 @@ import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
* @author chanjarster | |||
* | |||
*/ | |||
public class WxMpMassSendResult { | |||
public class WxMpMassSendResult implements Serializable { | |||
private String errorCode; | |||
private String errorMsg; | |||
@@ -2,6 +2,8 @@ package me.chanjar.weixin.mp.bean.result; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* <pre> | |||
* 上传群发用的素材的结果 | |||
@@ -10,7 +12,7 @@ import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
* @author chanjarster | |||
* | |||
*/ | |||
public class WxMpMassUploadResult { | |||
public class WxMpMassUploadResult implements Serializable { | |||
private String type; | |||
private String mediaId; | |||
@@ -2,7 +2,9 @@ package me.chanjar.weixin.mp.bean.result; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
public class WxMpOAuth2AccessToken { | |||
import java.io.Serializable; | |||
public class WxMpOAuth2AccessToken implements Serializable { | |||
private String accessToken; | |||
@@ -2,12 +2,14 @@ package me.chanjar.weixin.mp.bean.result; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* 换取二维码的Ticket | |||
* | |||
* @author chanjarster | |||
*/ | |||
public class WxMpQrCodeTicket { | |||
public class WxMpQrCodeTicket implements Serializable { | |||
protected String ticket; | |||
protected int expire_seconds = -1; | |||
@@ -2,6 +2,8 @@ package me.chanjar.weixin.mp.bean.result; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* 语义理解查询结果对象 | |||
* | |||
@@ -9,7 +11,7 @@ import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public class WxMpSemanticQueryResult { | |||
public class WxMpSemanticQueryResult implements Serializable { | |||
private String query; | |||
private String type; | |||
@@ -2,12 +2,14 @@ package me.chanjar.weixin.mp.bean.result; | |||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
import java.io.Serializable; | |||
/** | |||
* 微信用户信息 | |||
* @author chanjarster | |||
* | |||
*/ | |||
public class WxMpUser { | |||
public class WxMpUser implements Serializable { | |||
protected Boolean subscribe; | |||
protected String openId; | |||
@@ -20,7 +22,11 @@ public class WxMpUser { | |||
protected String headImgUrl; | |||
protected Long subscribeTime; | |||
protected String unionId; | |||
protected Integer sexId; | |||
public Boolean getSubscribe() { | |||
return subscribe; | |||
} | |||
public Boolean isSubscribe() { | |||
return subscribe; | |||
} | |||
@@ -87,7 +93,16 @@ public class WxMpUser { | |||
public void setUnionId(String unionId) { | |||
this.unionId = unionId; | |||
} | |||
public Integer getSexId() { | |||
return sexId; | |||
} | |||
public void setSexId(Integer sexId) { | |||
this.sexId = sexId; | |||
} | |||
public static WxMpUser fromJson(String json) { | |||
return WxMpGsonBuilder.INSTANCE.create().fromJson(json, WxMpUser.class); | |||
} | |||
@@ -0,0 +1,41 @@ | |||
package me.chanjar.weixin.mp.bean.result; | |||
import java.io.Serializable; | |||
import java.util.Date; | |||
/** | |||
* <pre> | |||
* 累计用户数据接口的返回JSON数据包 | |||
* http://mp.weixin.qq.com/wiki/3/ecfed6e1a0a03b5f35e5efac98e864b7.html | |||
* </pre> | |||
*/ | |||
public class WxMpUserCumulate implements Serializable { | |||
private Date refDate; | |||
private Integer cumulateUser; | |||
public Date getRefDate() { | |||
return refDate; | |||
} | |||
public void setRefDate(Date refDate) { | |||
this.refDate = refDate; | |||
} | |||
public Integer getCumulateUser() { | |||
return cumulateUser; | |||
} | |||
public void setCumulateUser(Integer cumulateUser) { | |||
this.cumulateUser = cumulateUser; | |||
} | |||
@Override | |||
public String toString() { | |||
return "WxMpUserCumulate{" + | |||
"refDate=" + refDate + | |||
", cumulateUser=" + cumulateUser + | |||
'}'; | |||
} | |||
} |
@@ -0,0 +1,63 @@ | |||
package me.chanjar.weixin.mp.bean.result; | |||
import java.io.Serializable; | |||
import java.util.Date; | |||
/** | |||
* <pre> | |||
* 用户增减数据接口的返回JSON数据包 | |||
* http://mp.weixin.qq.com/wiki/3/ecfed6e1a0a03b5f35e5efac98e864b7.html | |||
* </pre> | |||
*/ | |||
public class WxMpUserSummary implements Serializable { | |||
private Date refDate; | |||
private Integer userSource; | |||
private Integer newUser; | |||
private Integer cancelUser; | |||
public Date getRefDate() { | |||
return refDate; | |||
} | |||
public void setRefDate(Date refDate) { | |||
this.refDate = refDate; | |||
} | |||
public Integer getUserSource() { | |||
return userSource; | |||
} | |||
public void setUserSource(Integer userSource) { | |||
this.userSource = userSource; | |||
} | |||
public Integer getNewUser() { | |||
return newUser; | |||
} | |||
public void setNewUser(Integer newUser) { | |||
this.newUser = newUser; | |||
} | |||
public Integer getCancelUser() { | |||
return cancelUser; | |||
} | |||
public void setCancelUser(Integer cancelUser) { | |||
this.cancelUser = cancelUser; | |||
} | |||
@Override | |||
public String toString() { | |||
return "WxMpUserSummary{" + | |||
"refDate=" + refDate + | |||
", userSource=" + userSource + | |||
", newUser=" + newUser + | |||
", cancelUser=" + cancelUser + | |||
'}'; | |||
} | |||
} |
@@ -16,7 +16,7 @@ public class WxMpGsonBuilder { | |||
INSTANCE.registerTypeAdapter(WxMpMassGroupMessage.class, new WxMpMassGroupMessageGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpMassOpenIdsMessage.class, new WxMpMassOpenIdsMessageGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpGroup.class, new WxMpGroupGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpUser.class, new WxUserGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpUser.class, new WxMpUserGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpUserList.class, new WxUserListGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpMassVideo.class, new WxMpMassVideoAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpMassSendResult.class, new WxMpMassSendResultAdapter()); | |||
@@ -25,6 +25,8 @@ public class WxMpGsonBuilder { | |||
INSTANCE.registerTypeAdapter(WxMpTemplateMessage.class, new WxMpTemplateMessageGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpSemanticQueryResult.class, new WxMpSemanticQueryResultAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpOAuth2AccessToken.class, new WxMpOAuth2AccessTokenAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpUserSummary.class, new WxMpUserSummaryGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxMpUserCumulate.class, new WxMpUserCumulateGsonAdapter()); | |||
} | |||
public static Gson create() { | |||
@@ -0,0 +1,47 @@ | |||
/* | |||
* 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.WxMpMassUploadResult; | |||
import me.chanjar.weixin.mp.bean.result.WxMpUserCumulate; | |||
import me.chanjar.weixin.mp.bean.result.WxMpUserSummary; | |||
import java.lang.reflect.Type; | |||
import java.text.ParseException; | |||
import java.text.SimpleDateFormat; | |||
/** | |||
* | |||
* @author Daniel Qian | |||
* | |||
*/ | |||
public class WxMpUserCumulateGsonAdapter implements JsonDeserializer<WxMpUserCumulate> { | |||
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); | |||
public WxMpUserCumulate deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||
WxMpUserCumulate cumulate = new WxMpUserCumulate(); | |||
JsonObject summaryJsonObject = json.getAsJsonObject(); | |||
try { | |||
String refDate = GsonHelper.getString(summaryJsonObject, "ref_date"); | |||
if (refDate != null) { | |||
cumulate.setRefDate(SIMPLE_DATE_FORMAT.parse(refDate)); | |||
} | |||
cumulate.setCumulateUser(GsonHelper.getInteger(summaryJsonObject, "cumulate_user")); | |||
} catch (ParseException e) { | |||
throw new JsonParseException(e); | |||
} | |||
return cumulate; | |||
} | |||
} |
@@ -14,7 +14,7 @@ import me.chanjar.weixin.mp.bean.result.WxMpUser; | |||
import java.lang.reflect.Type; | |||
public class WxUserGsonAdapter implements JsonDeserializer<WxMpUser> { | |||
public class WxMpUserGsonAdapter implements JsonDeserializer<WxMpUser> { | |||
public WxMpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||
JsonObject o = json.getAsJsonObject(); | |||
@@ -29,10 +29,11 @@ public class WxUserGsonAdapter implements JsonDeserializer<WxMpUser> { | |||
wxMpUser.setProvince(GsonHelper.getString(o, "province")); | |||
wxMpUser.setSubscribeTime(GsonHelper.getLong(o, "subscribe_time")); | |||
wxMpUser.setUnionId(GsonHelper.getString(o, "unionid")); | |||
Integer sex = GsonHelper.getInteger(o, "sex"); | |||
if(new Integer(1).equals(sex)) { | |||
Integer sexId = GsonHelper.getInteger(o, "sex"); | |||
wxMpUser.setSexId(sexId); | |||
if(new Integer(1).equals(sexId)) { | |||
wxMpUser.setSex("男"); | |||
} else if (new Integer(2).equals(sex)) { | |||
} else if (new Integer(2).equals(sexId)) { | |||
wxMpUser.setSex("女"); | |||
} else { | |||
wxMpUser.setSex("未知"); |
@@ -0,0 +1,47 @@ | |||
/* | |||
* 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.WxMpMassUploadResult; | |||
import me.chanjar.weixin.mp.bean.result.WxMpUserSummary; | |||
import java.lang.reflect.Type; | |||
import java.text.ParseException; | |||
import java.text.SimpleDateFormat; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
public class WxMpUserSummaryGsonAdapter implements JsonDeserializer<WxMpUserSummary> { | |||
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); | |||
public WxMpUserSummary deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) | |||
throws JsonParseException { | |||
WxMpUserSummary summary = new WxMpUserSummary(); | |||
JsonObject summaryJsonObject = json.getAsJsonObject(); | |||
try { | |||
String refDate = GsonHelper.getString(summaryJsonObject, "ref_date"); | |||
if (refDate != null) { | |||
summary.setRefDate(SIMPLE_DATE_FORMAT.parse(refDate)); | |||
} | |||
summary.setUserSource(GsonHelper.getInteger(summaryJsonObject, "user_source")); | |||
summary.setNewUser(GsonHelper.getInteger(summaryJsonObject, "new_user")); | |||
summary.setCancelUser(GsonHelper.getInteger(summaryJsonObject, "cancel_user")); | |||
} catch (ParseException e) { | |||
throw new JsonParseException(e); | |||
} | |||
return summary; | |||
} | |||
} |
@@ -22,9 +22,11 @@ public class WxUserListGsonAdapter implements JsonDeserializer<WxMpUserList> { | |||
wxMpUserList.setTotal(GsonHelper.getInteger(o, "total")); | |||
wxMpUserList.setCount(GsonHelper.getInteger(o, "count")); | |||
wxMpUserList.setNextOpenId(GsonHelper.getString(o, "next_openid")); | |||
JsonArray data = o.get("data").getAsJsonObject().get("openid").getAsJsonArray(); | |||
for (int i = 0; i < data.size(); i++) { | |||
wxMpUserList.getOpenIds().add(GsonHelper.getAsString(data.get(i))); | |||
if (!o.get("data").isJsonNull() && !o.get("data").getAsJsonObject().get("openid").isJsonNull()) { | |||
JsonArray data = o.get("data").getAsJsonObject().get("openid").getAsJsonArray(); | |||
for (int i = 0; i < data.size(); i++) { | |||
wxMpUserList.getOpenIds().add(GsonHelper.getAsString(data.get(i))); | |||
} | |||
} | |||
return wxMpUserList; | |||
} | |||
@@ -0,0 +1,69 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import com.google.inject.Inject; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
import me.chanjar.weixin.common.exception.WxErrorException; | |||
import me.chanjar.weixin.common.session.WxSession; | |||
import me.chanjar.weixin.mp.bean.WxMpMassGroupMessage; | |||
import me.chanjar.weixin.mp.bean.WxMpMassNews; | |||
import me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage; | |||
import me.chanjar.weixin.mp.bean.WxMpMassVideo; | |||
import me.chanjar.weixin.mp.bean.result.WxMpMassSendResult; | |||
import me.chanjar.weixin.mp.bean.result.WxMpMassUploadResult; | |||
import me.chanjar.weixin.mp.bean.result.WxMpUserCumulate; | |||
import me.chanjar.weixin.mp.bean.result.WxMpUserSummary; | |||
import org.testng.Assert; | |||
import org.testng.annotations.DataProvider; | |||
import org.testng.annotations.Guice; | |||
import org.testng.annotations.Test; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.text.ParseException; | |||
import java.text.SimpleDateFormat; | |||
import java.util.Arrays; | |||
import java.util.Date; | |||
import java.util.List; | |||
/** | |||
* | |||
* @author chanjarster | |||
* | |||
*/ | |||
@Test(groups = "miscAPI", dependsOnGroups = { "baseAPI"}) | |||
@Guice(modules = ApiTestModule.class) | |||
public class WxMpMiscAPITest { | |||
@Inject | |||
protected WxMpServiceImpl wxService; | |||
@Test | |||
public void testGetCallbackIP() throws WxErrorException { | |||
String[] ipArray = wxService.getCallbackIP(); | |||
System.out.println(Arrays.toString(ipArray)); | |||
Assert.assertNotNull(ipArray); | |||
Assert.assertNotEquals(ipArray.length, 0); | |||
} | |||
@Test | |||
public void testGetUserSummary() throws WxErrorException, ParseException { | |||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); | |||
Date beginDate = simpleDateFormat.parse("2015-01-01"); | |||
Date endDate = simpleDateFormat.parse("2015-01-02"); | |||
List<WxMpUserSummary> summaries = wxService.getUserSummary(beginDate, endDate); | |||
System.out.println(summaries); | |||
Assert.assertNotNull(summaries); | |||
} | |||
@Test | |||
public void testGetUserCumulate() throws WxErrorException, ParseException { | |||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); | |||
Date beginDate = simpleDateFormat.parse("2015-01-01"); | |||
Date endDate = simpleDateFormat.parse("2015-01-02"); | |||
List<WxMpUserCumulate> cumulates = wxService.getUserCumulate(beginDate, endDate); | |||
System.out.println(cumulates); | |||
Assert.assertNotNull(cumulates); | |||
} | |||
} |
@@ -15,6 +15,7 @@ | |||
<class name="me.chanjar.weixin.mp.api.WxMpShortUrlAPITest" /> | |||
<class name="me.chanjar.weixin.mp.api.WxMpMessageRouterTest" /> | |||
<class name="me.chanjar.weixin.mp.api.WxMpJsAPITest" /> | |||
<class name="me.chanjar.weixin.mp.api.WxMpMiscAPITest" /> | |||
</classes> | |||
</test> | |||