diff --git a/.gitignore b/.gitignore index ba4f93dc..740251ba 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,6 @@ target .project .classpath -src/test/resources/test-config.xml sw-pom.xml *.iml +test-config.xml diff --git a/README.md b/README.md index 0dbe5466..6ab18863 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,34 @@ -weixin-java-tools +weixin-java-cp =========== -微信java开发工具集,本项目主要分为两大块:微信消息路由器、微信Java API +微信公众号、企业号Java SDK。 -特性列表: - -1. 不基于Servlet、和其他MVC框架,仅作为工具使用,提供更多的灵活性 -2. 详尽的单元测试代码,可以拿来当example用 -3. 详尽的javadoc -4. access token过期自动刷新的功能 -5. 微信服务端繁忙自动重试的功能 -6. 提供微信错误信息的异常处理机制 +从``1.0.3``开始,本项目拆分成3个部分: +1. weixin-java-common,公共lib +2. weixin-java-cp,企业号SDK +3. weixin-java-mp,公众号(订阅号、服务号)SDK 详细文档请看 [wiki](https://github.com/chanjarster/weixin-java-tools/wiki) -# Quickstart +# Quick Start + +如果要开发公众号(订阅号、服务号)应用,在你的maven项目中添加: -在你的maven项目中添加: ```xml me.chanjar - weixin-java-tools - 1.0.2 + weixin-java-mp + 1.0.3 ``` -如果要使用``*-SNAPSHOT``版,则需要在你的``pom.xml``中添加这段: +如果要开发企业号应用,在你的maven项目中添加: ```xml - - - - sonatype snapshots - https://oss.sonatype.org/content/repositories/snapshots/ - - -``` - -## Hello World -```java -WxConfigStorage config = new WxInMemoryConfigStorage(); -config.setAppId(...); // 设置微信公众号的appid -config.setSecret(...); // 设置微信公众号的app secret -config.setToken(...); // 设置微信公众号的token - -WxServiceImpl wxService = new WxServiceImpl(); -wxService.setWxConfigStorage(config); - -// 用户的openid在下面地址获得 -// https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index&type=用户管理&form=获取关注者列表接口%20/user/get -String openId = ...; -WxCustomMessage message = WxCustomMessage.TEXT().toUser(openId).content("Hello World").build(); -wxService.customMessageSend(message); + + me.chanjar + weixin-java-cp + 1.0.3 + ``` diff --git a/pom.xml b/pom.xml index 1ac3f48c..cd72620e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,10 +4,11 @@ xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 me.chanjar - weixin-java-tools + weixin-java-parent 1.0.3-SNAPSHOT - WeiXin Java Tools - 用于开发微信公众号的Java工具 + pom + WeiXin Java Tools - Parent + 微信公众号、企业号上级POM https://github.com/chanjarster/weixin-java-tools @@ -30,19 +31,20 @@ https://github.com/chanjarster/weixin-java-tools + + weixin-java-common + weixin-java-cp + weixin-java-mp + + UTF-8 true true 4.3.5 + - - junit - junit - 4.11 - test - org.apache.httpcomponents fluent-hc @@ -63,34 +65,11 @@ jaxb-impl 2.2.7 - - org.apache.oltu.oauth2 - org.apache.oltu.oauth2.client - 1.0.0 - com.google.code.gson gson 2.2.2 - - org.testng - testng - 6.8.7 - test - - - org.mockito - mockito-all - 1.9.5 - test - - - com.google.inject - guice - 3.0 - test - org.apache.commons commons-lang3 @@ -106,20 +85,49 @@ commons-io 2.4 - - org.eclipse.jetty - jetty-server - 9.3.0.M0 - test - - - org.eclipse.jetty - jetty-servlet - 9.3.0.M0 - test - + + + + junit + junit + 4.11 + test + + + com.google.inject + guice + 3.0 + test + + + org.testng + testng + 6.8.7 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + org.eclipse.jetty + jetty-server + 9.3.0.M0 + test + + + org.eclipse.jetty + jetty-servlet + 9.3.0.M0 + test + + + + ossrh @@ -186,17 +194,17 @@ + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.17 + + + + - - org.apache.maven.plugins - maven-surefire-plugin - 2.17 - - - src/test/resources/testng.xml - - - org.sonatype.plugins nexus-staging-maven-plugin diff --git a/src/main/java/me/chanjar/weixin/bean/result/WxError.java b/src/main/java/me/chanjar/weixin/bean/result/WxError.java deleted file mode 100644 index 1e42e42d..00000000 --- a/src/main/java/me/chanjar/weixin/bean/result/WxError.java +++ /dev/null @@ -1,136 +0,0 @@ -package me.chanjar.weixin.bean.result; - -import java.util.HashMap; -import java.util.Map; - -import me.chanjar.weixin.util.json.WxGsonBuilder; - -/** - * 微信错误码说明 - * http://mp.weixin.qq.com/wiki/index.php?title=全局返回码说明 - * @author chanjarster - * - */ -public class WxError { - - protected static final Map errMap = new HashMap(); - - static { - errMap.put(-1, "系统繁忙"); - errMap.put(0, "请求成功"); - errMap.put(40001, "获取access_token时AppSecret错误,或者access_token无效"); - errMap.put(40002, "不合法的凭证类型"); - errMap.put(40003, "不合法的OpenID"); - errMap.put(40004, "不合法的媒体文件类型"); - errMap.put(40005, "不合法的文件类型"); - errMap.put(40006, "不合法的文件大小"); - errMap.put(40007, "不合法的媒体文件id"); - errMap.put(40008, "不合法的消息类型"); - errMap.put(40009, "不合法的图片文件大小"); - errMap.put(40010, "不合法的语音文件大小"); - errMap.put(40011, "不合法的视频文件大小"); - errMap.put(40012, "不合法的缩略图文件大小"); - errMap.put(40013, "不合法的APPID"); - errMap.put(40014, "不合法的access_token"); - errMap.put(40015, "不合法的菜单类型"); - errMap.put(40016, "不合法的按钮个数"); - errMap.put(40017, "不合法的按钮个数"); - errMap.put(40018, "不合法的按钮名字长度"); - errMap.put(40019, "不合法的按钮KEY长度"); - errMap.put(40020, "不合法的按钮URL长度"); - errMap.put(40021, "不合法的菜单版本号"); - errMap.put(40022, "不合法的子菜单级数"); - errMap.put(40023, "不合法的子菜单按钮个数"); - errMap.put(40024, "不合法的子菜单按钮类型"); - errMap.put(40025, "不合法的子菜单按钮名字长度"); - errMap.put(40026, "不合法的子菜单按钮KEY长度"); - errMap.put(40027, "不合法的子菜单按钮URL长度"); - errMap.put(40028, "不合法的自定义菜单使用用户"); - errMap.put(40029, "不合法的oauth_code"); - errMap.put(40030, "不合法的refresh_token"); - errMap.put(40031, "不合法的openid列表"); - errMap.put(40032, "不合法的openid列表长度"); - errMap.put(40033, "不合法的请求字符,不能包含\\uxxxx格式的字符"); - errMap.put(40035, "不合法的参数"); - errMap.put(40038, "不合法的请求格式"); - errMap.put(40039, "不合法的URL长度"); - errMap.put(40050, "不合法的分组id"); - errMap.put(40051, "分组名字不合法"); - errMap.put(41001, "缺少access_token参数"); - errMap.put(41002, "缺少appid参数"); - errMap.put(41003, "缺少refresh_token参数"); - errMap.put(41004, "缺少secret参数"); - errMap.put(41005, "缺少多媒体文件数据"); - errMap.put(41006, "缺少media_id参数"); - errMap.put(41007, "缺少子菜单数据"); - errMap.put(41008, "缺少oauth code"); - errMap.put(41009, "缺少openid"); - errMap.put(42001, "access_token超时"); - errMap.put(42002, "refresh_token超时"); - errMap.put(42003, "oauth_code超时"); - errMap.put(43001, "需要GET请求"); - errMap.put(43002, "需要POST请求"); - errMap.put(43003, "需要HTTPS请求"); - errMap.put(43004, "需要接收者关注"); - errMap.put(43005, "需要好友关系"); - errMap.put(44001, "多媒体文件为空"); - errMap.put(44002, "POST的数据包为空"); - errMap.put(44003, "图文消息内容为空"); - errMap.put(44004, "文本消息内容为空"); - errMap.put(45001, "多媒体文件大小超过限制"); - errMap.put(45002, "消息内容超过限制"); - errMap.put(45003, "标题字段超过限制"); - errMap.put(45004, "描述字段超过限制"); - errMap.put(45005, "链接字段超过限制"); - errMap.put(45006, "图片链接字段超过限制"); - errMap.put(45007, "语音播放时间超过限制"); - errMap.put(45008, "图文消息超过限制"); - errMap.put(45009, "接口调用超过限制"); - errMap.put(45010, "创建菜单个数超过限制"); - errMap.put(45015, "回复时间超过限制"); - errMap.put(45016, "系统分组,不允许修改"); - errMap.put(45017, "分组名字过长"); - errMap.put(45018, "分组数量超过上限"); - errMap.put(46001, "不存在媒体数据"); - errMap.put(46002, "不存在的菜单版本"); - errMap.put(46003, "不存在的菜单数据"); - errMap.put(46004, "不存在的用户"); - errMap.put(47001, "解析JSON/XML内容错误"); - errMap.put(48001, "api功能未授权"); - errMap.put(50001, "用户未授权该api"); - } - - protected int errorCode; - - protected String errorMsg; - - public int getErrorCode() { - return errorCode; - } - - public void setErrorCode(int errorCode) { - this.errorCode = errorCode; - } - - public String getErrorMsg() { - return errorMsg; - } - - public void setErrorMsg(String errorMsg) { - this.errorMsg = errorMsg; - } - - public String getDescription() { - return errMap.get(errorCode); - } - - public static WxError fromJson(String json) { - return WxGsonBuilder.create().fromJson(json, WxError.class); - } - - @Override - public String toString() { - return "微信错误 errcode=" + errorCode + ", errmsg=" + errorMsg + ", description=" + getDescription(); - } - -} diff --git a/src/main/java/me/chanjar/weixin/util/json/WxGsonBuilder.java b/src/main/java/me/chanjar/weixin/util/json/WxGsonBuilder.java deleted file mode 100644 index 09d8247c..00000000 --- a/src/main/java/me/chanjar/weixin/util/json/WxGsonBuilder.java +++ /dev/null @@ -1,36 +0,0 @@ -package me.chanjar.weixin.util.json; - -import me.chanjar.weixin.bean.*; -import me.chanjar.weixin.bean.result.*; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -public class WxGsonBuilder { - - public static final GsonBuilder INSTANCE = new GsonBuilder(); - - static { - INSTANCE.disableHtmlEscaping(); - INSTANCE.registerTypeAdapter(WxCustomMessage.class, new WxCustomMessageGsonAdapter()); - INSTANCE.registerTypeAdapter(WxMenu.class, new WxMenuGsonAdapter()); - INSTANCE.registerTypeAdapter(WxMassNews.class, new WxMassNewsGsonAdapter()); - INSTANCE.registerTypeAdapter(WxMassGroupMessage.class, new WxMassMessageGsonAdapter()); - INSTANCE.registerTypeAdapter(WxMassOpenIdsMessage.class, new WxMassOpenIdsMessageGsonAdapter()); - INSTANCE.registerTypeAdapter(WxGroup.class, new WxGroupGsonAdapter()); - INSTANCE.registerTypeAdapter(WxUser.class, new WxUserGsonAdapter()); - INSTANCE.registerTypeAdapter(WxUserList.class, new WxUserListGsonAdapter()); - INSTANCE.registerTypeAdapter(WxAccessToken.class, new WxAccessTokenAdapter()); - INSTANCE.registerTypeAdapter(WxError.class, new WxErrorAdapter()); - INSTANCE.registerTypeAdapter(WxMassVideo.class, new WxMassVideoAdapter()); - INSTANCE.registerTypeAdapter(WxMediaUploadResult.class, new WxMediaUploadResultAdapter()); - INSTANCE.registerTypeAdapter(WxMassSendResult.class, new WxMassSendResultAdapter()); - INSTANCE.registerTypeAdapter(WxMassUploadResult.class, new WxMassUploadResultAdapter()); - INSTANCE.registerTypeAdapter(WxQrCodeTicket.class, new WxQrCodeTicketAdapter()); - } - - public static Gson create() { - return INSTANCE.create(); - } - -} diff --git a/src/main/java/me/chanjar/weixin/util/json/WxUserGsonAdapter.java b/src/main/java/me/chanjar/weixin/util/json/WxUserGsonAdapter.java deleted file mode 100644 index ea50adfc..00000000 --- a/src/main/java/me/chanjar/weixin/util/json/WxUserGsonAdapter.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.util.json; - -import java.lang.reflect.Type; - -import me.chanjar.weixin.bean.result.WxUser; - -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; - -/** - * - * @author qianjia - * - */ -public class WxUserGsonAdapter implements JsonDeserializer { - - public WxUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - JsonObject o = json.getAsJsonObject(); - WxUser wxUser = new WxUser(); - wxUser.setSubscribe(new Integer(0).equals(GsonHelper.getInteger(o, "subscribe")) ? false : true); - wxUser.setCity(GsonHelper.getString(o, "city")); - wxUser.setCountry(GsonHelper.getString(o, "country")); - wxUser.setHeadImgUrl(GsonHelper.getString(o, "headimgurl")); - wxUser.setLanguage(GsonHelper.getString(o, "language")); - wxUser.setNickname(GsonHelper.getString(o, "nickname")); - wxUser.setOpenId(GsonHelper.getString(o, "openid")); - wxUser.setProvince(GsonHelper.getString(o, "province")); - wxUser.setSubscribeTime(GsonHelper.getLong(o, "subscribe_time")); - wxUser.setUnionId(GsonHelper.getString(o, "unionid")); - Integer sex = GsonHelper.getInteger(o, "sex"); - if(new Integer(1).equals(sex)) { - wxUser.setSex("男"); - } else if (new Integer(2).equals(sex)) { - wxUser.setSex("女"); - } else { - wxUser.setSex("未知"); - } - return wxUser; - } - -} diff --git a/src/main/java/me/chanjar/weixin/util/json/WxUserListGsonAdapter.java b/src/main/java/me/chanjar/weixin/util/json/WxUserListGsonAdapter.java deleted file mode 100644 index fe5ae730..00000000 --- a/src/main/java/me/chanjar/weixin/util/json/WxUserListGsonAdapter.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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.util.json; - -import java.lang.reflect.Type; - -import me.chanjar.weixin.bean.result.WxUserList; - -import com.google.gson.JsonArray; -import com.google.gson.JsonDeserializationContext; -import com.google.gson.JsonDeserializer; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParseException; - -/** - * - * @author qianjia - * - */ -public class WxUserListGsonAdapter implements JsonDeserializer { - - public WxUserList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { - JsonObject o = json.getAsJsonObject(); - WxUserList wxUserList = new WxUserList(); - wxUserList.setTotal(GsonHelper.getInteger(o, "total")); - wxUserList.setCount(GsonHelper.getInteger(o, "count")); - wxUserList.setNextOpenId(GsonHelper.getString(o, "next_openid")); - JsonArray data = o.get("data").getAsJsonObject().get("openid").getAsJsonArray(); - for (int i = 0; i < data.size(); i++) { - wxUserList.getOpenIds().add(GsonHelper.getAsString(data.get(i))); - } - return wxUserList; - } - -} diff --git a/src/test/java/me/chanjar/weixin/api/WxUserAPITest.java b/src/test/java/me/chanjar/weixin/api/WxUserAPITest.java deleted file mode 100644 index f737dba4..00000000 --- a/src/test/java/me/chanjar/weixin/api/WxUserAPITest.java +++ /dev/null @@ -1,56 +0,0 @@ -package me.chanjar.weixin.api; - -import org.testng.Assert; -import org.testng.annotations.Guice; -import org.testng.annotations.Test; - -import me.chanjar.weixin.api.ApiTestModule.WxXmlConfigStorage; -import me.chanjar.weixin.bean.result.WxUser; -import me.chanjar.weixin.bean.result.WxUserList; -import me.chanjar.weixin.exception.WxErrorException; - -import com.google.inject.Inject; - -/** - * 测试用户相关的接口 - * @author chanjarster - * - */ -@Test(groups = "userAPI", dependsOnGroups = { "baseAPI", "groupAPI" }) -@Guice(modules = ApiTestModule.class) -public class WxUserAPITest { - - @Inject - protected WxServiceImpl wxService; - - public void testUserUpdateRemark() throws WxErrorException { - WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigStorage; - wxService.userUpdateRemark(configProvider.getOpenId(), "测试备注名"); - } - - public void testUserInfo() throws WxErrorException { - WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigStorage; - WxUser user = wxService.userInfo(configProvider.getOpenId(), null); - Assert.assertNotNull(user); - } - - public void testUserList() throws WxErrorException { - WxUserList wxUserList = wxService.userList(null); - Assert.assertNotNull(wxUserList); - Assert.assertFalse(wxUserList.getCount() == -1); - Assert.assertFalse(wxUserList.getTotal() == -1); - Assert.assertFalse(wxUserList.getOpenIds().size() == -1); - } - - public void testGroupQueryUserGroup() throws WxErrorException { - WxXmlConfigStorage configStorage = (WxXmlConfigStorage) wxService.wxConfigStorage; - long groupid = wxService.userGetGroup(configStorage.getOpenId()); - Assert.assertTrue(groupid != -1l); - } - - public void getGroupMoveUser() throws WxErrorException { - WxXmlConfigStorage configStorage = (WxXmlConfigStorage) wxService.wxConfigStorage; - wxService.userUpdateGroup(configStorage.getOpenId(), wxService.groupGet().get(3).getId()); - } - -} diff --git a/src/test/resources/testng.xml b/src/test/resources/testng.xml deleted file mode 100644 index 3a0c80aa..00000000 --- a/src/test/resources/testng.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml new file mode 100644 index 00000000..26cc3ddd --- /dev/null +++ b/weixin-java-common/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + me.chanjar + weixin-java-parent + 1.0.3-SNAPSHOT + + + weixin-java-common + WeiXin Java Tools - Common + 微信公众号、企业号Java SDK Common + + + + junit + junit + test + + + org.testng + testng + 6.8.7 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + com.google.inject + guice + 3.0 + test + + + org.eclipse.jetty + jetty-server + 9.3.0.M0 + test + + + org.eclipse.jetty + jetty-servlet + 9.3.0.M0 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/resources/testng.xml + + + + + + + diff --git a/src/main/java/me/chanjar/weixin/api/WxConsts.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java similarity index 96% rename from src/main/java/me/chanjar/weixin/api/WxConsts.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java index f2152428..084c8ee0 100644 --- a/src/main/java/me/chanjar/weixin/api/WxConsts.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.api; +package me.chanjar.weixin.common.api; import java.util.HashMap; import java.util.Map; @@ -20,7 +20,7 @@ public class WxConsts { public static final String XML_TRANSFER_CUSTOMER_SERVICE = "transfer_customer_service"; /////////////////////// - // 客服消息的消息类型 + // 主动发送消息的消息类型 /////////////////////// public static final String CUSTOM_MSG_TEXT = "text"; public static final String CUSTOM_MSG_IMAGE = "image"; @@ -28,6 +28,7 @@ public class WxConsts { public static final String CUSTOM_MSG_VIDEO = "video"; public static final String CUSTOM_MSG_MUSIC = "music"; public static final String CUSTOM_MSG_NEWS = "news"; + public static final String CUSTOM_MSG_FILE = "file"; /////////////////////// // 群发消息的消息类型 @@ -95,6 +96,7 @@ public class WxConsts { public static final String MEDIA_VOICE = "voice"; public static final String MEDIA_VIDEO = "video"; public static final String MEDIA_THUMB = "thumb"; + public static final String MEDIA_FILE = "file"; /////////////////////// // 文件类型 diff --git a/src/main/java/me/chanjar/weixin/bean/WxAccessToken.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxAccessToken.java similarity index 79% rename from src/main/java/me/chanjar/weixin/bean/WxAccessToken.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxAccessToken.java index 1cbb7154..2365daa2 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxAccessToken.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxAccessToken.java @@ -1,12 +1,12 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.common.bean; -import me.chanjar.weixin.util.json.WxGsonBuilder; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; public class WxAccessToken { private String accessToken; - private int expiresIn; + private int expiresIn = -1; public String getAccessToken() { return accessToken; diff --git a/src/main/java/me/chanjar/weixin/bean/WxMenu.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxMenu.java similarity index 92% rename from src/main/java/me/chanjar/weixin/bean/WxMenu.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxMenu.java index 5363ab0d..70110230 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxMenu.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxMenu.java @@ -1,15 +1,15 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.common.bean; import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.List; -import me.chanjar.weixin.util.json.WxGsonBuilder; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; /** - * 公众号菜单 - * @author chanjarster + * 企业号菜单 + * @author Daniel Qian * */ public class WxMenu { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxError.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxError.java new file mode 100644 index 00000000..495ede81 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxError.java @@ -0,0 +1,53 @@ +package me.chanjar.weixin.common.bean.result; + +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +/** + * 微信错误码说明 + * http://mp.weixin.qq.com/wiki/index.php?title=全局返回码说明 + * @author Daniel Qian + * + */ +public class WxError { + + private int errorCode; + + private String errorMsg; + + private String json; + + public int getErrorCode() { + return errorCode; + } + + public void setErrorCode(int errorCode) { + this.errorCode = errorCode; + } + + public String getErrorMsg() { + return errorMsg; + } + + public void setErrorMsg(String errorMsg) { + this.errorMsg = errorMsg; + } + + public String getJson() { + return json; + } + + public void setJson(String json) { + this.json = json; + } + + public static WxError fromJson(String json) { + WxError error = WxGsonBuilder.create().fromJson(json, WxError.class); + return error; + } + + @Override + public String toString() { + return "微信错误 errcode=" + errorCode + ", errmsg=" + errorMsg + "\njson:" + json; + } + +} diff --git a/src/main/java/me/chanjar/weixin/bean/result/WxMediaUploadResult.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMediaUploadResult.java similarity index 90% rename from src/main/java/me/chanjar/weixin/bean/result/WxMediaUploadResult.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMediaUploadResult.java index e3dba600..1695489a 100644 --- a/src/main/java/me/chanjar/weixin/bean/result/WxMediaUploadResult.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMediaUploadResult.java @@ -1,6 +1,6 @@ -package me.chanjar.weixin.bean.result; +package me.chanjar.weixin.common.bean.result; -import me.chanjar.weixin.util.json.WxGsonBuilder; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; public class WxMediaUploadResult { @@ -33,10 +33,6 @@ public class WxMediaUploadResult { this.createdAt = createdAt; } - public static WxMediaUploadResult fromJson(String json) { - return WxGsonBuilder.create().fromJson(json, WxMediaUploadResult.class); - } - public String getThumbMediaId() { return thumbMediaId; } @@ -45,6 +41,10 @@ public class WxMediaUploadResult { this.thumbMediaId = thumbMediaId; } + public static WxMediaUploadResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxMediaUploadResult.class); + } + @Override public String toString() { return "WxUploadResult [type=" + type + ", media_id=" + mediaId + ", thumb_media_id=" + thumbMediaId diff --git a/src/main/java/me/chanjar/weixin/exception/WxErrorException.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/exception/WxErrorException.java similarity index 76% rename from src/main/java/me/chanjar/weixin/exception/WxErrorException.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/exception/WxErrorException.java index 311d00fb..d1dc569b 100644 --- a/src/main/java/me/chanjar/weixin/exception/WxErrorException.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/exception/WxErrorException.java @@ -1,6 +1,6 @@ -package me.chanjar.weixin.exception; +package me.chanjar.weixin.common.exception; -import me.chanjar.weixin.bean.result.WxError; +import me.chanjar.weixin.common.bean.result.WxError; public class WxErrorException extends Exception { diff --git a/src/main/java/me/chanjar/weixin/util/crypto/ByteGroup.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/ByteGroup.java similarity index 65% rename from src/main/java/me/chanjar/weixin/util/crypto/ByteGroup.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/ByteGroup.java index b0bda3cb..de777dc9 100755 --- a/src/main/java/me/chanjar/weixin/util/crypto/ByteGroup.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/ByteGroup.java @@ -1,11 +1,11 @@ -package me.chanjar.weixin.util.crypto; +package me.chanjar.weixin.common.util.crypto; import java.util.ArrayList; -class ByteGroup { +public class ByteGroup { ArrayList byteContainer = new ArrayList(); - byte[] toBytes() { + public byte[] toBytes() { byte[] bytes = new byte[byteContainer.size()]; for (int i = 0; i < byteContainer.size(); i++) { bytes[i] = byteContainer.get(i); @@ -13,14 +13,14 @@ class ByteGroup { return bytes; } - ByteGroup addBytes(byte[] bytes) { + public ByteGroup addBytes(byte[] bytes) { for (byte b : bytes) { byteContainer.add(b); } return this; } - int size() { + public int size() { return byteContainer.size(); } } diff --git a/src/main/java/me/chanjar/weixin/util/crypto/PKCS7Encoder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/PKCS7Encoder.java similarity index 84% rename from src/main/java/me/chanjar/weixin/util/crypto/PKCS7Encoder.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/PKCS7Encoder.java index e02b509c..35771c47 100755 --- a/src/main/java/me/chanjar/weixin/util/crypto/PKCS7Encoder.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/PKCS7Encoder.java @@ -6,7 +6,7 @@ // ------------------------------------------------------------------------ -package me.chanjar.weixin.util.crypto; +package me.chanjar.weixin.common.util.crypto; import java.nio.charset.Charset; import java.util.Arrays; @@ -14,7 +14,7 @@ import java.util.Arrays; /** * 提供基于PKCS7算法的加解 */ -class PKCS7Encoder { +public class PKCS7Encoder { private static final Charset CHARSET = Charset.forName("utf-8"); private static final int BLOCK_SIZE = 32; @@ -25,7 +25,7 @@ class PKCS7Encoder { * @param count 需要进行填充补位操作的明文字节个数 * @return 补齐用的字节数组 */ - static byte[] encode(int count) { + public static byte[] encode(int count) { // 计算需要填充的位数 int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); if (amountToPad == 0) { @@ -46,7 +46,7 @@ class PKCS7Encoder { * @param decrypted 解密后的明文 * @return 删除补位字符后的明文 */ - static byte[] decode(byte[] decrypted) { + public static byte[] decode(byte[] decrypted) { int pad = (int) decrypted[decrypted.length - 1]; if (pad < 1 || pad > 32) { pad = 0; @@ -60,7 +60,7 @@ class PKCS7Encoder { * @param a 需要转化的数字 * @return 转化得到的字符 */ - static char chr(int a) { + public static char chr(int a) { byte target = (byte) (a & 0xFF); return (char) target; } diff --git a/src/main/java/me/chanjar/weixin/util/crypto/SHA1.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java similarity index 91% rename from src/main/java/me/chanjar/weixin/util/crypto/SHA1.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java index eec3c53b..cc233308 100644 --- a/src/main/java/me/chanjar/weixin/util/crypto/SHA1.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java @@ -1,11 +1,11 @@ -package me.chanjar.weixin.util.crypto; +package me.chanjar.weixin.common.util.crypto; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; /** - * Created by qianjia on 14/10/19. + * Created by Daniel Qian on 14/10/19. */ public class SHA1 { diff --git a/src/main/java/me/chanjar/weixin/util/crypto/WxCryptUtil.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java similarity index 86% rename from src/main/java/me/chanjar/weixin/util/crypto/WxCryptUtil.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java index 764d851b..363f0be0 100755 --- a/src/main/java/me/chanjar/weixin/util/crypto/WxCryptUtil.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java @@ -11,13 +11,12 @@ * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi */ -package me.chanjar.weixin.util.crypto; +package me.chanjar.weixin.common.util.crypto; -import java.io.StringReader; -import java.nio.charset.Charset; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; -import java.util.Random; +import org.apache.commons.codec.binary.Base64; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.InputSource; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; @@ -25,12 +24,11 @@ import javax.crypto.spec.SecretKeySpec; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; - -import me.chanjar.weixin.api.WxConfigStorage; -import org.apache.commons.codec.binary.Base64; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.InputSource; +import java.io.StringReader; +import java.nio.charset.Charset; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Random; public class WxCryptUtil { @@ -48,41 +46,24 @@ public class WxCryptUtil { } }; - private byte[] aesKey; - private String token; - private String appId; - - /** - * 构造函数 - * - * @param wxConfigStorage - */ - public WxCryptUtil(WxConfigStorage wxConfigStorage) { - /* - * @param token 公众平台上,开发者设置的token - * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey - * @param appId 公众平台appid - */ - String encodingAesKey = wxConfigStorage.getAesKey(); - String token = wxConfigStorage.getToken(); - String appId = wxConfigStorage.getAppId(); - - this.token = token; - this.appId = appId; - this.aesKey = Base64.decodeBase64(encodingAesKey + "="); + protected byte[] aesKey; + protected String token; + protected String appidOrCorpid; + public WxCryptUtil() { + super(); } /** * 构造函数 * - * @param token 公众平台上,开发者设置的token - * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey - * @param appId 公众平台appid + * @param token 公众平台上,开发者设置的token + * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey + * @param appidOrCorpid 公众平台appid/corpid */ - public WxCryptUtil(String token, String encodingAesKey, String appId) { + public WxCryptUtil(String token, String encodingAesKey, String appidOrCorpid) { this.token = token; - this.appId = appId; + this.appidOrCorpid = appidOrCorpid; this.aesKey = Base64.decodeBase64(encodingAesKey + "="); } @@ -125,7 +106,7 @@ public class WxCryptUtil { byte[] randomStringBytes = randomStr.getBytes(CHARSET); byte[] plainTextBytes = plainText.getBytes(CHARSET); byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder(plainTextBytes.length); - byte[] appIdBytes = appId.getBytes(CHARSET); + byte[] appIdBytes = appidOrCorpid.getBytes(CHARSET); // randomStr + networkBytesOrder + text + appid byteCollector.addBytes(randomStringBytes); @@ -174,7 +155,7 @@ public class WxCryptUtil { * @return 解密后的原文 */ public String decrypt(String msgSignature, String timeStamp, String nonce, String encryptedXml) { - // 密钥,公众账号的app secret + // 密钥,公众账号的app corpSecret // 提取密文 String cipherText = extractEncryptPart(encryptedXml); @@ -199,7 +180,7 @@ public class WxCryptUtil { * @param cipherText 需要解密的密文 * @return 解密得到的明文 */ - protected String decrypt(String cipherText) { + public String decrypt(String cipherText) { byte[] original; try { // 设置解密模式为AES的CBC模式 @@ -235,7 +216,7 @@ public class WxCryptUtil { } // appid不相同的情况 - if (!from_appid.equals(appId)) { + if (!from_appid.equals(appidOrCorpid)) { throw new RuntimeException("AppID不正确"); } diff --git a/src/main/java/me/chanjar/weixin/util/fs/FileUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java similarity index 96% rename from src/main/java/me/chanjar/weixin/util/fs/FileUtils.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java index 1acc4561..d2ecc42a 100644 --- a/src/main/java/me/chanjar/weixin/util/fs/FileUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.util.fs; +package me.chanjar.weixin.common.util.fs; import java.io.File; import java.io.FileOutputStream; diff --git a/src/main/java/me/chanjar/weixin/util/http/InputStreamResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamResponseHandler.java similarity index 95% rename from src/main/java/me/chanjar/weixin/util/http/InputStreamResponseHandler.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamResponseHandler.java index 8854e0f8..cf7f26cf 100644 --- a/src/main/java/me/chanjar/weixin/util/http/InputStreamResponseHandler.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamResponseHandler.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.util.http; +package me.chanjar.weixin.common.util.http; import java.io.IOException; import java.io.InputStream; diff --git a/src/main/java/me/chanjar/weixin/util/http/MediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaDownloadRequestExecutor.java similarity index 83% rename from src/main/java/me/chanjar/weixin/util/http/MediaDownloadRequestExecutor.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaDownloadRequestExecutor.java index b62ad35f..caa8e03b 100644 --- a/src/main/java/me/chanjar/weixin/util/http/MediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaDownloadRequestExecutor.java @@ -1,11 +1,11 @@ -package me.chanjar.weixin.util.http; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +package me.chanjar.weixin.common.util.http; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.util.fs.FileUtils; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.http.InputStreamResponseHandler; +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.client.ClientProtocolException; @@ -13,13 +13,15 @@ import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.entity.ContentType; -import me.chanjar.weixin.bean.result.WxError; -import me.chanjar.weixin.exception.WxErrorException; -import me.chanjar.weixin.util.fs.FileUtils; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * 下载媒体文件请求执行器,请求的参数是String, 返回的结果是File - * @author chanjarster + * @author Daniel Qian * */ public class MediaDownloadRequestExecutor implements RequestExecutor { diff --git a/src/main/java/me/chanjar/weixin/util/http/MediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java similarity index 84% rename from src/main/java/me/chanjar/weixin/util/http/MediaUploadRequestExecutor.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java index 15c8c8a0..62223722 100644 --- a/src/main/java/me/chanjar/weixin/util/http/MediaUploadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java @@ -1,8 +1,9 @@ -package me.chanjar.weixin.util.http; +package me.chanjar.weixin.common.util.http; import java.io.File; import java.io.IOException; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import org.apache.http.HttpEntity; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; @@ -10,13 +11,12 @@ import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.mime.MultipartEntityBuilder; -import me.chanjar.weixin.bean.result.WxError; -import me.chanjar.weixin.bean.result.WxMediaUploadResult; -import me.chanjar.weixin.exception.WxErrorException; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.WxErrorException; /** * 上传媒体文件请求执行器,请求的参数是File, 返回的结果是String - * @author chanjarster + * @author Daniel Qian * */ public class MediaUploadRequestExecutor implements RequestExecutor { diff --git a/src/main/java/me/chanjar/weixin/util/http/RequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestExecutor.java similarity index 80% rename from src/main/java/me/chanjar/weixin/util/http/RequestExecutor.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestExecutor.java index 58901672..e7959dca 100644 --- a/src/main/java/me/chanjar/weixin/util/http/RequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestExecutor.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.util.http; +package me.chanjar.weixin.common.util.http; import java.io.IOException; @@ -6,11 +6,11 @@ import org.apache.http.client.ClientProtocolException; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; -import me.chanjar.weixin.exception.WxErrorException; +import me.chanjar.weixin.common.exception.WxErrorException; /** * http请求执行器 - * @author chanjarster + * @author Daniel Qian * * @param 返回值类型 * @param 请求参数类型 diff --git a/src/main/java/me/chanjar/weixin/util/http/SimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java similarity index 85% rename from src/main/java/me/chanjar/weixin/util/http/SimpleGetRequestExecutor.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java index 07924107..4d5ea024 100644 --- a/src/main/java/me/chanjar/weixin/util/http/SimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.util.http; +package me.chanjar.weixin.common.util.http; import java.io.IOException; @@ -6,12 +6,12 @@ import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; -import me.chanjar.weixin.bean.result.WxError; -import me.chanjar.weixin.exception.WxErrorException; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.WxErrorException; /** * 简单的GET请求执行器,请求的参数是String, 返回的结果也是String - * @author chanjarster + * @author Daniel Qian * */ public class SimpleGetRequestExecutor implements RequestExecutor { diff --git a/src/main/java/me/chanjar/weixin/util/http/SimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java similarity index 85% rename from src/main/java/me/chanjar/weixin/util/http/SimplePostRequestExecutor.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java index ffb05327..8025d6dc 100644 --- a/src/main/java/me/chanjar/weixin/util/http/SimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java @@ -1,9 +1,9 @@ -package me.chanjar.weixin.util.http; +package me.chanjar.weixin.common.util.http; import java.io.IOException; -import me.chanjar.weixin.bean.result.WxError; -import me.chanjar.weixin.exception.WxErrorException; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.exception.WxErrorException; import org.apache.http.Consts; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.CloseableHttpResponse; @@ -12,7 +12,7 @@ import org.apache.http.entity.StringEntity; /** * 简单的POST请求执行器,请求的参数是String, 返回的结果也是String - * @author chanjarster + * @author Daniel Qian * */ public class SimplePostRequestExecutor implements RequestExecutor { diff --git a/src/main/java/me/chanjar/weixin/util/http/Utf8ResponseHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/Utf8ResponseHandler.java similarity index 93% rename from src/main/java/me/chanjar/weixin/util/http/Utf8ResponseHandler.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/Utf8ResponseHandler.java index 3c19c132..bad067c0 100644 --- a/src/main/java/me/chanjar/weixin/util/http/Utf8ResponseHandler.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/Utf8ResponseHandler.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.util.http; +package me.chanjar.weixin.common.util.http; import java.io.IOException; @@ -12,7 +12,7 @@ import org.apache.http.util.EntityUtils; /** * copy from {@link org.apache.http.impl.client.BasicResponseHandler} - * @author chanjarster + * @author Daniel Qian * */ public class Utf8ResponseHandler implements ResponseHandler { diff --git a/src/main/java/me/chanjar/weixin/util/json/GsonHelper.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java similarity index 98% rename from src/main/java/me/chanjar/weixin/util/json/GsonHelper.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java index 4dc16de0..614e28e2 100644 --- a/src/main/java/me/chanjar/weixin/util/json/GsonHelper.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java @@ -6,7 +6,7 @@ * 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.util.json; +package me.chanjar.weixin.common.util.json; import com.google.gson.JsonElement; diff --git a/src/main/java/me/chanjar/weixin/util/json/WxAccessTokenAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxAccessTokenAdapter.java similarity index 93% rename from src/main/java/me/chanjar/weixin/util/json/WxAccessTokenAdapter.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxAccessTokenAdapter.java index 60e3475a..cfe4edb2 100644 --- a/src/main/java/me/chanjar/weixin/util/json/WxAccessTokenAdapter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxAccessTokenAdapter.java @@ -6,10 +6,10 @@ * 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.util.json; +package me.chanjar.weixin.common.util.json; import com.google.gson.*; -import me.chanjar.weixin.bean.WxAccessToken; +import me.chanjar.weixin.common.bean.WxAccessToken; import java.lang.reflect.Type; diff --git a/src/main/java/me/chanjar/weixin/util/json/WxErrorAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxErrorAdapter.java similarity index 90% rename from src/main/java/me/chanjar/weixin/util/json/WxErrorAdapter.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxErrorAdapter.java index 5779bbae..74b9ded8 100644 --- a/src/main/java/me/chanjar/weixin/util/json/WxErrorAdapter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxErrorAdapter.java @@ -6,11 +6,10 @@ * 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.util.json; +package me.chanjar.weixin.common.util.json; import com.google.gson.*; -import me.chanjar.weixin.bean.WxAccessToken; -import me.chanjar.weixin.bean.result.WxError; +import me.chanjar.weixin.common.bean.result.WxError; import java.lang.reflect.Type; @@ -31,6 +30,7 @@ public class WxErrorAdapter implements JsonDeserializer { if (wxErrorJsonObject.get("errmsg") != null && !wxErrorJsonObject.get("errmsg").isJsonNull()) { wxError.setErrorMsg(GsonHelper.getAsString(wxErrorJsonObject.get("errmsg"))); } + wxError.setJson(json.toString()); return wxError; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java new file mode 100644 index 00000000..7aa56965 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.common.util.json; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import me.chanjar.weixin.common.bean.WxMenu; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; + +public class WxGsonBuilder { + + public static final GsonBuilder INSTANCE = new GsonBuilder(); + + static { + INSTANCE.disableHtmlEscaping(); + INSTANCE.registerTypeAdapter(WxAccessToken.class, new WxAccessTokenAdapter()); + INSTANCE.registerTypeAdapter(WxError.class, new WxErrorAdapter()); + INSTANCE.registerTypeAdapter(WxMenu.class, new WxMenuGsonAdapter()); + INSTANCE.registerTypeAdapter(WxMediaUploadResult.class, new WxMediaUploadResultAdapter()); + } + + public static Gson create() { + return INSTANCE.create(); + } + +} diff --git a/src/main/java/me/chanjar/weixin/util/json/WxMediaUploadResultAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMediaUploadResultAdapter.java similarity index 94% rename from src/main/java/me/chanjar/weixin/util/json/WxMediaUploadResultAdapter.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMediaUploadResultAdapter.java index 0fbfa7ac..35eb18b4 100644 --- a/src/main/java/me/chanjar/weixin/util/json/WxMediaUploadResultAdapter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMediaUploadResultAdapter.java @@ -6,10 +6,10 @@ * 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.util.json; +package me.chanjar.weixin.common.util.json; import com.google.gson.*; -import me.chanjar.weixin.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import java.lang.reflect.Type; diff --git a/src/main/java/me/chanjar/weixin/util/json/WxMenuGsonAdapter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java similarity index 96% rename from src/main/java/me/chanjar/weixin/util/json/WxMenuGsonAdapter.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java index 6fae9d2c..6fc788d3 100644 --- a/src/main/java/me/chanjar/weixin/util/json/WxMenuGsonAdapter.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java @@ -6,11 +6,11 @@ * 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.util.json; +package me.chanjar.weixin.common.util.json; import java.lang.reflect.Type; -import me.chanjar.weixin.bean.WxMenu; +import me.chanjar.weixin.common.bean.WxMenu; import com.google.gson.JsonArray; import com.google.gson.JsonDeserializationContext; @@ -23,7 +23,7 @@ import com.google.gson.JsonSerializer; /** * - * @author qianjia + * @author Daniel Qian * */ public class WxMenuGsonAdapter implements JsonSerializer, JsonDeserializer { diff --git a/src/main/java/me/chanjar/weixin/util/xml/AdapterCDATA.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/AdapterCDATA.java similarity index 87% rename from src/main/java/me/chanjar/weixin/util/xml/AdapterCDATA.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/AdapterCDATA.java index edcceed8..c38c2795 100644 --- a/src/main/java/me/chanjar/weixin/util/xml/AdapterCDATA.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/AdapterCDATA.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.util.xml; +package me.chanjar.weixin.common.util.xml; import javax.xml.bind.annotation.adapters.XmlAdapter; @@ -6,7 +6,7 @@ import javax.xml.bind.annotation.adapters.XmlAdapter; * * http://stackoverflow.com/questions/14193944/jaxb-marshalling-unmarshalling-with-cdata * - * @author chanjarster + * @author Daniel Qian * */ public class AdapterCDATA extends XmlAdapter { diff --git a/src/main/java/me/chanjar/weixin/util/xml/MediaIdMarshaller.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/MediaIdMarshaller.java similarity index 85% rename from src/main/java/me/chanjar/weixin/util/xml/MediaIdMarshaller.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/MediaIdMarshaller.java index 39225136..10e6a3aa 100644 --- a/src/main/java/me/chanjar/weixin/util/xml/MediaIdMarshaller.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/MediaIdMarshaller.java @@ -1,9 +1,9 @@ -package me.chanjar.weixin.util.xml; +package me.chanjar.weixin.common.util.xml; import javax.xml.bind.annotation.adapters.XmlAdapter; /** - * @author chanjarster + * @author Daniel Qian */ public class MediaIdMarshaller extends XmlAdapter { diff --git a/src/test/java/me/chanjar/weixin/bean/WxAccessTokenTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxAccessTokenTest.java similarity index 91% rename from src/test/java/me/chanjar/weixin/bean/WxAccessTokenTest.java rename to weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxAccessTokenTest.java index 7391f49b..d236c631 100644 --- a/src/test/java/me/chanjar/weixin/bean/WxAccessTokenTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxAccessTokenTest.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.common.bean; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/src/test/java/me/chanjar/weixin/bean/WxErrorTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxErrorTest.java similarity index 91% rename from src/test/java/me/chanjar/weixin/bean/WxErrorTest.java rename to weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxErrorTest.java index a7501d16..51e9dab3 100644 --- a/src/test/java/me/chanjar/weixin/bean/WxErrorTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxErrorTest.java @@ -1,10 +1,9 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.common.bean; +import me.chanjar.weixin.common.bean.result.WxError; import org.testng.Assert; import org.testng.annotations.Test; -import me.chanjar.weixin.bean.result.WxError; - @Test public class WxErrorTest { diff --git a/src/test/java/me/chanjar/weixin/bean/WxMenuTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxMenuTest.java similarity index 96% rename from src/test/java/me/chanjar/weixin/bean/WxMenuTest.java rename to weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxMenuTest.java index de5e8691..015889d7 100644 --- a/src/test/java/me/chanjar/weixin/bean/WxMenuTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxMenuTest.java @@ -1,11 +1,10 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.common.bean; +import me.chanjar.weixin.common.bean.WxMenu.WxMenuButton; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import me.chanjar.weixin.bean.WxMenu.WxMenuButton; - @Test public class WxMenuTest { diff --git a/src/test/java/me/chanjar/weixin/util/crypto/WxCryptUtilTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java similarity index 94% rename from src/test/java/me/chanjar/weixin/util/crypto/WxCryptUtilTest.java rename to weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java index 0c61d3d0..15b08c26 100755 --- a/src/test/java/me/chanjar/weixin/util/crypto/WxCryptUtilTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.util.crypto; +package me.chanjar.weixin.common.util.crypto; import org.testng.annotations.Test; import org.w3c.dom.Document; @@ -13,7 +13,8 @@ import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import java.io.StringReader; -import static org.testng.Assert.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.fail; @Test public class WxCryptUtilTest { @@ -50,7 +51,7 @@ public class WxCryptUtilTest { String messageText = String.format(xmlFormat, cipherText); - // 第三方收到公众号平台发送的消息 + // 第三方收到企业号平台发送的消息 String plainMessage = pc.decrypt(cipherText); System.out.println(plainMessage); diff --git a/weixin-java-common/src/test/resources/testng.xml b/weixin-java-common/src/test/resources/testng.xml new file mode 100644 index 00000000..f0058b53 --- /dev/null +++ b/weixin-java-common/src/test/resources/testng.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/LICENSE b/weixin-java-cp/LICENSE similarity index 100% rename from LICENSE rename to weixin-java-cp/LICENSE diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml new file mode 100644 index 00000000..9c40e02d --- /dev/null +++ b/weixin-java-cp/pom.xml @@ -0,0 +1,73 @@ + + + 4.0.0 + + me.chanjar + weixin-java-parent + 1.0.3-SNAPSHOT + + + weixin-java-cp + WeiXin Java Tools - CP + 微信企业号Java SDK + + + + me.chanjar + weixin-java-common + ${project.version} + + + junit + junit + test + + + org.testng + testng + 6.8.7 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + com.google.inject + guice + 3.0 + test + + + org.eclipse.jetty + jetty-server + 9.3.0.M0 + test + + + org.eclipse.jetty + jetty-servlet + 9.3.0.M0 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/resources/testng.xml + + + + + + + diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java new file mode 100644 index 00000000..d61b0035 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java @@ -0,0 +1,30 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.bean.WxAccessToken; + +/** + * 微信客户端配置存储 + * @author Daniel Qian + * + */ +public interface WxCpConfigStorage { + + public void updateAccessToken(WxAccessToken accessToken); + + public void updateAccessToken(String accessToken, int expiresIn); + + public String getAccessToken(); + + public String getCorpId(); + + public String getCorpSecret(); + + public String getAgentId(); + + public String getToken(); + + public String getAesKey(); + + public int getExpiresIn(); + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java new file mode 100644 index 00000000..26d15f4b --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java @@ -0,0 +1,98 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.bean.WxAccessToken; + +/** + * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 + * @author Daniel Qian + * + */ +public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { + + protected String corpId; + protected String corpSecret; + + protected String token; + protected String accessToken; + protected String aesKey; + protected String agentId; + protected int expiresIn; + + public void updateAccessToken(WxAccessToken accessToken) { + updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } + + public void updateAccessToken(String accessToken, int expiresIn) { + this.accessToken = accessToken; + this.expiresIn = expiresIn; + } + + public String getAccessToken() { + return this.accessToken; + } + + public String getCorpId() { + return this.corpId; + } + + public String getCorpSecret() { + return this.corpSecret; + } + + public String getToken() { + return this.token; + } + + public int getExpiresIn() { + return this.expiresIn; + } + + public void setCorpId(String corpId) { + this.corpId = corpId; + } + + public void setCorpSecret(String corpSecret) { + this.corpSecret = corpSecret; + } + + public void setToken(String token) { + this.token = token; + } + + public String getAesKey() { + return aesKey; + } + + public void setAesKey(String aesKey) { + this.aesKey = aesKey; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public void setExpiresIn(int expiresIn) { + this.expiresIn = expiresIn; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + @Override + public String toString() { + return "WxInMemoryCpConfigStorage{" + + "appidOrCorpid='" + corpId + '\'' + + ", corpSecret='" + corpSecret + '\'' + + ", token='" + token + '\'' + + ", accessToken='" + accessToken + '\'' + + ", aesKey='" + aesKey + '\'' + + ", agentId='" + agentId + '\'' + + ", expiresIn=" + expiresIn + + '}'; + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageHandler.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageHandler.java new file mode 100644 index 00000000..b82dddeb --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageHandler.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.cp.api; + +import java.util.Map; + +import me.chanjar.weixin.cp.bean.WxCpXmlMessage; +import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; + +/** + * 处理微信推送消息的处理器接口 + * @author Daniel Qian + * + */ +public interface WxCpMessageHandler { + + /** + * + * @param wxMessage + * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 + * @return xml格式的消息,如果在异步规则里处理的话,可以返回null + */ + public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context); + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageInterceptor.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageInterceptor.java new file mode 100644 index 00000000..7a09dacd --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageInterceptor.java @@ -0,0 +1,22 @@ +package me.chanjar.weixin.cp.api; + +import java.util.Map; + +import me.chanjar.weixin.cp.bean.WxCpXmlMessage; + +/** + * 微信消息拦截器,可以用来做验证 + * @author Daniel Qian + * + */ +public interface WxCpMessageInterceptor { + + /** + * 拦截微信消息 + * @param wxMessage + * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 + * @return true代表OK,false代表不OK + */ + public boolean intercept(WxCpXmlMessage wxMessage, Map context); + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageRouter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageRouter.java new file mode 100644 index 00000000..333fca9a --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageRouter.java @@ -0,0 +1,307 @@ +package me.chanjar.weixin.cp.api; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Pattern; + +import me.chanjar.weixin.cp.bean.WxCpXmlMessage; +import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; + +/** + *
+ * 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理
+ *
+ * 说明:
+ * 1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理
+ * 2. 默认情况下消息只会被处理一次,除非使用 {@link Rule#next()}
+ * 3. 规则的结束必须用{@link Rule#end()}或者{@link Rule#next()},否则不会生效
+ *
+ * 使用方法:
+ * WxCpMessageRouter router = new WxCpMessageRouter();
+ * router
+ *   .rule()
+ *       .msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT")
+ *       .interceptor(interceptor, ...).handler(handler, ...)
+ *   .end()
+ *   .rule()
+ *       // 另外一个匹配规则
+ *   .end()
+ * ;
+ *
+ * // 将WxXmlMessage交给消息路由器
+ * router.route(message);
+ *
+ * 
+ * @author Daniel Qian + * + */ +public class WxCpMessageRouter { + + private final List rules = new ArrayList(); + + private final ExecutorService es = Executors.newCachedThreadPool(); + + /** + * 开始一个新的Route规则 + * @return + */ + public Rule rule() { + return new Rule(this); + } + + /** + * 处理微信消息 + * @param wxMessage + */ + public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { + final List matchRules = new ArrayList(); + // 收集匹配的规则 + for (final Rule rule : rules) { + if (rule.test(wxMessage)) { + matchRules.add(rule); + } + } + + if (matchRules.size() == 0) { + return null; + } + + if (matchRules.get(0).async) { + // 只要第一个是异步的,那就异步执行 + // 在另一个线程里执行 + es.submit(new Runnable() { + public void run() { + for (final Rule rule : matchRules) { + rule.service(wxMessage); + if (!rule.reEnter) { + break; + } + } + } + }); + return null; + } + + WxCpXmlOutMessage res = null; + for (final Rule rule : matchRules) { + // 返回最后一个匹配规则的结果 + res = rule.service(wxMessage); + if (!rule.reEnter) { + break; + } + } + return res; + } + + public static class Rule { + + private final WxCpMessageRouter routerBuilder; + + private boolean async = true; + + private String msgType; + + private String event; + + private String eventKey; + + private String content; + + private String rContent; + + private boolean reEnter = false; + + private Integer agentId; + + private List handlers = new ArrayList(); + + private List interceptors = new ArrayList(); + + protected Rule(WxCpMessageRouter routerBuilder) { + this.routerBuilder = routerBuilder; + } + + /** + * 设置是否异步执行,默认是true + * @param async + * @return + */ + public Rule async(boolean async) { + this.async = async; + return this; + } + + /** + * 如果agentId匹配 + * @param agentId + * @return + */ + public Rule agentId(Integer agentId) { + this.agentId = agentId; + return this; + } + + /** + * 如果msgType等于某值 + * @param msgType + * @return + */ + public Rule msgType(String msgType) { + this.msgType = msgType; + return this; + } + + /** + * 如果event等于某值 + * @param event + * @return + */ + public Rule event(String event) { + this.event = event; + return this; + } + + /** + * 如果eventKey等于某值 + * @param eventKey + * @return + */ + public Rule eventKey(String eventKey) { + this.eventKey = eventKey; + return this; + } + + /** + * 如果content等于某值 + * @param content + * @return + */ + public Rule content(String content) { + this.content = content; + return this; + } + + /** + * 如果content匹配该正则表达式 + * @param regex + * @return + */ + public Rule rContent(String regex) { + this.rContent = regex; + return this; + } + + /** + * 设置微信消息拦截器 + * @param interceptor + * @return + */ + public Rule interceptor(WxCpMessageInterceptor interceptor) { + return interceptor(interceptor, (WxCpMessageInterceptor[]) null); + } + + /** + * 设置微信消息拦截器 + * @param interceptor + * @param otherInterceptors + * @return + */ + public Rule interceptor(WxCpMessageInterceptor interceptor, WxCpMessageInterceptor... otherInterceptors) { + this.interceptors.add(interceptor); + if (otherInterceptors != null && otherInterceptors.length > 0) { + for (WxCpMessageInterceptor i : otherInterceptors) { + this.interceptors.add(i); + } + } + return this; + } + + /** + * 设置微信消息处理器 + * @param handler + * @return + */ + public Rule handler(WxCpMessageHandler handler) { + return handler(handler, (WxCpMessageHandler[]) null); + } + + /** + * 设置微信消息处理器 + * @param handler + * @param otherHandlers + * @return + */ + public Rule handler(WxCpMessageHandler handler, WxCpMessageHandler... otherHandlers) { + this.handlers.add(handler); + if (otherHandlers != null && otherHandlers.length > 0) { + for (WxCpMessageHandler i : otherHandlers) { + this.handlers.add(i); + } + } + return this; + } + + /** + * 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则 + * @return + */ + public WxCpMessageRouter end() { + this.routerBuilder.rules.add(this); + return this.routerBuilder; + } + + /** + * 规则结束,但是消息还会进入其他规则 + * @return + */ + public WxCpMessageRouter next() { + this.reEnter = true; + return end(); + } + + protected boolean test(WxCpXmlMessage wxMessage) { + return + (this.agentId == null || this.agentId.equals(wxMessage.getAgentId())) + && + (this.msgType == null || this.msgType.equals(wxMessage.getMsgType())) + && + (this.event == null || this.event.equals(wxMessage.getEvent())) + && + (this.eventKey == null || this.eventKey.equals(wxMessage.getEventKey())) + && + (this.content == null || this.content.equals(wxMessage.getContent() == null ? null : wxMessage.getContent().trim())) + && + (this.rContent == null || Pattern.matches(this.rContent, wxMessage.getContent() == null ? "" : wxMessage.getContent().trim())) + ; + } + + /** + * 处理微信推送过来的消息 + * @param wxMessage + * @return true 代表继续执行别的router,false 代表停止执行别的router + */ + protected WxCpXmlOutMessage service(WxCpXmlMessage wxMessage) { + Map context = new HashMap(); + // 如果拦截器不通过 + for (WxCpMessageInterceptor interceptor : this.interceptors) { + if (!interceptor.intercept(wxMessage, context)) { + return null; + } + } + + // 交给handler处理 + WxCpXmlOutMessage res = null; + for (WxCpMessageHandler handler : this.handlers) { + // 返回最后handler的结果 + res = handler.handle(wxMessage, context); + } + return res; + } + + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java new file mode 100644 index 00000000..900a47f7 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java @@ -0,0 +1,290 @@ +package me.chanjar.weixin.cp.api; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import me.chanjar.weixin.common.bean.WxMenu; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.cp.bean.*; +import me.chanjar.weixin.cp.bean.WxCpDepart; +import me.chanjar.weixin.cp.bean.WxCpUser; +import me.chanjar.weixin.common.exception.WxErrorException; + +/** + * 微信API的Service + */ +public interface WxCpService { + + /** + *
+   * 验证推送过来的消息的正确性
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=验证消息真实性
+   * 
+ * + * @param msgSignature + * @param timestamp + * @param nonce + * @param data 微信传输过来的数据,有可能是echoStr,有可能是xml消息 + * @return + */ + public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data); + + /** + *
+   *   用在二次验证的时候
+   *   企业在员工验证成功后,调用本方法告诉企业号平台该员工关注成功。
+   * 
+ * + * @param userId + */ + public void userAuthenticated(String userId) throws WxErrorException; + + /** + *
+   * 获取access_token,本方法线程安全
+   * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限
+   * 另:本service的所有方法都会在access_token过期是调用此方法
+   * 程序员在非必要情况下尽量不要主动调用此方法
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
+   * 
+ * + * @throws me.chanjar.weixin.common.exception.WxErrorException + */ + public void accessTokenRefresh() throws WxErrorException; + + /** + *
+   * 上传多媒体文件
+   * 上传的多媒体文件有格式和大小限制,如下:
+   *   图片(image): 1M,支持JPG格式
+   *   语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
+   *   视频(video):10MB,支持MP4格式
+   *   缩略图(thumb):64KB,支持JPG格式
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
+   * 
+ * + * @param mediaType 媒体类型, 请看{@link WxCpConsts} + * @param fileType 文件类型,请看{@link WxCpConsts} + * @param inputStream 输入流 + * @throws WxErrorException + */ + public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) + throws WxErrorException, IOException; + + /** + * @param mediaType + * @param file + * @throws WxErrorException + * @see #mediaUpload(String, String, InputStream) + */ + public WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException; + + /** + *
+   * 下载多媒体文件
+   * 根据微信文档,视频文件下载不了,会返回null
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
+   * 
+ * + * @return 保存到本地的临时文件 + * @throws WxErrorException + * @params media_id + */ + public File mediaDownload(String media_id) throws WxErrorException; + + /** + *
+   * 发送消息
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=发送消息
+   * 
+ * + * @param message + * @throws WxErrorException + */ + public void messageSend(WxCpMessage message) throws WxErrorException; + + /** + *
+   * 自定义菜单创建接口
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口
+   * 
+ * + * @param menu + * @throws WxErrorException + */ + public void menuCreate(WxMenu menu) throws WxErrorException; + + /** + *
+   * 自定义菜单删除接口
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口
+   * 
+ * + * @throws WxErrorException + */ + public void menuDelete() throws WxErrorException; + + /** + *
+   * 自定义菜单查询接口
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口
+   * 
+ * + * @return + * @throws WxErrorException + */ + public WxMenu menuGet() throws WxErrorException; + + /** + *
+   * 部门管理接口 - 创建部门
+   * 最多支持创建500个部门
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口
+   * 
+ * + * @param depart 部门 + * @return 部门id + * @throws WxErrorException + */ + public Integer departCreate(WxCpDepart depart) throws WxErrorException; + + /** + *
+   * 部门管理接口 - 查询所有部门
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口
+   * 
+ * + * @return + * @throws WxErrorException + */ + public List departGet() throws WxErrorException; + + /** + *
+   * 部门管理接口 - 修改部门名
+   * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口
+   * 如果id为0(未部门),1(黑名单),2(星标组),或者不存在的id,微信会返回系统繁忙的错误
+   * 
+ * + * @param group 要更新的group,group的id,name必须设置 + * @throws WxErrorException + */ + public void departUpdate(WxCpDepart group) throws WxErrorException; + + /** + *
+   * 部门管理接口 - 删除部门
+   * 
+ * + * @param departId + * @throws WxErrorException + */ + public void departDelete(Integer departId) throws WxErrorException; + + /** + * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98 + * + * @param departId 必填。部门id + * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 + * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 + * @return + * @throws WxErrorException + */ + public List departGetUsers(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException; + + /** + * 新建用户 + * + * @param user + * @throws WxErrorException + */ + public void userCreate(WxCpUser user) throws WxErrorException; + + /** + * 更新用户 + * + * @param user + * @throws WxErrorException + */ + public void userUpdate(WxCpUser user) throws WxErrorException; + + /** + * 删除用户 + * + * @param userid + * @throws WxErrorException + */ + public void userDelete(String userid) throws WxErrorException; + + /** + * 获取用户 + * + * @param userid + * @return + * @throws WxErrorException + */ + public WxCpUser userGet(String userid) throws WxErrorException; + + /** + * 创建标签 + * + * @param tagName + * @return + */ + public String tagCreate(String tagName) throws WxErrorException; + + /** + * 更新标签 + * + * @param tagId + * @param tagName + */ + public void tagUpdate(String tagId, String tagName) throws WxErrorException; + + /** + * 删除标签 + * + * @param tagId + */ + public void tagDelete(String tagId) throws WxErrorException; + + /** + * 获得标签列表 + * + * @return + */ + public List tagGet() throws WxErrorException; + + /** + * 获取标签成员 + * + * @param tagId + * @return + */ + public List tagGetUsers(String tagId) throws WxErrorException; + + /** + * 增加标签成员 + * + * @param tagId + * @param userIds + */ + public void tagAddUsers(String tagId, List userIds) throws WxErrorException; + + /** + * 移除标签成员 + * + * @param tagId + * @param userIds + */ + public void tagRemoveUsers(String tagId, List userIds) throws WxErrorException; + + /** + * 注入 {@link WxCpConfigStorage} 的实现 + * + * @param wxConfigProvider + */ + public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider); +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java new file mode 100644 index 00000000..4d9b2af3 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java @@ -0,0 +1,378 @@ +package me.chanjar.weixin.cp.api; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.bean.WxMenu; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.cp.bean.*; +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.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; + +import me.chanjar.weixin.cp.bean.WxCpDepart; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.cp.bean.WxCpUser; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.fs.FileUtils; +import me.chanjar.weixin.common.util.http.MediaDownloadRequestExecutor; +import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; + +import com.google.gson.JsonElement; +import com.google.gson.internal.Streams; +import com.google.gson.reflect.TypeToken; +import com.google.gson.stream.JsonReader; + +public class WxCpServiceImpl implements WxCpService { + + /** + * 全局的是否正在刷新Access Token的flag + * true: 正在刷新 + * false: 没有刷新 + */ + 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 retryTimes = new ThreadLocal(); + + public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { + try { + return SHA1.gen(wxCpConfigStorage.getToken(), timestamp, nonce, data).equals(msgSignature); + } catch (Exception e) { + return false; + } + } + + public void userAuthenticated(String userId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?userid=" + userId; + execute(new SimpleGetRequestExecutor(), url, null); + } + + public void accessTokenRefresh() throws WxErrorException { + if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) { + try { + String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" + + "&corpid=" + wxCpConfigStorage.getCorpId() + + "&corpsecret=" + wxCpConfigStorage.getCorpSecret(); + try { + HttpGet httpGet = new HttpGet(url); + CloseableHttpResponse response = httpclient.execute(httpGet); + String resultContent = new BasicResponseHandler().handleResponse(response); + WxError error = WxError.fromJson(resultContent); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + wxCpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } catch (ClientProtocolException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } finally { + GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.set(false); + } + } else { + // 每隔100ms检查一下是否刷新完毕了 + while (GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.get()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + } + } + // 刷新完毕了,就没他什么事儿了 + } + } + + public void messageSend(WxCpMessage message) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; + execute(new SimplePostRequestExecutor(), url, message.toJson()); + } + + public void menuCreate(WxMenu menu) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/create?agentid=" + wxCpConfigStorage.getAgentId(); + execute(new SimplePostRequestExecutor(), url, menu.toJson()); + } + + public void menuDelete() throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/delete?agentid=" + wxCpConfigStorage.getAgentId(); + execute(new SimpleGetRequestExecutor(), url, null); + } + + public WxMenu menuGet() throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/get?agentid=" + wxCpConfigStorage.getAgentId(); + try { + String resultContent = execute(new SimpleGetRequestExecutor(), url, null); + return WxMenu.fromJson(resultContent); + } catch (WxErrorException e) { + // 46003 不存在的菜单数据 + if (e.getError().getErrorCode() == 46003) { + return null; + } + throw e; + } + } + + public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) + throws WxErrorException, IOException { + return mediaUpload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType)); + } + + public WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType; + return execute(new MediaUploadRequestExecutor(), url, file); + } + + public File mediaDownload(String media_id) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/media/get"; + return execute(new MediaDownloadRequestExecutor(), url, "media_id=" + media_id); + } + + + public Integer departCreate(WxCpDepart depart) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create"; + String responseContent = execute( + new SimplePostRequestExecutor(), + url, + depart.toJson()); + JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); + return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id")); + } + + public void departUpdate(WxCpDepart group) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/department/update"; + execute(new SimplePostRequestExecutor(), url, group.toJson()); + } + + public void departDelete(Integer departId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/department/delete?id=" + departId; + execute(new SimpleGetRequestExecutor(), url, null); + } + + public List departGet() throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/department/list"; + String responseContent = execute(new SimpleGetRequestExecutor(), url, null); + /* + * 操蛋的微信API,创建时返回的是 { group : { id : ..., name : ...} } + * 查询时返回的是 { groups : [ { id : ..., name : ..., count : ... }, ... ] } + */ + JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); + return WxCpGsonBuilder.INSTANCE.create() + .fromJson( + tmpJsonElement.getAsJsonObject().get("department"), + new TypeToken>() { }.getType() + ); + } + + @Override + public void userCreate(WxCpUser user) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/create"; + execute(new SimplePostRequestExecutor(), url, user.toJson()); + } + + @Override + public void userUpdate(WxCpUser user) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/update"; + execute(new SimplePostRequestExecutor(), url, user.toJson()); + } + + @Override + public void userDelete(String userid) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/delete?userid=" + userid; + execute(new SimpleGetRequestExecutor(), url, null); + } + + @Override + public WxCpUser userGet(String userid) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?userid=" + userid; + String responseContent = execute(new SimpleGetRequestExecutor(), url, null); + return WxCpUser.fromJson(responseContent); + } + + @Override + public List departGetUsers(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?department_id=" + departId; + String params = ""; + if (fetchChild != null) { + params += "&fetch_child=" + (fetchChild ? "1" : "0"); + } + if (status != null) { + params += "&status=" + status; + } else { + params += "&status=0"; + } + + String responseContent = execute(new SimpleGetRequestExecutor(), url, params); + JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); + return WxCpGsonBuilder.INSTANCE.create() + .fromJson( + tmpJsonElement.getAsJsonObject().get("userlist"), + new TypeToken>() { }.getType() + ); + } + + @Override + public String tagCreate(String tagName) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/create"; + JsonObject o = new JsonObject(); + o.addProperty("tagname", tagName); + String responseContent = execute(new SimplePostRequestExecutor(), url, o.toString()); + JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); + return tmpJsonElement.getAsJsonObject().get("tagid").getAsString(); + } + + @Override + public void tagUpdate(String tagId, String tagName) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/update"; + JsonObject o = new JsonObject(); + o.addProperty("tagid", tagId); + o.addProperty("tagname", tagName); + execute(new SimplePostRequestExecutor(), url, o.toString()); + } + + @Override + public void tagDelete(String tagId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?tagid=" + tagId; + execute(new SimpleGetRequestExecutor(), url, null); + } + + @Override + public List tagGet() throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/list"; + String responseContent = execute(new SimpleGetRequestExecutor(), url, null); + JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); + return WxCpGsonBuilder.INSTANCE.create() + .fromJson( + tmpJsonElement.getAsJsonObject().get("taglist"), + new TypeToken>() { }.getType() + ); + } + + @Override + public List tagGetUsers(String tagId) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/get?tagid=" + tagId; + String responseContent = execute(new SimpleGetRequestExecutor(), url, null); + JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); + return WxCpGsonBuilder.INSTANCE.create() + .fromJson( + tmpJsonElement.getAsJsonObject().get("userlist"), + new TypeToken>() { }.getType() + ); + } + + @Override + public void tagAddUsers(String tagId, List userIds) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/addtagusers"; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("tagid", tagId); + JsonArray jsonArray = new JsonArray(); + for (String userId : userIds) { + jsonArray.add(new JsonPrimitive(userId)); + } + jsonObject.add("userlist", jsonArray); + execute(new SimplePostRequestExecutor(), url, jsonObject.toString()); + } + + @Override + public void tagRemoveUsers(String tagId, List userIds) throws WxErrorException { + String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/deltagusers"; + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("tagid", tagId); + JsonArray jsonArray = new JsonArray(); + for (String userId : userIds) { + jsonArray.add(new JsonPrimitive(userId)); + } + jsonObject.add("userlist", jsonArray); + execute(new SimplePostRequestExecutor(), url, jsonObject.toString()); + } + + /** + * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 + * + * @param executor + * @param uri + * @param data + * @return + * @throws WxErrorException + */ + public T execute(RequestExecutor executor, String uri, E data) throws WxErrorException { + if (StringUtils.isBlank(wxCpConfigStorage.getAccessToken())) { + accessTokenRefresh(); + } + String accessToken = wxCpConfigStorage.getAccessToken(); + + String uriWithAccessToken = uri; + uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; + + try { + return executor.execute(uriWithAccessToken, data); + } catch (WxErrorException e) { + WxError error = e.getError(); + /* + * 发生以下情况时尝试刷新access_token + * 40001 获取access_token时AppSecret错误,或者access_token无效 + * 42001 access_token超时 + */ + if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { + accessTokenRefresh(); + return execute(executor, uri, data); + } + /** + * -1 系统繁忙, 1000ms后重试 + */ + if (error.getErrorCode() == -1) { + if (retryTimes.get() == null) { + retryTimes.set(0); + } + if (retryTimes.get() > 4) { + retryTimes.set(0); + throw new RuntimeException("微信服务端异常,超出重试次数"); + } + int sleepMillis = 1000 * (1 << retryTimes.get()); + try { + System.out.println("微信系统繁忙," + sleepMillis + "ms后重试"); + Thread.sleep(sleepMillis); + retryTimes.set(retryTimes.get() + 1); + return execute(executor, uri, data); + } catch (InterruptedException e1) { + throw new RuntimeException(e1); + } + } + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return null; + } catch (ClientProtocolException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) { + this.wxCpConfigStorage = wxConfigProvider; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java new file mode 100644 index 00000000..975cd595 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.cp.bean; + +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 微信部门 + * + * @author Daniel Qian + */ +public class WxCpDepart { + + private Integer id; + private String name; + private Integer parentId; + private Integer order; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getParentId() { + return parentId; + } + + public void setParentId(Integer parentId) { + this.parentId = parentId; + } + + public Integer getOrder() { + return order; + } + + public void setOrder(Integer order) { + this.order = order; + } + + public static WxCpDepart fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpDepart.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + + @Override + public String toString() { + return "WxCpDepart{" + + "id=" + id + + ", name='" + name + '\'' + + ", parentId=" + parentId + + ", order=" + order + + '}'; + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java new file mode 100644 index 00000000..d700aa74 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java @@ -0,0 +1,215 @@ +package me.chanjar.weixin.cp.bean; + +import java.util.ArrayList; +import java.util.List; + +import me.chanjar.weixin.cp.bean.messagebuilder.*; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 消息 + * @author Daniel Qian + * + */ +public class WxCpMessage { + + private String toUser; + private String toParty; + private String toTag; + private String agentId; + private String msgType; + private String content; + private String mediaId; + private String thumbMediaId; + private String title; + private String description; + private String musicUrl; + private String hqMusicUrl; + private List articles = new ArrayList(); + + public String getToUser() { + return toUser; + } + public void setToUser(String toUser) { + this.toUser = toUser; + } + + public String getToParty() { + return toParty; + } + + public void setToParty(String toParty) { + this.toParty = toParty; + } + + public String getToTag() { + return toTag; + } + + public void setToTag(String toTag) { + this.toTag = toTag; + } + + public String getAgentId() { + return agentId; + } + + public void setAgentId(String agentId) { + this.agentId = agentId; + } + + public String getMsgType() { + return msgType; + } + + /** + *
+   * 请使用
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#CUSTOM_MSG_TEXT}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#CUSTOM_MSG_IMAGE}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#CUSTOM_MSG_VOICE}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#CUSTOM_MSG_MUSIC}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#CUSTOM_MSG_VIDEO}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#CUSTOM_MSG_NEWS}
+   * 
+ * @param msgType + */ + public void setMsgType(String msgType) { + this.msgType = msgType; + } + public String getContent() { + return content; + } + public void setContent(String content) { + this.content = content; + } + public String getMediaId() { + return mediaId; + } + public void setMediaId(String mediaId) { + this.mediaId = mediaId; + } + public String getThumbMediaId() { + return thumbMediaId; + } + public void setThumbMediaId(String thumbMediaId) { + this.thumbMediaId = thumbMediaId; + } + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public String getMusicUrl() { + return musicUrl; + } + public void setMusicUrl(String musicUrl) { + this.musicUrl = musicUrl; + } + public String getHqMusicUrl() { + return hqMusicUrl; + } + public void setHqMusicUrl(String hqMusicUrl) { + this.hqMusicUrl = hqMusicUrl; + } + public List getArticles() { + return articles; + } + public void setArticles(List articles) { + this.articles = articles; + } + + public String toJson() { + return WxCpGsonBuilder.INSTANCE.create().toJson(this); + } + + public static class WxArticle { + + private String title; + private String description; + private String url; + private String picUrl; + + public String getTitle() { + return title; + } + public void setTitle(String title) { + this.title = title; + } + public String getDescription() { + return description; + } + public void setDescription(String description) { + this.description = description; + } + public String getUrl() { + return url; + } + public void setUrl(String url) { + this.url = url; + } + public String getPicUrl() { + return picUrl; + } + public void setPicUrl(String picUrl) { + this.picUrl = picUrl; + } + + } + + /** + * 获得文本消息builder + * @return + */ + public static TextBuilder TEXT() { + return new TextBuilder(); + } + + /** + * 获得图片消息builder + * @return + */ + public static ImageBuilder IMAGE() { + return new ImageBuilder(); + } + + /** + * 获得语音消息builder + * @return + */ + public static VoiceBuilder VOICE() { + return new VoiceBuilder(); + } + + /** + * 获得视频消息builder + * @return + */ + public static VideoBuilder VIDEO() { + return new VideoBuilder(); + } + + /** + * 获得图文消息builder + * @return + */ + public static NewsBuilder NEWS() { + return new NewsBuilder(); + } + + /** + * 获得文件消息builder + * @return + */ + public static FileBuilder FILE() { + return new FileBuilder(); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTag.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTag.java new file mode 100644 index 00000000..a56e9c5c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTag.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.cp.bean; + +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * Created by Daniel Qian + */ +public class WxCpTag { + + private String id; + + private String name; + + public WxCpTag() { + super(); + } + + public WxCpTag(String id, String name) { + super(); + this.id = id; + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public static WxCpTag fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTag.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java new file mode 100644 index 00000000..90125a5d --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java @@ -0,0 +1,142 @@ +package me.chanjar.weixin.cp.bean; + +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.ArrayList; +import java.util.List; + +/** + * 微信用户信息 + * + * @author Daniel Qian + */ +public class WxCpUser { + + private String userId; + private String name; + private Integer[] departIds; + private String position; + private String mobile; + private String gender; + private String tel; + private String email; + private String weiXinId; + private final List extAttrs = new ArrayList(); + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer[] getDepartIds() { + return departIds; + } + + public void setDepartIds(Integer[] departIds) { + this.departIds = departIds; + } + + public String getGender() { + return gender; + } + + public void setGender(String gender) { + this.gender = gender; + } + + public String getPosition() { + return position; + } + + public void setPosition(String position) { + this.position = position; + } + + public String getMobile() { + return mobile; + } + + public void setMobile(String mobile) { + this.mobile = mobile; + } + + public String getTel() { + return tel; + } + + public void setTel(String tel) { + this.tel = tel; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getWeiXinId() { + return weiXinId; + } + + public void setWeiXinId(String weiXinId) { + this.weiXinId = weiXinId; + } + + public void addExtAttr(String name, String value) { + this.extAttrs.add(new Attr(name, value)); + } + + public List getExtAttrs() { + return extAttrs; + } + + public String toJson() { + return WxCpGsonBuilder.INSTANCE.create().toJson(this); + } + + public static WxCpUser fromJson(String json) { + return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpUser.class); + } + + public static class Attr { + + private String name; + private String value; + + public Attr(String name, String value) { + this.name = name; + this.value = value; + } + + 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; + } + + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java new file mode 100644 index 00000000..82d2884c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java @@ -0,0 +1,648 @@ +package me.chanjar.weixin.cp.bean; + +import me.chanjar.weixin.common.util.xml.AdapterCDATA; +import me.chanjar.weixin.cp.api.WxCpConfigStorage; +import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; +import me.chanjar.weixin.cp.util.xml.XmlTransformer; +import org.apache.commons.io.IOUtils; + +import javax.xml.bind.JAXBException; +import javax.xml.bind.annotation.*; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + *
+ * 微信推送过来的消息,也是同步回复给用户的消息,xml格式
+ * 相关字段的解释看微信开发者文档:
+ * http://mp.weixin.qq.com/wiki/index.php?title=接收普通消息
+ * http://mp.weixin.qq.com/wiki/index.php?title=接收事件推送
+ * http://mp.weixin.qq.com/wiki/index.php?title=接收语音识别结果
+ * 
+ * + * @author Daniel Qian + */ +@XmlRootElement(name = "xml") +@XmlAccessorType(XmlAccessType.FIELD) +public class WxCpXmlMessage { + + /////////////////////// + // 以下都是微信推送过来的消息的xml的element所对应的属性 + /////////////////////// + + @XmlElement(name="AgentID") + private Integer agentId; + + @XmlElement(name = "ToUserName") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String toUserName; + + @XmlElement(name = "FromUserName") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String fromUserName; + + @XmlElement(name = "CreateTime") + private Long createTime; + + @XmlElement(name = "MsgType") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String msgType; + + @XmlElement(name = "Content") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String content; + + @XmlElement(name = "MsgId") + private Long msgId; + + @XmlElement(name = "PicUrl") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String picUrl; + + @XmlElement(name = "MediaId") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String mediaId; + + @XmlElement(name = "Format") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String format; + + @XmlElement(name = "ThumbMediaId") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String thumbMediaId; + + @XmlElement(name = "Location_X") + private Double locationX; + + @XmlElement(name = "Location_Y") + private Double locationY; + + @XmlElement(name = "Scale") + private Double scale; + + @XmlElement(name = "Label") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String label; + + @XmlElement(name = "Title") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String title; + + @XmlElement(name = "Description") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String description; + + @XmlElement(name = "Url") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String url; + + @XmlElement(name = "Event") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String event; + + @XmlElement(name = "EventKey") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String eventKey; + + @XmlElement(name = "Ticket") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String ticket; + + @XmlElement(name = "Latitude") + private Double latitude; + + @XmlElement(name = "Longitude") + private Double longitude; + + @XmlElement(name = "Precision") + private Double precision; + + @XmlElement(name = "Recognition") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String recognition; + + /////////////////////////////////////// + // 群发消息返回的结果 + /////////////////////////////////////// + /** + * 群发的结果 + */ + @XmlElement(name = "Status") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String status; + /** + * group_id下粉丝数;或者openid_list中的粉丝数 + */ + @XmlElement(name = "TotalCount") + private Integer totalCount; + /** + * 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount + */ + @XmlElement(name = "FilterCount") + private Integer filterCount; + /** + * 发送成功的粉丝数 + */ + @XmlElement(name = "SentCount") + private Integer sentCount; + /** + * 发送失败的粉丝数 + */ + @XmlElement(name = "ErrorCount") + private Integer errorCount; + + @XmlElement(name = "ScanCodeInfo") + private ScanCodeInfo scanCodeInfo = new ScanCodeInfo(); + + @XmlElement(name = "SendPicsInfo") + private SendPicsInfo sendPicsInfo = new SendPicsInfo(); + + @XmlElement(name = "SendLocationInfo") + private SendLocationInfo sendLocationInfo = new SendLocationInfo(); + + public Integer getAgentId() { + return agentId; + } + + public void setAgentId(Integer agentId) { + this.agentId = agentId; + } + + public String getToUserName() { + return toUserName; + } + + public void setToUserName(String toUserName) { + this.toUserName = toUserName; + } + + public Long getCreateTime() { + return createTime; + } + + public void setCreateTime(Long createTime) { + this.createTime = createTime; + } + + /** + *
+   * 当接受用户消息时,可能会获得以下值:
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_TEXT}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_IMAGE}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_VOICE}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_VIDEO}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_LOCATION}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_LINK}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_EVENT}
+   * 
+ * + * @return + */ + public String getMsgType() { + return msgType; + } + + /** + *
+   * 当发送消息的时候使用:
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_TEXT}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_IMAGE}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_VOICE}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_VIDEO}
+   * {@link me.chanjar.weixin.cp.api.WxCpConsts#XML_MSG_NEWS}
+   * 
+ * + * @param msgType + */ + public void setMsgType(String msgType) { + this.msgType = msgType; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Long getMsgId() { + return msgId; + } + + public void setMsgId(Long msgId) { + this.msgId = msgId; + } + + public String getPicUrl() { + return picUrl; + } + + public void setPicUrl(String picUrl) { + this.picUrl = picUrl; + } + + public String getMediaId() { + return mediaId; + } + + public void setMediaId(String mediaId) { + this.mediaId = mediaId; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public String getThumbMediaId() { + return thumbMediaId; + } + + public void setThumbMediaId(String thumbMediaId) { + this.thumbMediaId = thumbMediaId; + } + + public Double getLocationX() { + return locationX; + } + + public void setLocationX(Double locationX) { + this.locationX = locationX; + } + + public Double getLocationY() { + return locationY; + } + + public void setLocationY(Double locationY) { + this.locationY = locationY; + } + + public Double getScale() { + return scale; + } + + public void setScale(Double scale) { + this.scale = scale; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getEvent() { + return event; + } + + public void setEvent(String event) { + this.event = event; + } + + public String getEventKey() { + return eventKey; + } + + public void setEventKey(String eventKey) { + this.eventKey = eventKey; + } + + public String getTicket() { + return ticket; + } + + public void setTicket(String ticket) { + this.ticket = ticket; + } + + public Double getLatitude() { + return latitude; + } + + public void setLatitude(Double latitude) { + this.latitude = latitude; + } + + public Double getLongitude() { + return longitude; + } + + public void setLongitude(Double longitude) { + this.longitude = longitude; + } + + public Double getPrecision() { + return precision; + } + + public void setPrecision(Double precision) { + this.precision = precision; + } + + public String getRecognition() { + return recognition; + } + + public void setRecognition(String recognition) { + this.recognition = recognition; + } + + public String getFromUserName() { + return fromUserName; + } + + public void setFromUserName(String fromUserName) { + this.fromUserName = fromUserName; + } + + protected static WxCpXmlMessage fromXml(String xml) { + try { + return XmlTransformer.fromXml(WxCpXmlMessage.class, xml); + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } + + protected static WxCpXmlMessage fromXml(InputStream is) { + try { + return XmlTransformer.fromXml(WxCpXmlMessage.class, is); + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } + + /** + * 从加密字符串转换 + * + * @param encryptedXml + * @param wxCpConfigStorage + * @param timestamp + * @param nonce + * @param msgSignature + * @return + */ + public static WxCpXmlMessage fromEncryptedXml( + String encryptedXml, + WxCpConfigStorage wxCpConfigStorage, + String timestamp, String nonce, String msgSignature) { + WxCpCryptUtil cryptUtil = new WxCpCryptUtil(wxCpConfigStorage); + String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, encryptedXml); + return fromXml(plainText); + } + + public static WxCpXmlMessage fromEncryptedXml( + InputStream is, + WxCpConfigStorage wxCpConfigStorage, + String timestamp, String nonce, String msgSignature) { + try { + return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxCpConfigStorage, timestamp, nonce, msgSignature); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public Integer getTotalCount() { + return totalCount; + } + + public void setTotalCount(Integer totalCount) { + this.totalCount = totalCount; + } + + public Integer getFilterCount() { + return filterCount; + } + + public void setFilterCount(Integer filterCount) { + this.filterCount = filterCount; + } + + public Integer getSentCount() { + return sentCount; + } + + public void setSentCount(Integer sentCount) { + this.sentCount = sentCount; + } + + public Integer getErrorCount() { + return errorCount; + } + + public void setErrorCount(Integer errorCount) { + this.errorCount = errorCount; + } + + public WxCpXmlMessage.ScanCodeInfo getScanCodeInfo() { + return scanCodeInfo; + } + + public void setScanCodeInfo(WxCpXmlMessage.ScanCodeInfo scanCodeInfo) { + this.scanCodeInfo = scanCodeInfo; + } + + public WxCpXmlMessage.SendPicsInfo getSendPicsInfo() { + return sendPicsInfo; + } + + public void setSendPicsInfo(WxCpXmlMessage.SendPicsInfo sendPicsInfo) { + this.sendPicsInfo = sendPicsInfo; + } + + public WxCpXmlMessage.SendLocationInfo getSendLocationInfo() { + return sendLocationInfo; + } + + public void setSendLocationInfo(WxCpXmlMessage.SendLocationInfo sendLocationInfo) { + this.sendLocationInfo = sendLocationInfo; + } + + @XmlRootElement(name = "ScanCodeInfo") + @XmlAccessorType(XmlAccessType.FIELD) + public static class ScanCodeInfo { + + @XmlElement(name = "ScanType") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String scanType; + + @XmlElement(name = "ScanResult") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String scanResult; + + /** + * 扫描类型,一般是qrcode + * @return + */ + public String getScanType() { + + return scanType; + } + + public void setScanType(String scanType) { + this.scanType = scanType; + } + + /** + * 扫描结果,即二维码对应的字符串信息 + * @return + */ + public String getScanResult() { + return scanResult; + } + + public void setScanResult(String scanResult) { + this.scanResult = scanResult; + } + + } + + @XmlRootElement(name = "SendPicsInfo") + @XmlAccessorType(XmlAccessType.FIELD) + public static class SendPicsInfo { + + @XmlElement(name = "Count") + private Long count; + + @XmlElementWrapper(name="PicList") + @XmlElement(name = "item") + protected final List picList = new ArrayList(); + + public Long getCount() { + return count; + } + + public void setCount(Long count) { + this.count = count; + } + + public List getPicList() { + return picList; + } + + @XmlRootElement(name = "item") + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "WxXmlMessage.SendPicsInfo.Item") + public static class Item { + + @XmlElement(name = "PicMd5Sum") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String PicMd5Sum; + + public String getPicMd5Sum() { + return PicMd5Sum; + } + + public void setPicMd5Sum(String picMd5Sum) { + PicMd5Sum = picMd5Sum; + } + } + } + + @XmlRootElement(name = "SendLocationInfo") + @XmlAccessorType(XmlAccessType.FIELD) + public static class SendLocationInfo { + + @XmlElement(name = "Location_X") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String locationX; + + @XmlElement(name = "Location_Y") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String locationY; + + @XmlElement(name = "Scale") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String scale; + + @XmlElement(name = "Label") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String label; + + @XmlElement(name = "Poiname") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String poiname; + + public String getLocationX() { + return locationX; + } + + public void setLocationX(String locationX) { + this.locationX = locationX; + } + + public String getLocationY() { + return locationY; + } + + public void setLocationY(String locationY) { + this.locationY = locationY; + } + + public String getScale() { + return scale; + } + + public void setScale(String scale) { + this.scale = scale; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public String getPoiname() { + return poiname; + } + + public void setPoiname(String poiname) { + this.poiname = poiname; + } + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java new file mode 100644 index 00000000..29efa04f --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.cp.bean; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import me.chanjar.weixin.cp.api.WxCpConsts; +import me.chanjar.weixin.common.util.xml.MediaIdMarshaller; + +@XmlRootElement(name = "xml") +@XmlAccessorType(XmlAccessType.FIELD) +public class WxCpXmlOutImageMessage extends WxCpXmlOutMessage { + + @XmlElement(name="Image") + @XmlJavaTypeAdapter(MediaIdMarshaller.class) + private String mediaId; + + public WxCpXmlOutImageMessage() { + this.msgType = WxCpConsts.XML_MSG_IMAGE; + } + + public String getMediaId() { + return mediaId; + } + + public void setMediaId(String mediaId) { + this.mediaId = mediaId; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java new file mode 100644 index 00000000..1ccacb00 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java @@ -0,0 +1,124 @@ +package me.chanjar.weixin.cp.bean; + +import me.chanjar.weixin.common.util.xml.AdapterCDATA; +import me.chanjar.weixin.cp.api.WxCpConfigStorage; +import me.chanjar.weixin.cp.bean.outxmlbuilder.*; +import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; +import me.chanjar.weixin.cp.util.xml.XmlTransformer; + +import javax.xml.bind.JAXBException; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +@XmlRootElement(name = "xml") +@XmlAccessorType(XmlAccessType.FIELD) +public class WxCpXmlOutMessage { + + @XmlElement(name="ToUserName") + @XmlJavaTypeAdapter(AdapterCDATA.class) + protected String toUserName; + + @XmlElement(name="FromUserName") + @XmlJavaTypeAdapter(AdapterCDATA.class) + protected String fromUserName; + + @XmlElement(name="CreateTime") + protected Long createTime; + + @XmlElement(name="MsgType") + @XmlJavaTypeAdapter(AdapterCDATA.class) + protected String msgType; + + public String getToUserName() { + return toUserName; + } + + public void setToUserName(String toUserName) { + this.toUserName = toUserName; + } + + public String getFromUserName() { + return fromUserName; + } + + public void setFromUserName(String fromUserName) { + this.fromUserName = fromUserName; + } + + public Long getCreateTime() { + return createTime; + } + + public void setCreateTime(Long createTime) { + this.createTime = createTime; + } + + public String getMsgType() { + return msgType; + } + + public void setMsgType(String msgType) { + this.msgType = msgType; + } + + protected String toXml() { + try { + return XmlTransformer.toXml((Class)this.getClass(), this); + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } + + /** + * 转换成加密的xml格式 + * @return + */ + public String toEncryptedXml(WxCpConfigStorage wxCpConfigStorage) { + String plainXml = toXml(); + WxCpCryptUtil pc = new WxCpCryptUtil(wxCpConfigStorage); + return pc.encrypt(plainXml); + } + + /** + * 获得文本消息builder + * @return + */ + public static TextBuilder TEXT() { + return new TextBuilder(); + } + + /** + * 获得图片消息builder + * @return + */ + public static ImageBuilder IMAGE() { + return new ImageBuilder(); + } + + /** + * 获得语音消息builder + * @return + */ + public static VoiceBuilder VOICE() { + return new VoiceBuilder(); + } + + /** + * 获得视频消息builder + * @return + */ + public static VideoBuilder VIDEO() { + return new VideoBuilder(); + } + + /** + * 获得图文消息builder + * @return + */ + public static NewsBuilder NEWS() { + return new NewsBuilder(); + } +} diff --git a/src/main/java/me/chanjar/weixin/bean/WxXmlOutMewsMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMewsMessage.java similarity index 87% rename from src/main/java/me/chanjar/weixin/bean/WxXmlOutMewsMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMewsMessage.java index ef3f6c86..24264f24 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxXmlOutMewsMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMewsMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.cp.bean; import java.util.ArrayList; import java.util.List; @@ -10,12 +10,12 @@ import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.util.xml.AdapterCDATA; +import me.chanjar.weixin.cp.api.WxCpConsts; +import me.chanjar.weixin.common.util.xml.AdapterCDATA; @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) -public class WxXmlOutMewsMessage extends WxXmlOutMessage { +public class WxCpXmlOutMewsMessage extends WxCpXmlOutMessage { @XmlElement(name = "ArticleCount") protected int articleCount; @@ -24,8 +24,8 @@ public class WxXmlOutMewsMessage extends WxXmlOutMessage { @XmlElement(name = "item") protected final List articles = new ArrayList(); - public WxXmlOutMewsMessage() { - this.msgType = WxConsts.XML_MSG_NEWS; + public WxCpXmlOutMewsMessage() { + this.msgType = WxCpConsts.XML_MSG_NEWS; } public int getArticleCount() { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java new file mode 100644 index 00000000..25a447fc --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.cp.bean; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import me.chanjar.weixin.cp.api.WxCpConsts; +import me.chanjar.weixin.common.util.xml.AdapterCDATA; + +@XmlRootElement(name = "xml") +@XmlAccessorType(XmlAccessType.FIELD) +public class WxCpXmlOutTextMessage extends WxCpXmlOutMessage { + + @XmlElement(name="Content") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String content; + + public WxCpXmlOutTextMessage() { + this.msgType = WxCpConsts.XML_MSG_TEXT; + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java new file mode 100644 index 00000000..896a71a5 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java @@ -0,0 +1,90 @@ +package me.chanjar.weixin.cp.bean; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import me.chanjar.weixin.cp.api.WxCpConsts; +import me.chanjar.weixin.common.util.xml.AdapterCDATA; + +@XmlRootElement(name = "xml") +@XmlAccessorType(XmlAccessType.FIELD) +public class WxCpXmlOutVideoMessage extends WxCpXmlOutMessage { + + @XmlElement(name = "Video") + protected final Video video = new Video(); + + public WxCpXmlOutVideoMessage() { + this.msgType = WxCpConsts.XML_MSG_VIDEO; + } + + public String getMediaId() { + return video.getMediaId(); + } + + public void setMediaId(String mediaId) { + video.setMediaId(mediaId); + } + + public String getTitle() { + return video.getTitle(); + } + + public void setTitle(String title) { + video.setTitle(title); + } + + public String getDescription() { + return video.getDescription(); + } + + public void setDescription(String description) { + video.setDescription(description); + } + + + @XmlRootElement(name = "Video") + @XmlAccessorType(XmlAccessType.FIELD) + private static class Video { + + @XmlElement(name = "MediaId") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String mediaId; + + @XmlElement(name = "Title") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String title; + + @XmlElement(name = "Description") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String description; + + public String getMediaId() { + return mediaId; + } + + public void setMediaId(String mediaId) { + this.mediaId = mediaId; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java new file mode 100644 index 00000000..b943aee9 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java @@ -0,0 +1,32 @@ +package me.chanjar.weixin.cp.bean; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; + +import me.chanjar.weixin.cp.api.WxCpConsts; +import me.chanjar.weixin.common.util.xml.MediaIdMarshaller; + +@XmlRootElement(name = "xml") +@XmlAccessorType(XmlAccessType.FIELD) +public class WxCpXmlOutVoiceMessage extends WxCpXmlOutMessage { + + @XmlElement(name="Voice") + @XmlJavaTypeAdapter(MediaIdMarshaller.class) + private String mediaId; + + public WxCpXmlOutVoiceMessage() { + this.msgType = WxCpConsts.XML_MSG_VOICE; + } + + public String getMediaId() { + return mediaId; + } + + public void setMediaId(String mediaId) { + this.mediaId = mediaId; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java new file mode 100644 index 00000000..f52f274c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.cp.bean.messagebuilder; + +import me.chanjar.weixin.cp.bean.WxCpMessage; + +public class BaseBuilder { + protected String msgType; + protected String agentId; + protected String toUser; + protected String toParty; + protected String toTag; + + public T agentId(String agentId) { + this.agentId = agentId; + return (T) this; + } + + public T toUser(String toUser) { + this.toUser = toUser; + return (T) this; + } + + public T toParty(String toParty) { + this.toParty = toParty; + return (T) this; + } + + public T toTag(String toTag) { + this.toTag = toTag; + return (T) this; + } + + public WxCpMessage build() { + WxCpMessage m = new WxCpMessage(); + m.setAgentId(this.agentId); + m.setMsgType(this.msgType); + m.setToUser(this.toUser); + m.setToParty(this.toParty); + m.setToTag(this.toTag); + return m; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/FileBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/FileBuilder.java new file mode 100644 index 00000000..ac15b5ee --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/FileBuilder.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.cp.bean.messagebuilder; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.cp.bean.WxCpMessage; + +/** + * 获得消息builder + *
+ * 用法: WxCustomMessage m = WxCustomMessage.FILE().mediaId(...).toUser(...).build();
+ * 
+ * @author Daniel Qian + * + */ +public final class FileBuilder extends BaseBuilder { + private String mediaId; + + public FileBuilder() { + this.msgType = WxConsts.CUSTOM_MSG_FILE; + } + + public FileBuilder mediaId(String media_id) { + this.mediaId = media_id; + return this; + } + + public WxCpMessage build() { + WxCpMessage m = super.build(); + m.setMediaId(this.mediaId); + return m; + } +} diff --git a/src/main/java/me/chanjar/weixin/bean/custombuilder/ImageBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/ImageBuilder.java similarity index 66% rename from src/main/java/me/chanjar/weixin/bean/custombuilder/ImageBuilder.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/ImageBuilder.java index de5cc8d0..5098708a 100644 --- a/src/main/java/me/chanjar/weixin/bean/custombuilder/ImageBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/ImageBuilder.java @@ -1,14 +1,14 @@ -package me.chanjar.weixin.bean.custombuilder; +package me.chanjar.weixin.cp.bean.messagebuilder; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.bean.WxCustomMessage; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.cp.bean.WxCpMessage; /** * 获得消息builder *
  * 用法: WxCustomMessage m = WxCustomMessage.IMAGE().mediaId(...).toUser(...).build();
  * 
- * @author chanjarster + * @author Daniel Qian * */ public final class ImageBuilder extends BaseBuilder { @@ -23,8 +23,8 @@ public final class ImageBuilder extends BaseBuilder { return this; } - public WxCustomMessage build() { - WxCustomMessage m = super.build(); + public WxCpMessage build() { + WxCpMessage m = super.build(); m.setMediaId(this.mediaId); return m; } diff --git a/src/main/java/me/chanjar/weixin/bean/custombuilder/NewsBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/NewsBuilder.java similarity index 53% rename from src/main/java/me/chanjar/weixin/bean/custombuilder/NewsBuilder.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/NewsBuilder.java index f05b5dbc..0711b6f5 100644 --- a/src/main/java/me/chanjar/weixin/bean/custombuilder/NewsBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/NewsBuilder.java @@ -1,35 +1,35 @@ -package me.chanjar.weixin.bean.custombuilder; +package me.chanjar.weixin.cp.bean.messagebuilder; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.cp.bean.WxCpMessage; import java.util.ArrayList; import java.util.List; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.bean.WxCustomMessage; - /** * 图文消息builder *
  * 用法:
  * WxCustomMessage m = WxCustomMessage.NEWS().addArticle(article).toUser(...).build();
  * 
- * @author chanjarster + * @author Daniel Qian * */ public final class NewsBuilder extends BaseBuilder { - private List articles = new ArrayList(); + private List articles = new ArrayList(); public NewsBuilder() { this.msgType = WxConsts.CUSTOM_MSG_NEWS; } - public NewsBuilder addArticle(WxCustomMessage.WxArticle article) { + public NewsBuilder addArticle(WxCpMessage.WxArticle article) { this.articles.add(article); return this; } - public WxCustomMessage build() { - WxCustomMessage m = super.build(); + public WxCpMessage build() { + WxCpMessage m = super.build(); m.setArticles(this.articles); return m; } diff --git a/src/main/java/me/chanjar/weixin/bean/custombuilder/TextBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextBuilder.java similarity index 66% rename from src/main/java/me/chanjar/weixin/bean/custombuilder/TextBuilder.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextBuilder.java index e36aebe8..44a75b8f 100644 --- a/src/main/java/me/chanjar/weixin/bean/custombuilder/TextBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextBuilder.java @@ -1,14 +1,14 @@ -package me.chanjar.weixin.bean.custombuilder; +package me.chanjar.weixin.cp.bean.messagebuilder; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.bean.WxCustomMessage; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.cp.bean.WxCpMessage; /** * 文本消息builder *
  * 用法: WxCustomMessage m = WxCustomMessage.TEXT().content(...).toUser(...).build();
  * 
- * @author chanjarster + * @author Daniel Qian * */ public final class TextBuilder extends BaseBuilder { @@ -23,8 +23,8 @@ public final class TextBuilder extends BaseBuilder { return this; } - public WxCustomMessage build() { - WxCustomMessage m = super.build(); + public WxCpMessage build() { + WxCpMessage m = super.build(); m.setContent(this.content); return m; } diff --git a/src/main/java/me/chanjar/weixin/bean/custombuilder/VideoBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VideoBuilder.java similarity index 84% rename from src/main/java/me/chanjar/weixin/bean/custombuilder/VideoBuilder.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VideoBuilder.java index d38c61e3..32990fa3 100644 --- a/src/main/java/me/chanjar/weixin/bean/custombuilder/VideoBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VideoBuilder.java @@ -1,7 +1,7 @@ -package me.chanjar.weixin.bean.custombuilder; +package me.chanjar.weixin.cp.bean.messagebuilder; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.bean.WxCustomMessage; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.cp.bean.WxCpMessage; /** * 视频消息builder @@ -14,7 +14,7 @@ import me.chanjar.weixin.bean.WxCustomMessage; * .toUser(...) * .build(); * - * @author chanjarster + * @author Daniel Qian * */ public final class VideoBuilder extends BaseBuilder { @@ -47,8 +47,8 @@ public final class VideoBuilder extends BaseBuilder { return this; } - public WxCustomMessage build() { - WxCustomMessage m = super.build(); + public WxCpMessage build() { + WxCpMessage m = super.build(); m.setMediaId(this.mediaId); m.setTitle(title); m.setDescription(description); diff --git a/src/main/java/me/chanjar/weixin/bean/custombuilder/VoiceBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VoiceBuilder.java similarity index 66% rename from src/main/java/me/chanjar/weixin/bean/custombuilder/VoiceBuilder.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VoiceBuilder.java index 4d1e2cf9..99914fcd 100644 --- a/src/main/java/me/chanjar/weixin/bean/custombuilder/VoiceBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VoiceBuilder.java @@ -1,14 +1,14 @@ -package me.chanjar.weixin.bean.custombuilder; +package me.chanjar.weixin.cp.bean.messagebuilder; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.bean.WxCustomMessage; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.cp.bean.WxCpMessage; /** * 语音消息builder *
  * 用法: WxCustomMessage m = WxCustomMessage.VOICE().mediaId(...).toUser(...).build();
  * 
- * @author chanjarster + * @author Daniel Qian * */ public final class VoiceBuilder extends BaseBuilder { @@ -23,8 +23,8 @@ public final class VoiceBuilder extends BaseBuilder { return this; } - public WxCustomMessage build() { - WxCustomMessage m = super.build(); + public WxCpMessage build() { + WxCpMessage m = super.build(); m.setMediaId(this.mediaId); return m; } diff --git a/src/main/java/me/chanjar/weixin/bean/outxmlbuilder/BaseBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/BaseBuilder.java similarity index 79% rename from src/main/java/me/chanjar/weixin/bean/outxmlbuilder/BaseBuilder.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/BaseBuilder.java index 9bb68aba..78ba1074 100644 --- a/src/main/java/me/chanjar/weixin/bean/outxmlbuilder/BaseBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/BaseBuilder.java @@ -1,7 +1,6 @@ -package me.chanjar.weixin.bean.outxmlbuilder; - -import me.chanjar.weixin.bean.WxXmlOutMessage; +package me.chanjar.weixin.cp.bean.outxmlbuilder; +import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; public abstract class BaseBuilder { @@ -21,7 +20,7 @@ public abstract class BaseBuilder { public abstract ValueType build(); - public void setCommon(WxXmlOutMessage m) { + public void setCommon(WxCpXmlOutMessage m) { m.setToUserName(this.toUserName); m.setFromUserName(this.fromUserName); m.setCreateTime(System.currentTimeMillis() / 1000l); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/ImageBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/ImageBuilder.java new file mode 100644 index 00000000..ac3b7fc9 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/ImageBuilder.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.cp.bean.outxmlbuilder; + +import me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessage; + +/** + * 图片消息builder + * @author Daniel Qian + */ +public final class ImageBuilder extends BaseBuilder { + + private String mediaId; + + public ImageBuilder mediaId(String media_id) { + this.mediaId = media_id; + return this; + } + + public WxCpXmlOutImageMessage build() { + WxCpXmlOutImageMessage m = new WxCpXmlOutImageMessage(); + setCommon(m); + m.setMediaId(this.mediaId); + return m; + } + +} diff --git a/src/main/java/me/chanjar/weixin/bean/outxmlbuilder/NewsBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java similarity index 57% rename from src/main/java/me/chanjar/weixin/bean/outxmlbuilder/NewsBuilder.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java index 6168705b..1785f714 100644 --- a/src/main/java/me/chanjar/weixin/bean/outxmlbuilder/NewsBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java @@ -1,16 +1,16 @@ -package me.chanjar.weixin.bean.outxmlbuilder; +package me.chanjar.weixin.cp.bean.outxmlbuilder; import java.util.ArrayList; import java.util.List; -import me.chanjar.weixin.bean.WxXmlOutMewsMessage; -import me.chanjar.weixin.bean.WxXmlOutMewsMessage.Item; +import me.chanjar.weixin.cp.bean.WxCpXmlOutMewsMessage; +import me.chanjar.weixin.cp.bean.WxCpXmlOutMewsMessage.Item; /** * 图文消息builder - * @author chanjarster + * @author Daniel Qian */ -public final class NewsBuilder extends BaseBuilder { +public final class NewsBuilder extends BaseBuilder { protected final List articles = new ArrayList(); @@ -19,8 +19,8 @@ public final class NewsBuilder extends BaseBuilder { + private String content; + + public TextBuilder content(String content) { + this.content = content; + return this; + } + + public WxCpXmlOutTextMessage build() { + WxCpXmlOutTextMessage m = new WxCpXmlOutTextMessage(); + setCommon(m); + m.setContent(this.content); + return m; + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VideoBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VideoBuilder.java new file mode 100644 index 00000000..1bb42ff2 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VideoBuilder.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.cp.bean.outxmlbuilder; + +import me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessage; + +/** + * 视频消息builder + * @author Daniel Qian + * + */ +public final class VideoBuilder extends BaseBuilder { + + private String mediaId; + private String title; + private String description; + + public VideoBuilder title(String title) { + this.title = title; + return this; + } + public VideoBuilder description(String description) { + this.description = description; + return this; + } + public VideoBuilder mediaId(String mediaId) { + this.mediaId = mediaId; + return this; + } + + public WxCpXmlOutVideoMessage build() { + WxCpXmlOutVideoMessage m = new WxCpXmlOutVideoMessage(); + setCommon(m); + m.setTitle(title); + m.setDescription(description); + m.setMediaId(mediaId); + return m; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VoiceBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VoiceBuilder.java new file mode 100644 index 00000000..fb07525a --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VoiceBuilder.java @@ -0,0 +1,25 @@ +package me.chanjar.weixin.cp.bean.outxmlbuilder; + +import me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessage; + +/** + * 语音消息builder + * @author Daniel Qian + */ +public final class VoiceBuilder extends BaseBuilder { + + private String mediaId; + + public VoiceBuilder mediaId(String mediaId) { + this.mediaId = mediaId; + return this; + } + + public WxCpXmlOutVoiceMessage build() { + WxCpXmlOutVoiceMessage m = new WxCpXmlOutVoiceMessage(); + setCommon(m); + m.setMediaId(mediaId); + return m; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpCryptUtil.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpCryptUtil.java new file mode 100755 index 00000000..d2d01c09 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpCryptUtil.java @@ -0,0 +1,43 @@ +/** + * 对公众平台发送给公众账号的消息加解密示例代码. + * + * @copyright Copyright (c) 1998-2014 Tencent Inc. + */ + +// ------------------------------------------------------------------------ + +/** + * 针对org.apache.commons.codec.binary.Base64, + * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) + * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi + */ +package me.chanjar.weixin.cp.util.crypto; + +import me.chanjar.weixin.common.util.crypto.WxCryptUtil; +import me.chanjar.weixin.cp.api.WxCpConfigStorage; +import org.apache.commons.codec.binary.Base64; + +public class WxCpCryptUtil extends WxCryptUtil { + + /** + * 构造函数 + * + * @param wxCpConfigStorage + */ + public WxCpCryptUtil(WxCpConfigStorage wxCpConfigStorage) { + /* + * @param token 公众平台上,开发者设置的token + * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey + * @param appidOrCorpid 公众平台appid + */ + String encodingAesKey = wxCpConfigStorage.getAesKey(); + String token = wxCpConfigStorage.getToken(); + String corpId = wxCpConfigStorage.getCorpId(); + + this.token = token; + this.appidOrCorpid = corpId; + this.aesKey = Base64.decodeBase64(encodingAesKey + "="); + } + + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java new file mode 100644 index 00000000..022333f8 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java @@ -0,0 +1,65 @@ +/* + * 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.cp.util.json; + +import java.lang.reflect.Type; + +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.cp.bean.WxCpDepart; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + +/** + * @author Daniel Qian + */ +public class WxCpDepartGsonAdapter implements JsonSerializer, JsonDeserializer { + + public JsonElement serialize(WxCpDepart group, Type typeOfSrc, JsonSerializationContext context) { + JsonObject json = new JsonObject(); + if (group.getId() != null) { + json.addProperty("id", group.getId()); + } + if (group.getName() != null) { + json.addProperty("name", group.getName()); + } + if (group.getParentId() != null) { + json.addProperty("parentid", group.getParentId()); + } + if (group.getOrder() != null) { + json.addProperty("order", String.valueOf(group.getOrder())); + } + return json; + } + + public WxCpDepart deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + WxCpDepart depart = new WxCpDepart(); + JsonObject departJson = json.getAsJsonObject(); + if (departJson.get("id") != null && !departJson.get("id").isJsonNull()) { + depart.setId(GsonHelper.getAsInteger(departJson.get("id"))); + } + if (departJson.get("name") != null && !departJson.get("name").isJsonNull()) { + depart.setName(GsonHelper.getAsString(departJson.get("name"))); + } + if (departJson.get("order") != null && !departJson.get("order").isJsonNull()) { + depart.setOrder(GsonHelper.getAsInteger(departJson.get("order"))); + } + if (departJson.get("parentid") != null && !departJson.get("parentid").isJsonNull()) { + depart.setParentId(GsonHelper.getAsInteger(departJson.get("parentid"))); + } + return depart; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java new file mode 100644 index 00000000..3a8b739b --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.cp.util.json; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import me.chanjar.weixin.common.bean.result.WxError; +import me.chanjar.weixin.common.util.json.WxMediaUploadResultAdapter; +import me.chanjar.weixin.common.util.json.WxErrorAdapter; +import me.chanjar.weixin.cp.bean.*; + +public class WxCpGsonBuilder { + + public static final GsonBuilder INSTANCE = new GsonBuilder(); + + static { + INSTANCE.disableHtmlEscaping(); + INSTANCE.registerTypeAdapter(WxCpMessage.class, new WxCpMessageGsonAdapter()); + INSTANCE.registerTypeAdapter(WxCpDepart.class, new WxCpDepartGsonAdapter()); + INSTANCE.registerTypeAdapter(WxCpUser.class, new WxCpUserGsonAdapter()); + INSTANCE.registerTypeAdapter(WxError.class, new WxErrorAdapter()); + INSTANCE.registerTypeAdapter(WxCpTag.class, new WxCpTagGsonAdapter()); + } + + public static Gson create() { + return INSTANCE.create(); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMessageGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMessageGsonAdapter.java new file mode 100644 index 00000000..2864e51f --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMessageGsonAdapter.java @@ -0,0 +1,93 @@ +/* + * 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.cp.util.json; + +import java.lang.reflect.Type; + +import me.chanjar.weixin.cp.api.WxCpConsts; +import me.chanjar.weixin.cp.bean.WxCpMessage; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.apache.commons.lang3.StringUtils; + +/** + * + * @author Daniel Qian + * + */ +public class WxCpMessageGsonAdapter implements JsonSerializer { + + public JsonElement serialize(WxCpMessage message, Type typeOfSrc, JsonSerializationContext context) { + JsonObject messageJson = new JsonObject(); + messageJson.addProperty("agentid", message.getAgentId()); + if (StringUtils.isNotBlank(message.getToUser())) { + messageJson.addProperty("touser", message.getToUser()); + } + messageJson.addProperty("msgtype", message.getMsgType()); + + if (StringUtils.isNotBlank(message.getToParty())) { + messageJson.addProperty("toparty", message.getToUser()); + } + if (StringUtils.isNotBlank(message.getToTag())) { + messageJson.addProperty("totag", message.getToUser()); + } + if (WxCpConsts.CUSTOM_MSG_TEXT.equals(message.getMsgType())) { + JsonObject text = new JsonObject(); + text.addProperty("content", message.getContent()); + messageJson.add("text", text); + } + + if (WxCpConsts.CUSTOM_MSG_IMAGE.equals(message.getMsgType())) { + JsonObject image = new JsonObject(); + image.addProperty("media_id", message.getMediaId()); + messageJson.add("image", image); + } + + if (WxCpConsts.CUSTOM_MSG_FILE.equals(message.getMsgType())) { + JsonObject image = new JsonObject(); + image.addProperty("media_id", message.getMediaId()); + messageJson.add("file", image); + } + + if (WxCpConsts.CUSTOM_MSG_VOICE.equals(message.getMsgType())) { + JsonObject voice = new JsonObject(); + voice.addProperty("media_id", message.getMediaId()); + messageJson.add("voice", voice); + } + + if (WxCpConsts.CUSTOM_MSG_VIDEO.equals(message.getMsgType())) { + JsonObject video = new JsonObject(); + video.addProperty("media_id", message.getMediaId()); + video.addProperty("thumb_media_id", message.getThumbMediaId()); + video.addProperty("title", message.getTitle()); + video.addProperty("description", message.getDescription()); + messageJson.add("video", video); + } + + if (WxCpConsts.CUSTOM_MSG_NEWS.equals(message.getMsgType())) { + JsonArray articleJsonArray = new JsonArray(); + for (WxCpMessage.WxArticle article : message.getArticles()) { + JsonObject articleJson = new JsonObject(); + articleJson.addProperty("title", article.getTitle()); + articleJson.addProperty("description", article.getDescription()); + articleJson.addProperty("url", article.getUrl()); + articleJson.addProperty("picurl", article.getPicUrl()); + articleJsonArray.add(articleJson); + } + messageJson.add("articles", articleJsonArray); + } + + return messageJson; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpTagGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpTagGsonAdapter.java new file mode 100644 index 00000000..776e8a44 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpTagGsonAdapter.java @@ -0,0 +1,35 @@ +/* + * 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.cp.util.json; + +import com.google.gson.*; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.cp.bean.WxCpTag; + +import java.lang.reflect.Type; + +/** + * @author Daniel Qian + */ +public class WxCpTagGsonAdapter implements JsonSerializer, JsonDeserializer { + + public JsonElement serialize(WxCpTag group, Type typeOfSrc, JsonSerializationContext context) { + JsonObject o = new JsonObject(); + o.addProperty("tagid", group.getId()); + o.addProperty("tagname", group.getName()); + return o; + } + + public WxCpTag deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + JsonObject jsonObject = json.getAsJsonObject(); + return new WxCpTag(GsonHelper.getString(jsonObject, "tagid"), GsonHelper.getString(jsonObject, "name")); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java new file mode 100644 index 00000000..bf04d50c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java @@ -0,0 +1,115 @@ +/* + * 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.cp.util.json; + +import java.lang.reflect.Type; + +import com.google.gson.*; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.cp.bean.WxCpUser; + +/** + * @author Daniel Qian + */ +public class WxCpUserGsonAdapter implements JsonDeserializer, JsonSerializer { + + public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + JsonObject o = json.getAsJsonObject(); + WxCpUser user = new WxCpUser(); + user.setUserId(GsonHelper.getString(o, "userid")); + user.setName(GsonHelper.getString(o, "name")); + + if(o.get("department") != null) { + JsonArray departJsonArray = o.get("department").getAsJsonArray(); + Integer[] departIds = new Integer[departJsonArray.size()]; + int i = 0; + for (JsonElement jsonElement : departJsonArray) { + departIds[i++] = jsonElement.getAsInt(); + } + user.setDepartIds(departIds); + } + + user.setPosition(GsonHelper.getString(o, "position")); + user.setMobile(GsonHelper.getString(o, "mobile")); + Integer gender = GsonHelper.getInteger(o, "gender"); + if (new Integer(1).equals(gender)) { + user.setGender("男"); + } else if (new Integer(2).equals(gender)) { + user.setGender("女"); + } else { + user.setGender("未知"); + } + user.setTel(GsonHelper.getString(o, "tel")); + user.setEmail(GsonHelper.getString(o, "email")); + user.setWeiXinId(GsonHelper.getString(o, "weixinid")); + + if (GsonHelper.isNotNull(o.get("extattr"))) { + JsonArray attrJsonElements = o.get("extattr").getAsJsonObject().get("attrs").getAsJsonArray(); + for (JsonElement attrJsonElement : attrJsonElements) { + WxCpUser.Attr attr = new WxCpUser.Attr( + GsonHelper.getString(attrJsonElement.getAsJsonObject(), "name"), + GsonHelper.getString(attrJsonElement.getAsJsonObject(), "value") + ); + user.getExtAttrs().add(attr); + } + } + return user; + } + + @Override + public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationContext context) { + JsonObject o = new JsonObject(); + if (user.getUserId() != null) { + o.addProperty("userid", user.getUserId()); + } + if (user.getName() != null) { + o.addProperty("name", user.getName()); + } + if (user.getDepartIds() != null) { + JsonArray jsonArray = new JsonArray(); + for (Integer departId : user.getDepartIds()) { + jsonArray.add(new JsonPrimitive(departId)); + } + o.add("department", jsonArray); + } + if (user.getPosition() != null) { + o.addProperty("position", user.getPosition()); + } + if (user.getMobile() != null) { + o.addProperty("mobile", user.getMobile()); + } + if (user.getGender() != null) { + o.addProperty("gender", user.getGender().equals("男") ? 0 : 1); + } + if (user.getTel() != null) { + o.addProperty("tel", user.getTel()); + } + if (user.getEmail() != null) { + o.addProperty("email", user.getEmail()); + } + if (user.getWeiXinId() != null) { + o.addProperty("weixinid", user.getWeiXinId()); + } + if (user.getExtAttrs().size() > 0) { + JsonArray attrsJsonArray = new JsonArray(); + for (WxCpUser.Attr attr : user.getExtAttrs()) { + JsonObject attrJson = new JsonObject(); + attrJson.addProperty("name", attr.getName()); + attrJson.addProperty("value", attr.getValue()); + attrsJsonArray.add(attrJson); + } + JsonObject attrsJson = new JsonObject(); + attrsJson.add("attrs", attrsJsonArray); + o.add("extattr", attrsJson); + } + return o; + } + +} diff --git a/src/main/java/me/chanjar/weixin/util/xml/XmlTransformer.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XmlTransformer.java similarity index 87% rename from src/main/java/me/chanjar/weixin/util/xml/XmlTransformer.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XmlTransformer.java index 026cc0a5..39914f44 100644 --- a/src/main/java/me/chanjar/weixin/util/xml/XmlTransformer.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XmlTransformer.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.util.xml; +package me.chanjar.weixin.cp.util.xml; import java.io.IOException; import java.io.InputStream; @@ -8,7 +8,7 @@ import java.io.Writer; import javax.xml.bind.*; -import me.chanjar.weixin.bean.*; +import me.chanjar.weixin.cp.bean.*; import org.xml.sax.InputSource; import com.sun.xml.bind.marshaller.CharacterEscapeHandler; @@ -77,14 +77,13 @@ public class XmlTransformer { */ try { return JAXBContext.newInstance( - WxXmlOutMessage.class, - WxXmlOutImageMessage.class, - WxXmlOutMewsMessage.class, - WxXmlOutMusicMessage.class, - WxXmlOutTextMessage.class, - WxXmlOutVideoMessage.class, - WxXmlOutVoiceMessage.class, - WxXmlMessage.class); + WxCpXmlOutMessage.class, + WxCpXmlOutImageMessage.class, + WxCpXmlOutMewsMessage.class, + WxCpXmlOutTextMessage.class, + WxCpXmlOutVideoMessage.class, + WxCpXmlOutVoiceMessage.class, + WxCpXmlMessage.class); } catch (JAXBException e) { throw new RuntimeException(e); } diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java new file mode 100644 index 00000000..00b890d9 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java @@ -0,0 +1,84 @@ +package me.chanjar.weixin.cp.api; + +import java.io.InputStream; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; + +import com.google.inject.Binder; +import com.google.inject.Module; +import org.xml.sax.InputSource; + +public class ApiTestModule implements Module { + + @Override + public void configure(Binder binder) { + try { + InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); + WxXmlCpInMemoryConfigStorage config = fromXml(WxXmlCpInMemoryConfigStorage.class, is1); + WxCpServiceImpl wxService = new WxCpServiceImpl(); + wxService.setWxCpConfigStorage(config); + + binder.bind(WxCpServiceImpl.class).toInstance(wxService); + binder.bind(WxCpConfigStorage.class).toInstance(config); + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } + + public static T fromXml(Class clazz, InputStream is) throws JAXBException { + Unmarshaller um = JAXBContext.newInstance(clazz).createUnmarshaller(); + InputSource inputSource = new InputSource(is); + inputSource.setEncoding("utf-8"); + T object = (T) um.unmarshal(inputSource); + return object; + } + + @XmlRootElement(name = "xml") + @XmlAccessorType(XmlAccessType.FIELD) + public static class WxXmlCpInMemoryConfigStorage extends WxCpInMemoryConfigStorage { + + protected String userId; + + protected String departmentId; + + protected String tagId; + + public String getUserId() { + return userId; + } + public void setUserId(String userId) { + this.userId = userId; + } + + public String getDepartmentId() { + return departmentId; + } + + public void setDepartmentId(String departmentId) { + this.departmentId = departmentId; + } + + public String getTagId() { + return tagId; + } + + public void setTagId(String tagId) { + this.tagId = tagId; + } + + @Override + public String toString() { + return super.toString() + " > WxXmlCpConfigStorage{" + + "userId='" + userId + '\'' + + ", departmentId='" + departmentId + '\'' + + ", tagId='" + tagId + '\'' + + '}'; + } + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java new file mode 100644 index 00000000..07d7d0a6 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.cp.api; + +import org.apache.commons.lang3.StringUtils; +import org.testng.Assert; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import me.chanjar.weixin.common.exception.WxErrorException; + +import com.google.inject.Inject; + +/** + * 基础API测试 + * @author Daniel Qian + * + */ +@Test(groups = "baseAPI") +@Guice(modules = ApiTestModule.class) +public class WxCpBaseAPITest { + + @Inject + protected WxCpServiceImpl wxService; + + public void testRefreshAccessToken() throws WxErrorException { + WxCpConfigStorage configStorage = wxService.wxCpConfigStorage; + String before = configStorage.getAccessToken(); + wxService.accessTokenRefresh(); + + String after = configStorage.getAccessToken(); + + Assert.assertNotEquals(before, after); + Assert.assertTrue(StringUtils.isNotBlank(after)); + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java new file mode 100644 index 00000000..28cf15b0 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java @@ -0,0 +1,63 @@ +package me.chanjar.weixin.cp.api; + +import java.util.List; + +import me.chanjar.weixin.cp.bean.WxCpDepart; +import org.testng.Assert; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import me.chanjar.weixin.common.exception.WxErrorException; + +import com.google.inject.Inject; + +/** + * 测试部门接口 + * + * @author Daniel Qian + */ +@Test(groups = "departAPI", dependsOnGroups = "baseAPI") +@Guice(modules = ApiTestModule.class) +public class WxCpDepartAPITest { + + @Inject + protected WxCpServiceImpl wxCpService; + + protected WxCpDepart depart; + + public void testDepartCreate() throws WxErrorException { + WxCpDepart depart = new WxCpDepart(); + depart.setName("子部门" + System.currentTimeMillis()); + depart.setParentId(1); + depart.setOrder(1); + Integer departId = wxCpService.departCreate(depart); + } + + @Test(dependsOnMethods = "testDepartCreate") + public void testDepartGet() throws WxErrorException { + System.out.println("=================获取部门"); + List departList = wxCpService.departGet(); + Assert.assertNotNull(departList); + Assert.assertTrue(departList.size() > 0); + for (WxCpDepart g : departList) { + depart = g; + System.out.println(depart.getId() + ":" + depart.getName()); + Assert.assertNotNull(g.getName()); + } + } + + @Test(dependsOnMethods = { "testDepartGet", "testDepartCreate" }) + public void testDepartUpdate() throws WxErrorException { + System.out.println("=================更新部门"); + depart.setName("子部门改名" + System.currentTimeMillis()); + wxCpService.departUpdate(depart); + } + + @Test(dependsOnMethods = "testDepartUpdate") + public void testDepartDelete() throws WxErrorException { + System.out.println("=================删除部门"); + System.out.println(depart.getId() + ":" + depart.getName()); + wxCpService.departDelete(depart.getId()); + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java new file mode 100644 index 00000000..336e11b7 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.cp.api; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import me.chanjar.weixin.common.exception.WxErrorException; + +import com.google.inject.Inject; + +/** + * 测试多媒体文件上传下载 + * @author Daniel Qian + * + */ +//@Test(groups="mediaAPI", dependsOnGroups="baseAPI") +@Test +@Guice(modules = ApiTestModule.class) +public class WxCpMediaAPITest { + + @Inject + protected WxCpServiceImpl wxService; + + private List media_ids = new ArrayList(); + + @Test(dataProvider="uploadMedia") + public void testUploadMedia(String mediaType, String fileType, String fileName) throws WxErrorException, IOException { + InputStream inputStream = ClassLoader.getSystemResourceAsStream(fileName); + WxMediaUploadResult res = wxService.mediaUpload(mediaType, fileType, inputStream); + Assert.assertNotNull(res.getType()); + Assert.assertNotNull(res.getCreatedAt()); + Assert.assertTrue(res.getMediaId() != null || res.getThumbMediaId() != null); + + if (res.getMediaId() != null) { + media_ids.add(res.getMediaId()); + } + if (res.getThumbMediaId() != null) { + media_ids.add(res.getThumbMediaId()); + } + } + + @DataProvider + public Object[][] uploadMedia() { + return new Object[][] { + new Object[] { WxCpConsts.MEDIA_IMAGE, WxCpConsts.FILE_JPG, "mm.jpeg" }, + new Object[] { WxCpConsts.MEDIA_VOICE, WxCpConsts.FILE_MP3, "mm.mp3" }, + new Object[] { WxCpConsts.MEDIA_VIDEO, WxCpConsts.FILE_MP4, "mm.mp4" }, + new Object[] { WxCpConsts.MEDIA_FILE, WxCpConsts.FILE_JPG, "mm.jpeg" } + }; + } + + @Test(dependsOnMethods = { "testUploadMedia" }, dataProvider="downloadMedia") + public void testDownloadMedia(String media_id) throws WxErrorException { + wxService.mediaDownload(media_id); + } + + @DataProvider + public Object[][] downloadMedia() { + Object[][] params = new Object[this.media_ids.size()][]; + for (int i = 0; i < this.media_ids.size(); i++) { + params[i] = new Object[] { this.media_ids.get(i) }; + } + return params; + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java new file mode 100644 index 00000000..01acca71 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.cp.bean.WxCpMessage; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import me.chanjar.weixin.common.exception.WxErrorException; + +import com.google.inject.Inject; + +/*** + * 测试发送消息 + * @author Daniel Qian + * + */ +@Test(groups="customMessageAPI", dependsOnGroups = "baseAPI") +@Guice(modules = ApiTestModule.class) +public class WxCpMessageAPITest { + + @Inject + protected WxCpServiceImpl wxService; + + public void testSendCustomMessage() throws WxErrorException { + ApiTestModule.WxXmlCpInMemoryConfigStorage configStorage = (ApiTestModule.WxXmlCpInMemoryConfigStorage) wxService.wxCpConfigStorage; + WxCpMessage message1 = new WxCpMessage(); + message1.setAgentId(configStorage.getAgentId()); + message1.setMsgType(WxCpConsts.CUSTOM_MSG_TEXT); + message1.setToUser(configStorage.getUserId()); + message1.setContent("欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World"); + wxService.messageSend(message1); + + WxCpMessage message2 = WxCpMessage + .TEXT() + .agentId(configStorage.getAgentId()) + .toUser(configStorage.getUserId()) + .content("欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World") + .build(); + wxService.messageSend(message2); + + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java new file mode 100644 index 00000000..6ad6f62c --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java @@ -0,0 +1,158 @@ +package me.chanjar.weixin.cp.api; + +import java.util.Map; + +import me.chanjar.weixin.cp.bean.WxCpXmlMessage; +import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +/** + * 测试消息路由器 + * @author Daniel Qian + * + */ +@Test +public class WxCpMessageRouterTest { + + @Test(enabled = false) + public void prepare(boolean async, StringBuffer sb, WxCpMessageRouter router) { + router + .rule() + .async(async) + .msgType(WxCpConsts.XML_MSG_TEXT).event(WxCpConsts.EVT_CLICK).eventKey("KEY_1").content("CONTENT_1") + .handler(new WxEchoCpMessageHandler(sb, "COMBINE_4")) + .end() + .rule() + .async(async) + .msgType(WxCpConsts.XML_MSG_TEXT).event(WxCpConsts.EVT_CLICK).eventKey("KEY_1") + .handler(new WxEchoCpMessageHandler(sb, "COMBINE_3")) + .end() + .rule() + .async(async) + .msgType(WxCpConsts.XML_MSG_TEXT).event(WxCpConsts.EVT_CLICK) + .handler(new WxEchoCpMessageHandler(sb, "COMBINE_2")) + .end() + .rule().async(async).msgType(WxCpConsts.XML_MSG_TEXT).handler(new WxEchoCpMessageHandler(sb, WxCpConsts.XML_MSG_TEXT)).end() + .rule().async(async).event(WxCpConsts.EVT_CLICK).handler(new WxEchoCpMessageHandler(sb, WxCpConsts.EVT_CLICK)).end() + .rule().async(async).eventKey("KEY_1").handler(new WxEchoCpMessageHandler(sb, "KEY_1")).end() + .rule().async(async).content("CONTENT_1").handler(new WxEchoCpMessageHandler(sb, "CONTENT_1")).end() + .rule().async(async).rContent(".*bc.*").handler(new WxEchoCpMessageHandler(sb, "abcd")).end() + .rule().async(async).handler(new WxEchoCpMessageHandler(sb, "ALL")).end(); + ; + } + + @Test(dataProvider="messages-1") + public void testSync(WxCpXmlMessage message, String expected) { + StringBuffer sb = new StringBuffer(); + WxCpMessageRouter router = new WxCpMessageRouter(); + prepare(false, sb, router); + router.route(message); + Assert.assertEquals(sb.toString(), expected); + } + + @Test(dataProvider="messages-1") + public void testAsync(WxCpXmlMessage message, String expected) throws InterruptedException { + StringBuffer sb = new StringBuffer(); + WxCpMessageRouter router = new WxCpMessageRouter(); + prepare(true, sb, router); + router.route(message); + Thread.sleep(500l); + Assert.assertEquals(sb.toString(), expected); + } + + public void testConcurrency() throws InterruptedException { + final WxCpMessageRouter router = new WxCpMessageRouter(); + router.rule().handler(new WxCpMessageHandler() { + @Override + public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context) { + return null; + } + }).end(); + + final WxCpXmlMessage m = new WxCpXmlMessage(); + Runnable r = new Runnable() { + @Override + public void run() { + router.route(m); + try { + Thread.sleep(1000l); + } catch (InterruptedException e) { + } + } + }; + for (int i = 0; i < 10; i++) { + new Thread(r).start(); + } + + Thread.sleep(1000l * 2); + } + @DataProvider(name="messages-1") + public Object[][] messages2() { + WxCpXmlMessage message1 = new WxCpXmlMessage(); + message1.setMsgType(WxCpConsts.XML_MSG_TEXT); + + WxCpXmlMessage message2 = new WxCpXmlMessage(); + message2.setEvent(WxCpConsts.EVT_CLICK); + + WxCpXmlMessage message3 = new WxCpXmlMessage(); + message3.setEventKey("KEY_1"); + + WxCpXmlMessage message4 = new WxCpXmlMessage(); + message4.setContent("CONTENT_1"); + + WxCpXmlMessage message5 = new WxCpXmlMessage(); + message5.setContent("BLA"); + + WxCpXmlMessage message6 = new WxCpXmlMessage(); + message6.setContent("abcd"); + + WxCpXmlMessage c2 = new WxCpXmlMessage(); + c2.setMsgType(WxCpConsts.XML_MSG_TEXT); + c2.setEvent(WxCpConsts.EVT_CLICK); + + WxCpXmlMessage c3 = new WxCpXmlMessage(); + c3.setMsgType(WxCpConsts.XML_MSG_TEXT); + c3.setEvent(WxCpConsts.EVT_CLICK); + c3.setEventKey("KEY_1"); + + WxCpXmlMessage c4 = new WxCpXmlMessage(); + c4.setMsgType(WxCpConsts.XML_MSG_TEXT); + c4.setEvent(WxCpConsts.EVT_CLICK); + c4.setEventKey("KEY_1"); + c4.setContent("CONTENT_1"); + + return new Object[][] { + new Object[] { message1, WxCpConsts.XML_MSG_TEXT + "," }, + new Object[] { message2, WxCpConsts.EVT_CLICK + "," }, + new Object[] { message3, "KEY_1," }, + new Object[] { message4, "CONTENT_1," }, + new Object[] { message5, "ALL," }, + new Object[] { message6, "abcd," }, + new Object[] { c2, "COMBINE_2," }, + new Object[] { c3, "COMBINE_3," }, + new Object[] { c4, "COMBINE_4," } + }; + + } + + public static class WxEchoCpMessageHandler implements WxCpMessageHandler { + + private StringBuffer sb; + private String echoStr; + + public WxEchoCpMessageHandler(StringBuffer sb, String echoStr) { + this.sb = sb; + this.echoStr = echoStr; + } + + @Override + public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context) { + sb.append(this.echoStr).append(','); + return null; + } + + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpTagAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpTagAPITest.java new file mode 100644 index 00000000..0bb7dba9 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpTagAPITest.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.cp.api; + +import com.google.inject.Inject; +import me.chanjar.weixin.cp.bean.WxCpTag; +import me.chanjar.weixin.cp.bean.WxCpUser; +import org.testng.Assert; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; + +@Test(groups = "departAPI", dependsOnGroups = "baseAPI") +@Guice(modules = ApiTestModule.class) +public class WxCpTagAPITest { + + @Inject + protected WxCpServiceImpl wxService; + + @Inject + protected WxCpConfigStorage configStorage; + + protected String tagId; + + public void testTagCreate() throws Exception { + tagId = wxService.tagCreate("测试标签4"); + System.out.println(tagId); + } + + @Test(dependsOnMethods = "testTagCreate") + public void testTagUpdate() throws Exception { + wxService.tagUpdate(tagId, "测试标签-改名"); + } + + @Test(dependsOnMethods = "testTagUpdate") + public void testTagGet() throws Exception { + List tags = wxService.tagGet(); + Assert.assertNotEquals(tags.size(), 0); + } + + @Test(dependsOnMethods = "testTagGet") + public void testTagAddUsers() throws Exception { + List userIds = new ArrayList(); + userIds.add(((ApiTestModule.WxXmlCpInMemoryConfigStorage)configStorage).getUserId()); + wxService.tagAddUsers(tagId, userIds); + } + + @Test(dependsOnMethods = "testTagAddUsers") + public void testTagGetUsers() throws Exception { + List users = wxService.tagGetUsers(tagId); + Assert.assertNotEquals(users.size(), 0); + } + + @Test(dependsOnMethods = "testTagGetUsers") + public void testTagRemoveUsers() throws Exception { + List userIds = new ArrayList(); + userIds.add(((ApiTestModule.WxXmlCpInMemoryConfigStorage)configStorage).getUserId()); + wxService.tagRemoveUsers(tagId, userIds); + } + + @Test(dependsOnMethods = "testTagRemoveUsers") + public void testTagDelete() throws Exception { + wxService.tagDelete(tagId); + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java new file mode 100644 index 00000000..9efb8a09 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.cp.api; + +import com.google.inject.Inject; +import me.chanjar.weixin.cp.bean.WxCpDepart; +import me.chanjar.weixin.cp.bean.WxCpUser; +import me.chanjar.weixin.common.exception.WxErrorException; +import org.testng.Assert; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.util.List; + +/** + * 测试用户接口 + * + * @author Daniel Qian + */ +@Test(groups = "userAPI", dependsOnGroups = "baseAPI") +@Guice(modules = ApiTestModule.class) +public class WxCpUserAPITest { + + @Inject + protected WxCpServiceImpl wxCpService; + + protected WxCpDepart depart; + + public void testUserCreate() throws WxErrorException { + WxCpUser user = new WxCpUser(); + user.setUserId("xiaohe.yang"); + user.setName("杨宝"); + user.setDepartIds(new Integer[] { 9, 8 }); + user.setEmail("yangxiaohe@ddd.com"); + user.setGender("女"); + user.setMobile("13564684979"); + user.setPosition("老婆"); + user.setTel("3300393"); + user.addExtAttr("爱好", "老公"); + wxCpService.userCreate(user); + } + + @Test(dependsOnMethods = "testUserCreate") + public void testUserUpdate() throws WxErrorException { + WxCpUser user = new WxCpUser(); + user.setUserId("xiaohe.yang"); + user.setName("杨宝"); + user.addExtAttr("爱好", "老公2"); + wxCpService.userUpdate(user); + } + + @Test(dependsOnMethods = "testUserUpdate") + public void testUserGet() throws WxErrorException { + WxCpUser user = wxCpService.userGet("xiaohe.yang"); + Assert.assertNotNull(user); + } + + @Test(dependsOnMethods = "testUserGet") + public void testDepartGetUsers() throws WxErrorException { + List users = wxCpService.departGetUsers(1, true, 0); + Assert.assertNotEquals(users.size(), 0); + } + + @Test(dependsOnMethods = "testDepartGetUsers") + public void testUserDelete() throws WxErrorException { + wxCpService.userDelete("xiaohe.yang"); + } +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java new file mode 100644 index 00000000..ebb292e2 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java @@ -0,0 +1,91 @@ +package me.chanjar.weixin.cp.api; + +import javax.xml.bind.JAXBException; + +import me.chanjar.weixin.common.bean.WxMenu; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import com.google.inject.Inject; + +import me.chanjar.weixin.common.bean.WxMenu.WxMenuButton; +import me.chanjar.weixin.common.exception.WxErrorException; + +/** + * 测试菜单 + * @author Daniel Qian + * + */ +@Test(groups="menuAPI", dependsOnGroups="baseAPI") +@Guice(modules = ApiTestModule.class) +public class WxMenuAPITest { + + @Inject + protected WxCpServiceImpl wxService; + + @Test(dataProvider = "menu") + public void testCreateMenu(WxMenu wxMenu) throws WxErrorException { + wxService.menuCreate(wxMenu); + } + + @Test(dependsOnMethods = { "testCreateMenu"}) + public void testGetMenu() throws WxErrorException { + Assert.assertNotNull(wxService.menuGet()); + } + + @Test(dependsOnMethods = { "testGetMenu"}) + public void testDeleteMenu() throws WxErrorException { + wxService.menuDelete(); + } + + @DataProvider(name="menu") + public Object[][] getMenu() throws JAXBException { + WxMenu menu = new WxMenu(); + WxMenuButton button1 = new WxMenuButton(); + button1.setType(WxCpConsts.BUTTON_CLICK); + button1.setName("今日歌曲"); + button1.setKey("V1001_TODAY_MUSIC"); + + WxMenuButton button2 = new WxMenuButton(); + button2.setType(WxCpConsts.BUTTON_CLICK); + button2.setName("歌手简介"); + button2.setKey("V1001_TODAY_SINGER"); + + WxMenuButton button3 = new WxMenuButton(); + button3.setName("菜单"); + + menu.getButtons().add(button1); + menu.getButtons().add(button2); + menu.getButtons().add(button3); + + WxMenuButton button31 = new WxMenuButton(); + button31.setType(WxCpConsts.BUTTON_VIEW); + button31.setName("搜索"); + button31.setUrl("http://www.soso.com/"); + + WxMenuButton button32 = new WxMenuButton(); + button32.setType(WxCpConsts.BUTTON_VIEW); + button32.setName("视频"); + button32.setUrl("http://v.qq.com/"); + + WxMenuButton button33 = new WxMenuButton(); + button33.setType(WxCpConsts.BUTTON_CLICK); + button33.setName("赞一下我们"); + button33.setKey("V1001_GOOD"); + + button3.getSubButtons().add(button31); + button3.getSubButtons().add(button32); + button3.getSubButtons().add(button33); + + return new Object[][] { + new Object[] { + menu + } + }; + + } + + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java new file mode 100644 index 00000000..56fca755 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java @@ -0,0 +1,108 @@ +package me.chanjar.weixin.cp.bean; + +import me.chanjar.weixin.cp.api.WxCpConsts; +import org.testng.Assert; +import org.testng.annotations.Test; + +import me.chanjar.weixin.cp.bean.WxCpMessage.WxArticle; + +@Test +public class WxCpMessageTest { + + public void testTextReply() { + WxCpMessage reply = new WxCpMessage(); + reply.setToUser("OPENID"); + reply.setMsgType(WxCpConsts.CUSTOM_MSG_TEXT); + reply.setContent("sfsfdsdf"); + Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"sfsfdsdf\"}}"); + } + + public void testTextBuild() { + WxCpMessage reply = WxCpMessage.TEXT().toUser("OPENID").content("sfsfdsdf").build(); + Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"sfsfdsdf\"}}"); + } + + public void testImageReply() { + WxCpMessage reply = new WxCpMessage(); + reply.setToUser("OPENID"); + reply.setMsgType(WxCpConsts.CUSTOM_MSG_IMAGE); + reply.setMediaId("MEDIA_ID"); + Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"image\",\"image\":{\"media_id\":\"MEDIA_ID\"}}"); + } + + public void testImageBuild() { + WxCpMessage reply = WxCpMessage.IMAGE().toUser("OPENID").mediaId("MEDIA_ID").build(); + Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"image\",\"image\":{\"media_id\":\"MEDIA_ID\"}}"); + } + + public void testVoiceReply() { + WxCpMessage reply = new WxCpMessage(); + reply.setToUser("OPENID"); + reply.setMsgType(WxCpConsts.CUSTOM_MSG_VOICE); + reply.setMediaId("MEDIA_ID"); + Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"voice\",\"voice\":{\"media_id\":\"MEDIA_ID\"}}"); + } + + public void testVoiceBuild() { + WxCpMessage reply = WxCpMessage.VOICE().toUser("OPENID").mediaId("MEDIA_ID").build(); + Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"voice\",\"voice\":{\"media_id\":\"MEDIA_ID\"}}"); + } + + public void testVideoReply() { + WxCpMessage reply = new WxCpMessage(); + reply.setToUser("OPENID"); + reply.setMsgType(WxCpConsts.CUSTOM_MSG_VIDEO); + reply.setMediaId("MEDIA_ID"); + reply.setThumbMediaId("MEDIA_ID"); + reply.setTitle("TITLE"); + reply.setDescription("DESCRIPTION"); + Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"video\",\"video\":{\"media_id\":\"MEDIA_ID\",\"thumb_media_id\":\"MEDIA_ID\",\"title\":\"TITLE\",\"description\":\"DESCRIPTION\"}}"); + } + + public void testVideoBuild() { + WxCpMessage reply = WxCpMessage.VIDEO().toUser("OPENID").title("TITLE").mediaId("MEDIA_ID").thumbMediaId("MEDIA_ID").description("DESCRIPTION").build(); + Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"video\",\"video\":{\"media_id\":\"MEDIA_ID\",\"thumb_media_id\":\"MEDIA_ID\",\"title\":\"TITLE\",\"description\":\"DESCRIPTION\"}}"); + } + + public void testNewsReply() { + WxCpMessage reply = new WxCpMessage(); + reply.setToUser("OPENID"); + reply.setMsgType(WxCpConsts.CUSTOM_MSG_NEWS); + + WxArticle article1 = new WxArticle(); + article1.setUrl("URL"); + article1.setPicUrl("PIC_URL"); + article1.setDescription("Is Really A Happy Day"); + article1.setTitle("Happy Day"); + reply.getArticles().add(article1); + + WxArticle article2 = new WxArticle(); + article2.setUrl("URL"); + article2.setPicUrl("PIC_URL"); + article2.setDescription("Is Really A Happy Day"); + article2.setTitle("Happy Day"); + reply.getArticles().add(article2); + + + Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"articles\":[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"},{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}"); + } + + public void testNewsBuild() { + WxArticle article1 = new WxArticle(); + article1.setUrl("URL"); + article1.setPicUrl("PIC_URL"); + article1.setDescription("Is Really A Happy Day"); + article1.setTitle("Happy Day"); + + WxArticle article2 = new WxArticle(); + article2.setUrl("URL"); + article2.setPicUrl("PIC_URL"); + article2.setDescription("Is Really A Happy Day"); + article2.setTitle("Happy Day"); + + WxCpMessage reply = WxCpMessage.NEWS().toUser("OPENID").addArticle(article1).addArticle(article2).build(); + + Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"articles\":[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"},{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}"); + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java new file mode 100644 index 00000000..d356126f --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java @@ -0,0 +1,91 @@ +package me.chanjar.weixin.cp.bean; + +import me.chanjar.weixin.cp.api.WxCpConsts; +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test +public class WxCpXmlMessageTest { + + public void testFromXml() { + + String xml = "" + + "" + + " " + + "1348831860" + + "" + + "" + + "1234567890123456" + + "" + + "" + + "" + + "" + + "23.134521" + + "113.358803" + + "20" + + "" + + "" + + "" + + "<![CDATA[公众平台官网链接]]>" + + "" + + "" + + "" + + "23.137466" + + "113.352425" + + "119.385040" + + "" + + " " + + " " + + "" + + "" + + " 1\n" + + " " + + " " + + " " + + " " + + " " + + "" + + "" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "" + + ""; + WxCpXmlMessage wxMessage = WxCpXmlMessage.fromXml(xml); + Assert.assertEquals(wxMessage.getToUserName(), "toUser"); + Assert.assertEquals(wxMessage.getFromUserName(), "fromUser"); + Assert.assertEquals(wxMessage.getCreateTime(), new Long(1348831860l)); + Assert.assertEquals(wxMessage.getMsgType(), WxCpConsts.XML_MSG_TEXT); + Assert.assertEquals(wxMessage.getContent(), "this is a test"); + Assert.assertEquals(wxMessage.getMsgId(), new Long(1234567890123456l)); + Assert.assertEquals(wxMessage.getPicUrl(), "this is a url"); + Assert.assertEquals(wxMessage.getMediaId(), "media_id"); + Assert.assertEquals(wxMessage.getFormat(), "Format"); + Assert.assertEquals(wxMessage.getThumbMediaId(), "thumb_media_id"); + Assert.assertEquals(wxMessage.getLocationX(), new Double(23.134521d)); + Assert.assertEquals(wxMessage.getLocationY(), new Double(113.358803d)); + Assert.assertEquals(wxMessage.getScale(), new Double(20)); + Assert.assertEquals(wxMessage.getLabel(), "位置信息"); + Assert.assertEquals(wxMessage.getDescription(), "公众平台官网链接"); + Assert.assertEquals(wxMessage.getUrl(), "url"); + Assert.assertEquals(wxMessage.getTitle(), "公众平台官网链接"); + Assert.assertEquals(wxMessage.getEvent(), "subscribe"); + Assert.assertEquals(wxMessage.getEventKey(), "qrscene_123123"); + Assert.assertEquals(wxMessage.getTicket(), "TICKET"); + Assert.assertEquals(wxMessage.getLatitude(), new Double(23.137466)); + Assert.assertEquals(wxMessage.getLongitude(), new Double(113.352425)); + Assert.assertEquals(wxMessage.getPrecision(), new Double(119.385040)); + Assert.assertEquals(wxMessage.getScanCodeInfo().getScanType(), "qrcode"); + Assert.assertEquals(wxMessage.getScanCodeInfo().getScanResult(), "1"); + Assert.assertEquals(wxMessage.getSendPicsInfo().getCount(), new Long(1l)); + Assert.assertEquals(wxMessage.getSendPicsInfo().getPicList().get(0).getPicMd5Sum(), "1b5f7c23b5bf75682a53e7b6d163e185"); + Assert.assertEquals(wxMessage.getSendLocationInfo().getLocationX(), "23"); + Assert.assertEquals(wxMessage.getSendLocationInfo().getLocationY(), "113"); + Assert.assertEquals(wxMessage.getSendLocationInfo().getScale(), "15"); + Assert.assertEquals(wxMessage.getSendLocationInfo().getLabel(), " 广州市海珠区客村艺苑路 106号"); + Assert.assertEquals(wxMessage.getSendLocationInfo().getPoiname(), "wo de poi"); + } + +} diff --git a/src/test/java/me/chanjar/weixin/bean/WxXmlOutImageMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessageTest.java similarity index 85% rename from src/test/java/me/chanjar/weixin/bean/WxXmlOutImageMessageTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessageTest.java index bbb5ba6e..9f97e4b8 100644 --- a/src/test/java/me/chanjar/weixin/bean/WxXmlOutImageMessageTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessageTest.java @@ -1,13 +1,13 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.cp.bean; import org.testng.Assert; import org.testng.annotations.Test; @Test -public class WxXmlOutImageMessageTest { +public class WxCpXmlOutImageMessageTest { public void test() { - WxXmlOutImageMessage m = new WxXmlOutImageMessage(); + WxCpXmlOutImageMessage m = new WxCpXmlOutImageMessage(); m.setMediaId("ddfefesfsdfef"); m.setCreateTime(1122l); m.setFromUserName("from"); @@ -26,7 +26,7 @@ public class WxXmlOutImageMessageTest { } public void testBuild() { - WxXmlOutImageMessage m = WxXmlOutMessage.IMAGE().mediaId("ddfefesfsdfef").fromUser("from").toUser("to").build(); + WxCpXmlOutImageMessage m = WxCpXmlOutMessage.IMAGE().mediaId("ddfefesfsdfef").fromUser("from").toUser("to").build(); String expected = "" + "" + "" diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessageTest.java new file mode 100644 index 00000000..f72c76b3 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessageTest.java @@ -0,0 +1,95 @@ +package me.chanjar.weixin.cp.bean; + +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test +public class WxCpXmlOutNewsMessageTest { + + public void test() { + WxCpXmlOutMewsMessage m = new WxCpXmlOutMewsMessage(); + m.setCreateTime(1122l); + m.setFromUserName("fromUser"); + m.setToUserName("toUser"); + + WxCpXmlOutMewsMessage.Item item = new WxCpXmlOutMewsMessage.Item(); + item.setDescription("description"); + item.setPicUrl("picUrl"); + item.setTitle("title"); + item.setUrl("url"); + m.addArticle(item); + m.addArticle(item); + String expected = "" + + "" + + "" + + "" + + "1122" + + "" + + " 2" + + " " + + " " + + " <![CDATA[title]]>" + + " " + + " " + + " " + + " " + + " " + + " <![CDATA[title]]>" + + " " + + " " + + " " + + " " + + " " + + ""; + System.out.println(m.toXml()); + Assert.assertEquals(m.toXml().replaceAll("\\s", ""), expected.replaceAll("\\s", "")); + } + + public void testBuild() { + WxCpXmlOutMewsMessage.Item item = new WxCpXmlOutMewsMessage.Item(); + item.setDescription("description"); + item.setPicUrl("picUrl"); + item.setTitle("title"); + item.setUrl("url"); + + WxCpXmlOutMewsMessage m = WxCpXmlOutMessage.NEWS() + .fromUser("fromUser") + .toUser("toUser") + .addArticle(item) + .addArticle(item) + .build(); + String expected = "" + + "" + + "" + + "" + + "1122" + + "" + + " 2" + + " " + + " " + + " <![CDATA[title]]>" + + " " + + " " + + " " + + " " + + " " + + " <![CDATA[title]]>" + + " " + + " " + + " " + + " " + + " " + + ""; + System.out.println(m.toXml()); + Assert.assertEquals( + m + .toXml() + .replaceAll("\\s", "") + .replaceAll(".*?", ""), + expected + .replaceAll("\\s", "") + .replaceAll(".*?", "") + ); + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessageTest.java new file mode 100644 index 00000000..4dce4f16 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessageTest.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.cp.bean; + +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test +public class WxCpXmlOutTextMessageTest { + + public void test() { + WxCpXmlOutTextMessage m = new WxCpXmlOutTextMessage(); + m.setContent("content"); + m.setCreateTime(1122l); + m.setFromUserName("from"); + m.setToUserName("to"); + + String expected = "" + + "" + + "" + + "" + + "1122" + + "" + + "" + + ""; + System.out.println(m.toXml()); + Assert.assertEquals(m.toXml().replaceAll("\\s", ""), expected.replaceAll("\\s", "")); + } + + public void testBuild() { + WxCpXmlOutTextMessage m = WxCpXmlOutMessage.TEXT().content("content").fromUser("from").toUser("to").build(); + String expected = "" + + "" + + "" + + "" + + "1122" + + "" + + "" + + ""; + System.out.println(m.toXml()); + Assert.assertEquals( + m + .toXml() + .replaceAll("\\s", "") + .replaceAll(".*?", ""), + expected + .replaceAll("\\s", "") + .replaceAll(".*?", "") + ); + + } + + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessageTest.java new file mode 100644 index 00000000..b5ba2456 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessageTest.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.cp.bean; + +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test +public class WxCpXmlOutVideoMessageTest { + + public void test() { + WxCpXmlOutVideoMessage m = new WxCpXmlOutVideoMessage(); + m.setMediaId("media_id"); + m.setTitle("title"); + m.setDescription("ddfff"); + m.setCreateTime(1122l); + m.setFromUserName("fromUser"); + m.setToUserName("toUser"); + + String expected = "" + + "" + + "" + + "" + + "1122" + + "" + + " " + + ""; + System.out.println(m.toXml()); + Assert.assertEquals(m.toXml().replaceAll("\\s", ""), expected.replaceAll("\\s", "")); + } + + public void testBuild() { + WxCpXmlOutVideoMessage m = WxCpXmlOutMessage.VIDEO() + .mediaId("media_id") + .fromUser("fromUser") + .toUser("toUser") + .title("title") + .description("ddfff") + .build(); + String expected = "" + + "" + + "" + + "" + + "1122" + + "" + + " " + + ""; + System.out.println(m.toXml()); + Assert.assertEquals( + m + .toXml() + .replaceAll("\\s", "") + .replaceAll(".*?", ""), + expected + .replaceAll("\\s", "") + .replaceAll(".*?", "") + ); + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessageTest.java new file mode 100644 index 00000000..fa73a329 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessageTest.java @@ -0,0 +1,50 @@ +package me.chanjar.weixin.cp.bean; + +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test +public class WxCpXmlOutVoiceMessageTest { + + public void test() { + WxCpXmlOutVoiceMessage m = new WxCpXmlOutVoiceMessage(); + m.setMediaId("ddfefesfsdfef"); + m.setCreateTime(1122l); + m.setFromUserName("from"); + m.setToUserName("to"); + + String expected = "" + + "" + + "" + + "" + + "1122" + + "" + + "" + + ""; + System.out.println(m.toXml()); + Assert.assertEquals(m.toXml().replaceAll("\\s", ""), expected.replaceAll("\\s", "")); + } + + public void testBuild() { + WxCpXmlOutVoiceMessage m = WxCpXmlOutMessage.VOICE().mediaId("ddfefesfsdfef").fromUser("from").toUser("to").build(); + String expected = "" + + "" + + "" + + "" + + "1122" + + "" + + "" + + ""; + System.out.println(m.toXml()); + Assert.assertEquals( + m + .toXml() + .replaceAll("\\s", "") + .replaceAll(".*?", ""), + expected + .replaceAll("\\s", "") + .replaceAll(".*?", "") + ); + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java new file mode 100644 index 00000000..0c0d3961 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java @@ -0,0 +1,35 @@ +package me.chanjar.weixin.cp.demo; + +import me.chanjar.weixin.cp.api.WxCpInMemoryConfigStorage; +import org.xml.sax.InputSource; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.InputStream; + +/** + * @author Daniel Qian + */ +@XmlRootElement(name = "xml") +@XmlAccessorType(XmlAccessType.FIELD) +class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage { + + @Override + public String toString() { + return "SimpleWxConfigProvider [appidOrCorpid=" + corpId + ", corpSecret=" + corpSecret + ", accessToken=" + accessToken + + ", expiresIn=" + expiresIn + ", token=" + token + ", aesKey=" + aesKey + "]"; + } + + + public static WxCpDemoInMemoryConfigStorage fromXml(InputStream is) throws JAXBException { + Unmarshaller um = JAXBContext.newInstance(WxCpDemoInMemoryConfigStorage.class).createUnmarshaller(); + InputSource inputSource = new InputSource(is); + inputSource.setEncoding("utf-8"); + return (WxCpDemoInMemoryConfigStorage) um.unmarshal(inputSource); + } + +} diff --git a/src/test/java/me/chanjar/weixin/demo/WxTestServer.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java similarity index 73% rename from src/test/java/me/chanjar/weixin/demo/WxTestServer.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java index e9c6d9d0..b56d1780 100644 --- a/src/test/java/me/chanjar/weixin/demo/WxTestServer.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.demo; +package me.chanjar.weixin.cp.demo; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; @@ -6,14 +6,14 @@ import org.eclipse.jetty.servlet.ServletHandler; /** * @author Daniel Qian */ -public class WxTestServer { +public class WxCpDemoServer { public static void main(String[] args) throws Exception { Server server = new Server(8080); ServletHandler handler = new ServletHandler(); server.setHandler(handler); - handler.addServletWithMapping(WxTestServlet.class, "/*"); + handler.addServletWithMapping(WxCpDemoServlet.class, "/*"); server.start(); server.join(); } diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServlet.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServlet.java new file mode 100644 index 00000000..b84d786f --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServlet.java @@ -0,0 +1,99 @@ +package me.chanjar.weixin.cp.demo; + +import me.chanjar.weixin.cp.api.*; +import me.chanjar.weixin.cp.bean.WxCpXmlMessage; +import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; +import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage; +import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; +import org.apache.commons.lang3.StringUtils; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.bind.JAXBException; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +/** + * @author Daniel Qian + */ +public class WxCpDemoServlet extends HttpServlet { + + protected WxCpService wxCpService; + protected WxCpConfigStorage wxCpConfigStorage; + protected WxCpMessageRouter wxCpMessageRouter; + + @Override public void init() throws ServletException { + // + super.init(); + try { + InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); + WxCpDemoInMemoryConfigStorage config = WxCpDemoInMemoryConfigStorage.fromXml(is1); + + wxCpConfigStorage = config; + wxCpService = new WxCpServiceImpl(); + wxCpService.setWxCpConfigStorage(config); + + WxCpMessageHandler handler = new WxCpMessageHandler() { + @Override public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context) { + WxCpXmlOutTextMessage m = WxCpXmlOutMessage + .TEXT() + .content("测试加密消息") + .fromUser(wxMessage.getToUserName()) + .toUser(wxMessage.getFromUserName()) + .build(); + return m; + } + }; + + wxCpMessageRouter = new WxCpMessageRouter(); + wxCpMessageRouter + .rule() + .async(false) + .content("哈哈") // 拦截内容为“哈哈”的消息 + .handler(handler) + .end(); + + } catch (JAXBException e) { + throw new RuntimeException(e); + } + } + + @Override protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + String msgSignature = request.getParameter("msg_signature"); + String nonce = request.getParameter("nonce"); + String timestamp = request.getParameter("timestamp"); + String echostr = request.getParameter("echostr"); + + response.setContentType("text/html;charset=utf-8"); + response.setStatus(HttpServletResponse.SC_OK); + if (StringUtils.isNotBlank(echostr)) { + if (!wxCpService.checkSignature(msgSignature, timestamp, nonce, echostr)) { + // 消息签名不正确,说明不是公众平台发过来的消息 + response.getWriter().println("非法请求"); + return; + } + WxCpCryptUtil cryptUtil = new WxCpCryptUtil(wxCpConfigStorage); + String plainText = cryptUtil.decrypt(echostr); + // 说明是一个仅仅用来验证的请求,回显echostr + response.getWriter().println(plainText); + return; + } + + + WxCpXmlMessage inMessage = WxCpXmlMessage.fromEncryptedXml(request.getInputStream(), wxCpConfigStorage, timestamp, nonce, msgSignature); + + WxCpXmlOutMessage outMessage = wxCpMessageRouter.route(inMessage); + + if (outMessage != null) { + response.getWriter().write(outMessage.toEncryptedXml(wxCpConfigStorage)); + } + + return; + } + +} diff --git a/src/test/resources/mm.jpeg b/weixin-java-cp/src/test/resources/mm.jpeg similarity index 100% rename from src/test/resources/mm.jpeg rename to weixin-java-cp/src/test/resources/mm.jpeg diff --git a/src/test/resources/mm.mp3 b/weixin-java-cp/src/test/resources/mm.mp3 similarity index 100% rename from src/test/resources/mm.mp3 rename to weixin-java-cp/src/test/resources/mm.mp3 diff --git a/src/test/resources/mm.mp4 b/weixin-java-cp/src/test/resources/mm.mp4 similarity index 100% rename from src/test/resources/mm.mp4 rename to weixin-java-cp/src/test/resources/mm.mp4 diff --git a/weixin-java-cp/src/test/resources/test-config.sample.xml b/weixin-java-cp/src/test/resources/test-config.sample.xml new file mode 100644 index 00000000..c30d7e35 --- /dev/null +++ b/weixin-java-cp/src/test/resources/test-config.sample.xml @@ -0,0 +1,12 @@ + + 企业号corpid + 企业号corpsecret + 企业号应用id + 企业号应用Token + 企业号应用EncodingAESKey + 可以不填写 + 可以不填写 + 企业号通讯录里的某个userid + 企业号通讯录的某个部门id + 企业号通讯录里的某个tagid + diff --git a/weixin-java-cp/src/test/resources/testng.xml b/weixin-java-cp/src/test/resources/testng.xml new file mode 100644 index 00000000..4cddddcf --- /dev/null +++ b/weixin-java-cp/src/test/resources/testng.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/weixin-java-mp/LICENSE b/weixin-java-mp/LICENSE new file mode 100644 index 00000000..5c304d1a --- /dev/null +++ b/weixin-java-mp/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml new file mode 100644 index 00000000..c2477c3f --- /dev/null +++ b/weixin-java-mp/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + me.chanjar + weixin-java-parent + 1.0.3-SNAPSHOT + + weixin-java-mp + WeiXin Java Tools - MP + 微信公众号Java SDK + + + + me.chanjar + weixin-java-common + ${project.version} + + + junit + junit + test + + + org.testng + testng + 6.8.7 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + com.google.inject + guice + 3.0 + test + + + org.eclipse.jetty + jetty-server + 9.3.0.M0 + test + + + org.eclipse.jetty + jetty-servlet + 9.3.0.M0 + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/resources/testng.xml + + + + + + + diff --git a/src/main/java/me/chanjar/weixin/api/WxConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java similarity index 76% rename from src/main/java/me/chanjar/weixin/api/WxConfigStorage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java index 57bdcd3b..e90013ff 100644 --- a/src/main/java/me/chanjar/weixin/api/WxConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java @@ -1,13 +1,13 @@ -package me.chanjar.weixin.api; +package me.chanjar.weixin.mp.api; -import me.chanjar.weixin.bean.WxAccessToken; +import me.chanjar.weixin.common.bean.WxAccessToken; /** * 微信客户端配置存储 * @author chanjarster * */ -public interface WxConfigStorage { +public interface WxMpConfigStorage { public void updateAccessToken(WxAccessToken accessToken); diff --git a/src/main/java/me/chanjar/weixin/api/WxInMemoryConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java similarity index 90% rename from src/main/java/me/chanjar/weixin/api/WxInMemoryConfigStorage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java index b4a734c5..0218067b 100644 --- a/src/main/java/me/chanjar/weixin/api/WxInMemoryConfigStorage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java @@ -1,13 +1,13 @@ -package me.chanjar.weixin.api; +package me.chanjar.weixin.mp.api; -import me.chanjar.weixin.bean.WxAccessToken; +import me.chanjar.weixin.common.bean.WxAccessToken; /** * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 * @author chanjarster * */ -public class WxInMemoryConfigStorage implements WxConfigStorage { +public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { protected String appId; protected String secret; diff --git a/src/main/java/me/chanjar/weixin/api/WxMessageHandler.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageHandler.java similarity index 56% rename from src/main/java/me/chanjar/weixin/api/WxMessageHandler.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageHandler.java index b433f957..e80ff5a1 100644 --- a/src/main/java/me/chanjar/weixin/api/WxMessageHandler.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageHandler.java @@ -1,16 +1,16 @@ -package me.chanjar.weixin.api; +package me.chanjar.weixin.mp.api; -import java.util.Map; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; -import me.chanjar.weixin.bean.WxXmlMessage; -import me.chanjar.weixin.bean.WxXmlOutMessage; +import java.util.Map; /** * 处理微信推送消息的处理器接口 * @author chanjarster * */ -public interface WxMessageHandler { +public interface WxMpMessageHandler { /** * @@ -18,6 +18,6 @@ public interface WxMessageHandler { * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 * @return xml格式的消息,如果在异步规则里处理的话,可以返回null */ - public WxXmlOutMessage handle(WxXmlMessage wxMessage, Map context); + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context); } diff --git a/src/main/java/me/chanjar/weixin/api/WxMessageInterceptor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageInterceptor.java similarity index 60% rename from src/main/java/me/chanjar/weixin/api/WxMessageInterceptor.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageInterceptor.java index ff4e659c..901defea 100644 --- a/src/main/java/me/chanjar/weixin/api/WxMessageInterceptor.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageInterceptor.java @@ -1,16 +1,15 @@ -package me.chanjar.weixin.api; +package me.chanjar.weixin.mp.api; -import java.util.Map; - -import me.chanjar.weixin.bean.WxXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import java.util.Map; /** * 微信消息拦截器,可以用来做验证 * @author qianjia * */ -public interface WxMessageInterceptor { +public interface WxMpMessageInterceptor { /** * 拦截微信消息 @@ -18,6 +17,6 @@ public interface WxMessageInterceptor { * @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 * @return true代表OK,false代表不OK */ - public boolean intercept(WxXmlMessage wxMessage, Map context); + public boolean intercept(WxMpXmlMessage wxMessage, Map context); } diff --git a/src/main/java/me/chanjar/weixin/api/WxMessageRouter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java similarity index 79% rename from src/main/java/me/chanjar/weixin/api/WxMessageRouter.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java index 2a651596..f49cafe6 100644 --- a/src/main/java/me/chanjar/weixin/api/WxMessageRouter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java @@ -1,4 +1,7 @@ -package me.chanjar.weixin.api; +package me.chanjar.weixin.mp.api; + +import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; import java.util.ArrayList; import java.util.HashMap; @@ -8,9 +11,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Pattern; -import me.chanjar.weixin.bean.WxXmlMessage; -import me.chanjar.weixin.bean.WxXmlOutMessage; - /** *
  * 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理
@@ -21,7 +21,7 @@ import me.chanjar.weixin.bean.WxXmlOutMessage;
  * 3. 规则的结束必须用{@link Rule#end()}或者{@link Rule#next()},否则不会生效
  * 
  * 使用方法:
- * WxMessageRouter router = new WxMessageRouter();
+ * WxMpMessageRouter router = new WxMpMessageRouter();
  * router
  *   .rule()
  *       .msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT")
@@ -39,7 +39,7 @@ import me.chanjar.weixin.bean.WxXmlOutMessage;
  * @author qianjia
  *
  */
-public class WxMessageRouter {
+public class WxMpMessageRouter {
   
   private final List rules = new ArrayList();
 
@@ -57,7 +57,7 @@ public class WxMessageRouter {
    * 处理微信消息
    * @param wxMessage
    */
-  public WxXmlOutMessage route(final WxXmlMessage wxMessage) {
+  public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) {
     final List matchRules = new ArrayList();
     // 收集匹配的规则
     for (final Rule rule : rules) {
@@ -86,7 +86,7 @@ public class WxMessageRouter {
       return null;
     }
     
-    WxXmlOutMessage res = null;
+    WxMpXmlOutMessage res = null;
     for (final Rule rule : matchRules) {
       // 返回最后一个匹配规则的结果
       res = rule.service(wxMessage);
@@ -99,7 +99,7 @@ public class WxMessageRouter {
   
   public static class Rule {
     
-    private final WxMessageRouter routerBuilder;
+    private final WxMpMessageRouter routerBuilder;
 
     private boolean async = true;
     
@@ -115,11 +115,11 @@ public class WxMessageRouter {
     
     private boolean reEnter = false;
     
-    private List handlers = new ArrayList();
+    private List handlers = new ArrayList();
     
-    private List interceptors = new ArrayList();
+    private List interceptors = new ArrayList();
     
-    protected Rule(WxMessageRouter routerBuilder) {
+    protected Rule(WxMpMessageRouter routerBuilder) {
       this.routerBuilder = routerBuilder;
     }
     
@@ -188,8 +188,8 @@ public class WxMessageRouter {
      * @param interceptor
      * @return
      */
-    public Rule interceptor(WxMessageInterceptor interceptor) {
-      return interceptor(interceptor, (WxMessageInterceptor[]) null);
+    public Rule interceptor(WxMpMessageInterceptor interceptor) {
+      return interceptor(interceptor, (WxMpMessageInterceptor[]) null);
     }
     
     /**
@@ -198,10 +198,10 @@ public class WxMessageRouter {
      * @param otherInterceptors
      * @return
      */
-    public Rule interceptor(WxMessageInterceptor interceptor, WxMessageInterceptor... otherInterceptors) {
+    public Rule interceptor(WxMpMessageInterceptor interceptor, WxMpMessageInterceptor... otherInterceptors) {
       this.interceptors.add(interceptor);
       if (otherInterceptors != null && otherInterceptors.length > 0) {
-        for (WxMessageInterceptor i : otherInterceptors) {
+        for (WxMpMessageInterceptor i : otherInterceptors) {
           this.interceptors.add(i);
         }
       }
@@ -213,8 +213,8 @@ public class WxMessageRouter {
      * @param handler
      * @return
      */
-    public Rule handler(WxMessageHandler handler) {
-      return handler(handler, (WxMessageHandler[]) null);
+    public Rule handler(WxMpMessageHandler handler) {
+      return handler(handler, (WxMpMessageHandler[]) null);
     }
     
     /**
@@ -223,10 +223,10 @@ public class WxMessageRouter {
      * @param otherHandlers
      * @return
      */
-    public Rule handler(WxMessageHandler handler, WxMessageHandler... otherHandlers) {
+    public Rule handler(WxMpMessageHandler handler, WxMpMessageHandler... otherHandlers) {
       this.handlers.add(handler);
       if (otherHandlers != null && otherHandlers.length > 0) {
-        for (WxMessageHandler i : otherHandlers) {
+        for (WxMpMessageHandler i : otherHandlers) {
           this.handlers.add(i);
         }
       }
@@ -237,7 +237,7 @@ public class WxMessageRouter {
      * 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则
      * @return
      */
-    public WxMessageRouter end() {
+    public WxMpMessageRouter end() {
       this.routerBuilder.rules.add(this);
       return this.routerBuilder;
     }
@@ -246,12 +246,12 @@ public class WxMessageRouter {
      * 规则结束,但是消息还会进入其他规则
      * @return
      */
-    public WxMessageRouter next() {
+    public WxMpMessageRouter next() {
       this.reEnter = true;
       return end();
     }
     
-    protected boolean test(WxXmlMessage wxMessage) {
+    protected boolean test(WxMpXmlMessage wxMessage) {
       return 
           (this.msgType == null || this.msgType.equals(wxMessage.getMsgType()))
           &&
@@ -270,18 +270,18 @@ public class WxMessageRouter {
      * @param wxMessage
      * @return true 代表继续执行别的router,false 代表停止执行别的router
      */
-    protected WxXmlOutMessage service(WxXmlMessage wxMessage) {
+    protected WxMpXmlOutMessage service(WxMpXmlMessage wxMessage) {
       Map context = new HashMap();
       // 如果拦截器不通过
-      for (WxMessageInterceptor interceptor : this.interceptors) {
+      for (WxMpMessageInterceptor interceptor : this.interceptors) {
         if (!interceptor.intercept(wxMessage, context)) {
           return null;
         }
       }
       
       // 交给handler处理
-      WxXmlOutMessage res = null;
-      for (WxMessageHandler handler : this.handlers) {
+      WxMpXmlOutMessage res = null;
+      for (WxMpMessageHandler handler : this.handlers) {
         // 返回最后handler的结果
         res = handler.handle(wxMessage, context);
       }
diff --git a/src/main/java/me/chanjar/weixin/api/WxService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
similarity index 77%
rename from src/main/java/me/chanjar/weixin/api/WxService.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
index c5db88bc..4b3a20fc 100644
--- a/src/main/java/me/chanjar/weixin/api/WxService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
@@ -1,29 +1,20 @@
-package me.chanjar.weixin.api;
+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.mp.bean.*;
+import me.chanjar.weixin.mp.bean.result.*;
+import me.chanjar.weixin.common.exception.WxErrorException;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.List;
 
-import me.chanjar.weixin.bean.WxCustomMessage;
-import me.chanjar.weixin.bean.WxGroup;
-import me.chanjar.weixin.bean.WxMassGroupMessage;
-import me.chanjar.weixin.bean.WxMassNews;
-import me.chanjar.weixin.bean.WxMassOpenIdsMessage;
-import me.chanjar.weixin.bean.WxMassVideo;
-import me.chanjar.weixin.bean.WxMenu;
-import me.chanjar.weixin.bean.result.WxMassSendResult;
-import me.chanjar.weixin.bean.result.WxMassUploadResult;
-import me.chanjar.weixin.bean.result.WxMediaUploadResult;
-import me.chanjar.weixin.bean.result.WxQrCodeTicket;
-import me.chanjar.weixin.bean.result.WxUser;
-import me.chanjar.weixin.bean.result.WxUserList;
-import me.chanjar.weixin.exception.WxErrorException;
-
 /**
  * 微信API的Service
  */
-public interface WxService {
+public interface WxMpService {
   
   /**
    * 
@@ -48,7 +39,7 @@ public interface WxService {
 
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
    * 
- * @throws WxErrorException + * @throws me.chanjar.weixin.common.exception.WxErrorException */ public void accessTokenRefresh() throws WxErrorException; @@ -64,8 +55,8 @@ public interface WxService { * * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 *
- * @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 */ @@ -99,7 +90,7 @@ public interface WxService { * @param message * @throws WxErrorException */ - public void customMessageSend(WxCustomMessage message) throws WxErrorException; + public void customMessageSend(WxMpCustomMessage message) throws WxErrorException; /** *
@@ -109,10 +100,10 @@ public interface WxService {
    * 
* @param news * @throws WxErrorException - * @see #massGroupMessageSend(WxMassGroupMessage) - * @see #massOpenIdsMessageSend(WxMassOpenIdsMessage) + * @see #massGroupMessageSend(me.chanjar.weixin.mp.bean.WxMpMassGroupMessage) + * @see #massOpenIdsMessageSend(me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage) */ - public WxMassUploadResult massNewsUpload(WxMassNews news) throws WxErrorException; + public WxMpMassUploadResult massNewsUpload(WxMpMassNews news) throws WxErrorException; /** *
@@ -121,36 +112,36 @@ public interface WxService {
    * 
* @return * @throws WxErrorException - * @see #massGroupMessageSend(WxMassGroupMessage) - * @see #massOpenIdsMessageSend(WxMassOpenIdsMessage) + * @see #massGroupMessageSend(me.chanjar.weixin.mp.bean.WxMpMassGroupMessage) + * @see #massOpenIdsMessageSend(me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage) */ - public WxMassUploadResult massVideoUpload(WxMassVideo video) throws WxErrorException; + public WxMpMassUploadResult massVideoUpload(WxMpMassVideo video) throws WxErrorException; /** *
    * 分组群发消息
-   * 如果发送图文消息,必须先使用 {@link #massNewsUpload(WxMassNews)} 获得media_id,然后再发送
-   * 如果发送视频消息,必须先使用 {@link #massVideoUpload(WxMassVideo)} 获得media_id,然后再发送
+   * 如果发送图文消息,必须先使用 {@link #massNewsUpload(me.chanjar.weixin.mp.bean.WxMpMassNews)} 获得media_id,然后再发送
+   * 如果发送视频消息,必须先使用 {@link #massVideoUpload(me.chanjar.weixin.mp.bean.WxMpMassVideo)} 获得media_id,然后再发送
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口
    * 
* @param message * @throws WxErrorException * @return */ - public WxMassSendResult massGroupMessageSend(WxMassGroupMessage message) throws WxErrorException; + public WxMpMassSendResult massGroupMessageSend(WxMpMassGroupMessage message) throws WxErrorException; /** *
    * 按openId列表群发消息
-   * 如果发送图文消息,必须先使用 {@link #massNewsUpload(WxMassNews)} 获得media_id,然后再发送
-   * 如果发送视频消息,必须先使用 {@link #massVideoUpload(WxMassVideo)} 获得media_id,然后再发送
+   * 如果发送图文消息,必须先使用 {@link #massNewsUpload(me.chanjar.weixin.mp.bean.WxMpMassNews)} 获得media_id,然后再发送
+   * 如果发送视频消息,必须先使用 {@link #massVideoUpload(me.chanjar.weixin.mp.bean.WxMpMassVideo)} 获得media_id,然后再发送
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口
    * 
* @param message * @return * @throws WxErrorException */ - public WxMassSendResult massOpenIdsMessageSend(WxMassOpenIdsMessage message) throws WxErrorException; + public WxMpMassSendResult massOpenIdsMessageSend(WxMpMassOpenIdsMessage message) throws WxErrorException; /** *
@@ -190,7 +181,7 @@ public interface WxService {
    * @param name 分组名字(30个字符以内) 
    * @throws WxErrorException
    */
-  public WxGroup groupCreate(String name) throws WxErrorException;
+  public WxMpGroup groupCreate(String name) throws WxErrorException;
   
   /**
    * 
@@ -200,7 +191,7 @@ public interface WxService {
    * @return
    * @throws WxErrorException
    */
-  public List groupGet() throws WxErrorException;
+  public List groupGet() throws WxErrorException;
   
   /**
    * 
@@ -222,7 +213,7 @@ public interface WxService {
    * @param group 要更新的group,group的id,name必须设置 
    * @throws WxErrorException
    */
-  public void groupUpdate(WxGroup group) throws WxErrorException;
+  public void groupUpdate(WxMpGroup group) throws WxErrorException;
   
   /**
    * 
@@ -258,7 +249,7 @@ public interface WxService {
    * @return
    * @throws WxErrorException
    */
-  public WxUser userInfo(String openid, String lang) throws WxErrorException;
+  public WxMpUser userInfo(String openid, String lang) throws WxErrorException;
 
   /**
    * 
@@ -269,7 +260,7 @@ public interface WxService {
    * @return
    * @throws WxErrorException
    */
-  public WxUserList userList(String next_openid) throws WxErrorException;
+  public WxMpUserList userList(String next_openid) throws WxErrorException;
 
   /**
    * 
@@ -281,7 +272,7 @@ public interface WxService {
    * @return
    * @throws WxErrorException
    */
-  public WxQrCodeTicket qrCodeCreateTmpTicket(int scene_id, Integer expire_seconds) throws WxErrorException;
+  public WxMpQrCodeTicket qrCodeCreateTmpTicket(int scene_id, Integer expire_seconds) throws WxErrorException;
 
   /**
    * 
@@ -292,7 +283,7 @@ public interface WxService {
    * @return
    * @throws WxErrorException
    */
-  public WxQrCodeTicket qrCodeCreateLastTicket(int scene_id) throws WxErrorException;
+  public WxMpQrCodeTicket qrCodeCreateLastTicket(int scene_id) throws WxErrorException;
 
   /**
    * 
@@ -303,7 +294,7 @@ public interface WxService {
    * @return
    * @throws WxErrorException
    */
-  public File qrCodePicture(WxQrCodeTicket ticket) throws WxErrorException;
+  public File qrCodePicture(WxMpQrCodeTicket ticket) throws WxErrorException;
 
   /**
    * 
@@ -317,8 +308,8 @@ public interface WxService {
   public String shortUrl(String long_url) throws WxErrorException;
   
   /**
-   * 注入 {@link WxConfigStorage} 的实现
+   * 注入 {@link WxMpConfigStorage} 的实现
    * @param wxConfigProvider
    */
-  public void setWxConfigStorage(WxConfigStorage wxConfigProvider);
+  public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider);
 }
diff --git a/src/main/java/me/chanjar/weixin/api/WxServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
similarity index 76%
rename from src/main/java/me/chanjar/weixin/api/WxServiceImpl.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
index 28fc3e7e..c40af33d 100644
--- a/src/main/java/me/chanjar/weixin/api/WxServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
@@ -1,16 +1,23 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.StringReader;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import me.chanjar.weixin.bean.WxCustomMessage;
-import me.chanjar.weixin.util.crypto.SHA1;
-import me.chanjar.weixin.util.json.GsonHelper;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+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.WxMenu;
+import me.chanjar.weixin.common.bean.result.WxError;
+import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.common.exception.WxErrorException;
+import me.chanjar.weixin.common.util.crypto.SHA1;
+import me.chanjar.weixin.common.util.fs.FileUtils;
+import me.chanjar.weixin.common.util.http.*;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+import me.chanjar.weixin.mp.bean.*;
+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.client.ClientProtocolException;
 import org.apache.http.client.methods.CloseableHttpResponse;
@@ -19,37 +26,15 @@ import org.apache.http.impl.client.BasicResponseHandler;
 import org.apache.http.impl.client.CloseableHttpClient;
 import org.apache.http.impl.client.HttpClients;
 
-import me.chanjar.weixin.bean.WxAccessToken;
-import me.chanjar.weixin.bean.WxGroup;
-import me.chanjar.weixin.bean.WxMassGroupMessage;
-import me.chanjar.weixin.bean.WxMassNews;
-import me.chanjar.weixin.bean.WxMassOpenIdsMessage;
-import me.chanjar.weixin.bean.WxMassVideo;
-import me.chanjar.weixin.bean.WxMenu;
-import me.chanjar.weixin.bean.result.WxError;
-import me.chanjar.weixin.bean.result.WxMassSendResult;
-import me.chanjar.weixin.bean.result.WxMassUploadResult;
-import me.chanjar.weixin.bean.result.WxMediaUploadResult;
-import me.chanjar.weixin.bean.result.WxQrCodeTicket;
-import me.chanjar.weixin.bean.result.WxUser;
-import me.chanjar.weixin.bean.result.WxUserList;
-import me.chanjar.weixin.exception.WxErrorException;
-import me.chanjar.weixin.util.fs.FileUtils;
-import me.chanjar.weixin.util.http.MediaDownloadRequestExecutor;
-import me.chanjar.weixin.util.http.MediaUploadRequestExecutor;
-import me.chanjar.weixin.util.http.QrCodeRequestExecutor;
-import me.chanjar.weixin.util.http.RequestExecutor;
-import me.chanjar.weixin.util.http.SimpleGetRequestExecutor;
-import me.chanjar.weixin.util.http.SimplePostRequestExecutor;
-import me.chanjar.weixin.util.json.WxGsonBuilder;
-
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.internal.Streams;
-import com.google.gson.reflect.TypeToken;
-import com.google.gson.stream.JsonReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.List;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
 
-public class WxServiceImpl implements WxService {
+public class WxMpServiceImpl implements WxMpService {
 
   /**
    * 全局的是否正在刷新Access Token的flag
@@ -60,13 +45,13 @@ public class WxServiceImpl implements WxService {
   
   protected static final CloseableHttpClient httpclient = HttpClients.createDefault();
   
-  protected WxConfigStorage wxConfigStorage;
+  protected WxMpConfigStorage wxMpConfigStorage;
   
   protected final ThreadLocal retryTimes = new ThreadLocal();
 
   public boolean checkSignature(String timestamp, String nonce, String signature) {
     try {
-      return SHA1.gen(wxConfigStorage.getToken(), timestamp, nonce).equals(signature);
+      return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature);
     } catch (Exception e) {
       return false;
     }
@@ -76,8 +61,8 @@ public class WxServiceImpl implements WxService {
     if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) {
       try {
         String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
-            + "&appid=" + wxConfigStorage.getAppId() 
-            + "&secret=" + wxConfigStorage.getSecret()
+            + "&appid=" + wxMpConfigStorage.getAppId()
+            + "&secret=" + wxMpConfigStorage.getSecret()
             ;
         try {
           HttpGet httpGet = new HttpGet(url);
@@ -88,7 +73,7 @@ public class WxServiceImpl implements WxService {
             throw new WxErrorException(error);
           }
           WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
-          wxConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
+          wxMpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
         } catch (ClientProtocolException e) {
           throw new RuntimeException(e);
         } catch (IOException e) {
@@ -109,7 +94,7 @@ public class WxServiceImpl implements WxService {
     }
   }
   
-  public void customMessageSend(WxCustomMessage message) throws WxErrorException {
+  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());
   }
@@ -152,31 +137,31 @@ public class WxServiceImpl implements WxService {
     return execute(new MediaDownloadRequestExecutor(), url, "media_id=" + media_id);
   }
 
-  public WxMassUploadResult massNewsUpload(WxMassNews news) throws WxErrorException {
+  public WxMpMassUploadResult massNewsUpload(WxMpMassNews news) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/media/uploadnews";
     String responseContent = execute(new SimplePostRequestExecutor(), url, news.toJson());
-    return WxMassUploadResult.fromJson(responseContent);
+    return WxMpMassUploadResult.fromJson(responseContent);
   }
   
-  public WxMassUploadResult massVideoUpload(WxMassVideo video) throws WxErrorException {
+  public WxMpMassUploadResult massVideoUpload(WxMpMassVideo video) throws WxErrorException {
     String url = "http://file.api.weixin.qq.com/cgi-bin/media/uploadvideo";
     String responseContent = execute(new SimplePostRequestExecutor(), url, video.toJson());
-    return WxMassUploadResult.fromJson(responseContent);
+    return WxMpMassUploadResult.fromJson(responseContent);
   }
   
-  public WxMassSendResult massGroupMessageSend(WxMassGroupMessage message) throws WxErrorException {
+  public WxMpMassSendResult massGroupMessageSend(WxMpMassGroupMessage message) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall";
     String responseContent = execute(new SimplePostRequestExecutor(), url, message.toJson());
-    return WxMassSendResult.fromJson(responseContent);
+    return WxMpMassSendResult.fromJson(responseContent);
   }
 
-  public WxMassSendResult massOpenIdsMessageSend(WxMassOpenIdsMessage message) throws WxErrorException {
+  public WxMpMassSendResult massOpenIdsMessageSend(WxMpMassOpenIdsMessage message) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/message/mass/send";
     String responseContent = execute(new SimplePostRequestExecutor(), url, message.toJson());
-    return WxMassSendResult.fromJson(responseContent);
+    return WxMpMassSendResult.fromJson(responseContent);
   }
   
-  public WxGroup groupCreate(String name) throws WxErrorException {
+  public WxMpGroup groupCreate(String name) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/groups/create";
     JsonObject json = new JsonObject();
     JsonObject groupJson = new JsonObject();
@@ -187,10 +172,10 @@ public class WxServiceImpl implements WxService {
         new SimplePostRequestExecutor(), 
         url, 
         json.toString());
-    return WxGroup.fromJson(responseContent);
+    return WxMpGroup.fromJson(responseContent);
   }
 
-  public List groupGet() throws WxErrorException {
+  public List groupGet() throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/groups/get";
     String responseContent = execute(new SimpleGetRequestExecutor(), url, null);
     /*
@@ -198,7 +183,7 @@ public class WxServiceImpl implements WxService {
      * 查询时返回的是 { groups : [ { id : ..., name : ..., count : ... }, ... ] }
      */
     JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent)));
-    return WxGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement.getAsJsonObject().get("groups"), new TypeToken>(){}.getType());
+    return WxMpGsonBuilder.INSTANCE.create().fromJson(tmpJsonElement.getAsJsonObject().get("groups"), new TypeToken>(){}.getType());
   }
   
   public long userGetGroup(String openid) throws WxErrorException {
@@ -210,7 +195,7 @@ public class WxServiceImpl implements WxService {
     return GsonHelper.getAsLong(tmpJsonElement.getAsJsonObject().get("groupid"));
   }
   
-  public void groupUpdate(WxGroup group) throws WxErrorException {
+  public void groupUpdate(WxMpGroup group) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/groups/update";
     execute(new SimplePostRequestExecutor(), url, group.toJson());
   }
@@ -231,20 +216,20 @@ public class WxServiceImpl implements WxService {
     execute(new SimplePostRequestExecutor(), url, json.toString());
   }
   
-  public WxUser userInfo(String openid, String lang) throws WxErrorException {
+  public WxMpUser userInfo(String openid, String lang) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/user/info";
     lang = lang == null ? "zh_CN" : lang;
     String responseContent = execute(new SimpleGetRequestExecutor(), url, "openid=" + openid + "&lang=" + lang);
-    return WxUser.fromJson(responseContent);
+    return WxMpUser.fromJson(responseContent);
   }
   
-  public WxUserList userList(String next_openid) throws WxErrorException {
+  public WxMpUserList userList(String next_openid) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/user/get";
     String responseContent = execute(new SimpleGetRequestExecutor(), url, next_openid == null ? null : "next_openid=" + next_openid);
-    return WxUserList.fromJson(responseContent);
+    return WxMpUserList.fromJson(responseContent);
   }
   
-  public WxQrCodeTicket qrCodeCreateTmpTicket(int scene_id, Integer expire_seconds) throws WxErrorException {
+  public WxMpQrCodeTicket qrCodeCreateTmpTicket(int scene_id, Integer expire_seconds) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create";
     JsonObject json = new JsonObject();
     json.addProperty("action_name", "QR_SCENE");
@@ -257,10 +242,10 @@ public class WxServiceImpl implements WxService {
     actionInfo.add("scene", scene);
     json.add("action_info", actionInfo);
     String responseContent = execute(new SimplePostRequestExecutor(), url, json.toString());
-    return WxQrCodeTicket.fromJson(responseContent);
+    return WxMpQrCodeTicket.fromJson(responseContent);
   }
   
-  public WxQrCodeTicket qrCodeCreateLastTicket(int scene_id) throws WxErrorException {
+  public WxMpQrCodeTicket qrCodeCreateLastTicket(int scene_id) throws WxErrorException {
     String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create";
     JsonObject json = new JsonObject();
     json.addProperty("action_name", "QR_LIMIT_SCENE");
@@ -270,10 +255,10 @@ public class WxServiceImpl implements WxService {
     actionInfo.add("scene", scene);
     json.add("action_info", actionInfo);
     String responseContent = execute(new SimplePostRequestExecutor(), url, json.toString());
-    return WxQrCodeTicket.fromJson(responseContent);
+    return WxMpQrCodeTicket.fromJson(responseContent);
   }
   
-  public File qrCodePicture(WxQrCodeTicket ticket) throws WxErrorException {
+  public File qrCodePicture(WxMpQrCodeTicket ticket) throws WxErrorException {
     String url = "https://mp.weixin.qq.com/cgi-bin/showqrcode";
     return execute(new QrCodeRequestExecutor(), url, ticket);
   }
@@ -297,10 +282,10 @@ public class WxServiceImpl implements WxService {
    * @throws WxErrorException
    */
   public  T execute(RequestExecutor executor, String uri, E data) throws WxErrorException {
-    if (StringUtils.isBlank(wxConfigStorage.getAccessToken())) {
+    if (StringUtils.isBlank(wxMpConfigStorage.getAccessToken())) {
       accessTokenRefresh();
     }
-    String accessToken = wxConfigStorage.getAccessToken();
+    String accessToken = wxMpConfigStorage.getAccessToken();
     
     String uriWithAccessToken = uri;
     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
@@ -350,8 +335,8 @@ public class WxServiceImpl implements WxService {
     }
   }
   
-  public void setWxConfigStorage(WxConfigStorage wxConfigProvider) {
-    this.wxConfigStorage = wxConfigProvider;
+  public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) {
+    this.wxMpConfigStorage = wxConfigProvider;
   }
 
 }
diff --git a/src/main/java/me/chanjar/weixin/bean/WxCustomMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpCustomMessage.java
similarity index 81%
rename from src/main/java/me/chanjar/weixin/bean/WxCustomMessage.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpCustomMessage.java
index eea1113b..dc3d3baa 100644
--- a/src/main/java/me/chanjar/weixin/bean/WxCustomMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpCustomMessage.java
@@ -1,23 +1,17 @@
-package me.chanjar.weixin.bean;
+package me.chanjar.weixin.mp.bean;
+
+import me.chanjar.weixin.mp.bean.custombuilder.*;
+import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import me.chanjar.weixin.api.WxConsts;
-import me.chanjar.weixin.bean.custombuilder.ImageBuilder;
-import me.chanjar.weixin.bean.custombuilder.MusicBuilder;
-import me.chanjar.weixin.bean.custombuilder.NewsBuilder;
-import me.chanjar.weixin.bean.custombuilder.TextBuilder;
-import me.chanjar.weixin.bean.custombuilder.VideoBuilder;
-import me.chanjar.weixin.bean.custombuilder.VoiceBuilder;
-import me.chanjar.weixin.util.json.WxGsonBuilder;
-
 /**
  * 客服消息
  * @author chanjarster
  *
  */
-public class WxCustomMessage {
+public class WxMpCustomMessage {
 
   private String toUser;
   private String msgType;
@@ -43,12 +37,12 @@ public class WxCustomMessage {
   /**
    * 
    * 请使用
-   * {@link WxConsts#CUSTOM_MSG_TEXT}
-   * {@link WxConsts#CUSTOM_MSG_IMAGE}
-   * {@link WxConsts#CUSTOM_MSG_VOICE}
-   * {@link WxConsts#CUSTOM_MSG_MUSIC}
-   * {@link WxConsts#CUSTOM_MSG_VIDEO}
-   * {@link WxConsts#CUSTOM_MSG_NEWS}
+   * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_TEXT}
+   * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_IMAGE}
+   * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_VOICE}
+   * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_MUSIC}
+   * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_VIDEO}
+   * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_NEWS}
    * 
* @param msgType */ @@ -105,7 +99,7 @@ public class WxCustomMessage { } public String toJson() { - return WxGsonBuilder.INSTANCE.create().toJson(this); + return WxMpGsonBuilder.INSTANCE.create().toJson(this); } public static class WxArticle { diff --git a/src/main/java/me/chanjar/weixin/bean/WxGroup.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpGroup.java similarity index 59% rename from src/main/java/me/chanjar/weixin/bean/WxGroup.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpGroup.java index 0174f562..cfa5fcb6 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxGroup.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpGroup.java @@ -1,13 +1,13 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; -import me.chanjar.weixin.util.json.WxGsonBuilder; +import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; /** * 微信用户分组 * @author chanjarster * */ -public class WxGroup { +public class WxMpGroup { private long id = -1; private String name; @@ -31,16 +31,16 @@ public class WxGroup { this.count = count; } - public static WxGroup fromJson(String json) { - return WxGsonBuilder.create().fromJson(json, WxGroup.class); + public static WxMpGroup fromJson(String json) { + return WxMpGsonBuilder.create().fromJson(json, WxMpGroup.class); } public String toJson() { - return WxGsonBuilder.create().toJson(this); + return WxMpGsonBuilder.create().toJson(this); } @Override public String toString() { - return "WxGroup [id=" + id + ", name=" + name + ", count=" + count + "]"; + return "WxMpGroup [id=" + id + ", name=" + name + ", count=" + count + "]"; } } diff --git a/src/main/java/me/chanjar/weixin/bean/WxMassGroupMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassGroupMessage.java similarity index 62% rename from src/main/java/me/chanjar/weixin/bean/WxMassGroupMessage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassGroupMessage.java index 32bd6c90..c8e859c0 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxMassGroupMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassGroupMessage.java @@ -1,21 +1,20 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.util.json.WxGsonBuilder; +import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; /** * 分组群发的消息 * * @author chanjarster */ -public class WxMassGroupMessage { +public class WxMpMassGroupMessage { private long groupId; private String msgtype; private String content; private String mediaId; - public WxMassGroupMessage() { + public WxMpMassGroupMessage() { super(); } @@ -26,11 +25,11 @@ public class WxMassGroupMessage { /** *
    * 请使用
-   * {@link WxConsts#MASS_MSG_IMAGE}
-   * {@link WxConsts#MASS_MSG_NEWS}
-   * {@link WxConsts#MASS_MSG_TEXT}
-   * {@link WxConsts#MASS_MSG_VIDEO}
-   * {@link WxConsts#MASS_MSG_VOICE}
+   * {@link me.chanjar.weixin.common.api.WxConsts#MASS_MSG_IMAGE}
+   * {@link me.chanjar.weixin.common.api.WxConsts#MASS_MSG_NEWS}
+   * {@link me.chanjar.weixin.common.api.WxConsts#MASS_MSG_TEXT}
+   * {@link me.chanjar.weixin.common.api.WxConsts#MASS_MSG_VIDEO}
+   * {@link me.chanjar.weixin.common.api.WxConsts#MASS_MSG_VOICE}
    * 如果msgtype和media_id不匹配的话,会返回系统繁忙的错误
    * 
* @param msgtype @@ -56,7 +55,7 @@ public class WxMassGroupMessage { } public String toJson() { - return WxGsonBuilder.INSTANCE.create().toJson(this); + return WxMpGsonBuilder.INSTANCE.create().toJson(this); } public long getGroupId() { diff --git a/src/main/java/me/chanjar/weixin/bean/WxMassNews.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassNews.java similarity index 86% rename from src/main/java/me/chanjar/weixin/bean/WxMassNews.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassNews.java index af136a83..f3c67d5a 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxMassNews.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassNews.java @@ -1,29 +1,29 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; + +import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; import java.util.ArrayList; import java.util.List; -import me.chanjar.weixin.util.json.WxGsonBuilder; - /** * 群发时用到的图文消息素材 * @author chanjarster * */ -public class WxMassNews { +public class WxMpMassNews { - private List articles = new ArrayList(); + private List articles = new ArrayList(); - public List getArticles() { + public List getArticles() { return articles; } - public void addArticle(WxMassNewsArticle article) { + public void addArticle(WxMpMassNewsArticle article) { this.articles.add(article); } public String toJson() { - return WxGsonBuilder.INSTANCE.create().toJson(this); + return WxMpGsonBuilder.INSTANCE.create().toJson(this); } /** @@ -40,7 +40,7 @@ public class WxMassNews { * @author chanjarster * */ - public static class WxMassNewsArticle { + public static class WxMpMassNewsArticle { /** * (必填) 图文消息缩略图的media_id,可以在基础支持-上传多媒体文件接口中获得 */ diff --git a/src/main/java/me/chanjar/weixin/bean/WxMassOpenIdsMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java similarity index 67% rename from src/main/java/me/chanjar/weixin/bean/WxMassOpenIdsMessage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java index 56397afb..b542162b 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxMassOpenIdsMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassOpenIdsMessage.java @@ -1,23 +1,23 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; + +import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; import java.util.ArrayList; import java.util.List; -import me.chanjar.weixin.util.json.WxGsonBuilder; - /** * OpenId列表群发的消息 * * @author chanjarster */ -public class WxMassOpenIdsMessage { +public class WxMpMassOpenIdsMessage { private List toUsers = new ArrayList(); private String msgType; private String content; private String mediaId; - public WxMassOpenIdsMessage() { + public WxMpMassOpenIdsMessage() { super(); } @@ -28,11 +28,11 @@ public class WxMassOpenIdsMessage { /** *
    * 请使用
-   * {@link me.chanjar.weixin.api.WxConsts#MASS_MSG_IMAGE}
-   * {@link me.chanjar.weixin.api.WxConsts#MASS_MSG_NEWS}
-   * {@link me.chanjar.weixin.api.WxConsts#MASS_MSG_TEXT}
-   * {@link me.chanjar.weixin.api.WxConsts#MASS_MSG_VIDEO}
-   * {@link me.chanjar.weixin.api.WxConsts#MASS_MSG_VOICE}
+   * {@link me.chanjar.weixin.common.api.WxConsts#MASS_MSG_IMAGE}
+   * {@link me.chanjar.weixin.common.api.WxConsts#MASS_MSG_NEWS}
+   * {@link me.chanjar.weixin.common.api.WxConsts#MASS_MSG_TEXT}
+   * {@link me.chanjar.weixin.common.api.WxConsts#MASS_MSG_VIDEO}
+   * {@link me.chanjar.weixin.common.api.WxConsts#MASS_MSG_VOICE}
    * 如果msgtype和media_id不匹配的话,会返回系统繁忙的错误
    * 
* @param msgType @@ -58,7 +58,7 @@ public class WxMassOpenIdsMessage { } public String toJson() { - return WxGsonBuilder.INSTANCE.create().toJson(this); + return WxMpGsonBuilder.INSTANCE.create().toJson(this); } /** diff --git a/src/main/java/me/chanjar/weixin/bean/WxMassVideo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassVideo.java similarity index 76% rename from src/main/java/me/chanjar/weixin/bean/WxMassVideo.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassVideo.java index 47d73683..3dc6c5d7 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxMassVideo.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMassVideo.java @@ -1,13 +1,13 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; -import me.chanjar.weixin.util.json.WxGsonBuilder; +import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; /** * 群发时用到的视频素材 * * @author chanjarster */ -public class WxMassVideo { +public class WxMpMassVideo { private String mediaId; private String title; @@ -38,6 +38,6 @@ public class WxMassVideo { } public String toJson() { - return WxGsonBuilder.INSTANCE.create().toJson(this); + return WxMpGsonBuilder.INSTANCE.create().toJson(this); } } diff --git a/src/main/java/me/chanjar/weixin/bean/WxXmlOutImageMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMpXmlOutImageMessage.java similarity index 71% rename from src/main/java/me/chanjar/weixin/bean/WxXmlOutImageMessage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMpXmlOutImageMessage.java index 59ac5394..1db47004 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxXmlOutImageMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMpXmlOutImageMessage.java @@ -1,4 +1,7 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.util.xml.MediaIdMarshaller; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -6,18 +9,15 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.util.xml.MediaIdMarshaller; - @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) -public class WxXmlOutImageMessage extends WxXmlOutMessage { +public class WxMpMpXmlOutImageMessage extends WxMpXmlOutMessage { @XmlElement(name="Image") @XmlJavaTypeAdapter(MediaIdMarshaller.class) private String mediaId; - public WxXmlOutImageMessage() { + public WxMpMpXmlOutImageMessage() { this.msgType = WxConsts.XML_MSG_IMAGE; } diff --git a/src/main/java/me/chanjar/weixin/bean/WxXmlMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlMessage.java similarity index 84% rename from src/main/java/me/chanjar/weixin/bean/WxXmlMessage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlMessage.java index aa70047d..8ed9e567 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxXmlMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlMessage.java @@ -1,10 +1,9 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; -import me.chanjar.weixin.api.WxConfigStorage; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.util.crypto.WxCryptUtil; -import me.chanjar.weixin.util.xml.AdapterCDATA; -import me.chanjar.weixin.util.xml.XmlTransformer; +import me.chanjar.weixin.common.util.xml.AdapterCDATA; +import me.chanjar.weixin.mp.api.WxMpConfigStorage; +import me.chanjar.weixin.mp.util.crypto.WxMpCryptUtil; +import me.chanjar.weixin.mp.util.xml.XmlTransformer; import org.apache.commons.io.IOUtils; import javax.xml.bind.JAXBException; @@ -28,7 +27,7 @@ import java.util.List; */ @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) -public class WxXmlMessage { +public class WxMpXmlMessage { /////////////////////// // 以下都是微信推送过来的消息的xml的element所对应的属性 @@ -180,13 +179,13 @@ public class WxXmlMessage { /** *
    * 当接受用户消息时,可能会获得以下值:
-   * {@link WxConsts#XML_MSG_TEXT}
-   * {@link WxConsts#XML_MSG_IMAGE}
-   * {@link WxConsts#XML_MSG_VOICE}
-   * {@link WxConsts#XML_MSG_VIDEO}
-   * {@link WxConsts#XML_MSG_LOCATION}
-   * {@link WxConsts#XML_MSG_LINK}
-   * {@link WxConsts#XML_MSG_EVENT}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_TEXT}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_IMAGE}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_VOICE}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_VIDEO}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_LOCATION}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_LINK}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_EVENT}
    * 
* * @return @@ -198,12 +197,12 @@ public class WxXmlMessage { /** *
    * 当发送消息的时候使用:
-   * {@link WxConsts#XML_MSG_TEXT}
-   * {@link WxConsts#XML_MSG_IMAGE}
-   * {@link WxConsts#XML_MSG_VOICE}
-   * {@link WxConsts#XML_MSG_VIDEO}
-   * {@link WxConsts#XML_MSG_NEWS}
-   * {@link WxConsts#XML_MSG_MUSIC}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_TEXT}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_IMAGE}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_VOICE}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_VIDEO}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_NEWS}
+   * {@link me.chanjar.weixin.common.api.WxConsts#XML_MSG_MUSIC}
    * 
* * @param msgType @@ -380,17 +379,17 @@ public class WxXmlMessage { this.fromUserName = fromUserName; } - public static WxXmlMessage fromXml(String xml) { + public static WxMpXmlMessage fromXml(String xml) { try { - return XmlTransformer.fromXml(WxXmlMessage.class, xml); + return XmlTransformer.fromXml(WxMpXmlMessage.class, xml); } catch (JAXBException e) { throw new RuntimeException(e); } } - public static WxXmlMessage fromXml(InputStream is) { + public static WxMpXmlMessage fromXml(InputStream is) { try { - return XmlTransformer.fromXml(WxXmlMessage.class, is); + return XmlTransformer.fromXml(WxMpXmlMessage.class, is); } catch (JAXBException e) { throw new RuntimeException(e); } @@ -400,27 +399,27 @@ public class WxXmlMessage { * 从加密字符串转换 * * @param encryptedXml - * @param wxConfigStorage + * @param wxMpConfigStorage * @param timestamp * @param nonce * @param msgSignature * @return */ - public static WxXmlMessage fromEncryptedXml( + public static WxMpXmlMessage fromEncryptedXml( String encryptedXml, - WxConfigStorage wxConfigStorage, + WxMpConfigStorage wxMpConfigStorage, String timestamp, String nonce, String msgSignature) { - WxCryptUtil cryptUtil = new WxCryptUtil(wxConfigStorage); + WxMpCryptUtil cryptUtil = new WxMpCryptUtil(wxMpConfigStorage); String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, encryptedXml); return fromXml(plainText); } - public static WxXmlMessage fromEncryptedXml( + public static WxMpXmlMessage fromEncryptedXml( InputStream is, - WxConfigStorage wxConfigStorage, + WxMpConfigStorage wxMpConfigStorage, String timestamp, String nonce, String msgSignature) { try { - return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxConfigStorage, timestamp, nonce, msgSignature); + return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxMpConfigStorage, timestamp, nonce, msgSignature); } catch (IOException e) { throw new RuntimeException(e); } @@ -466,27 +465,27 @@ public class WxXmlMessage { this.errorCount = errorCount; } - public WxXmlMessage.ScanCodeInfo getScanCodeInfo() { + public WxMpXmlMessage.ScanCodeInfo getScanCodeInfo() { return scanCodeInfo; } - public void setScanCodeInfo(WxXmlMessage.ScanCodeInfo scanCodeInfo) { + public void setScanCodeInfo(WxMpXmlMessage.ScanCodeInfo scanCodeInfo) { this.scanCodeInfo = scanCodeInfo; } - public WxXmlMessage.SendPicsInfo getSendPicsInfo() { + public WxMpXmlMessage.SendPicsInfo getSendPicsInfo() { return sendPicsInfo; } - public void setSendPicsInfo(WxXmlMessage.SendPicsInfo sendPicsInfo) { + public void setSendPicsInfo(WxMpXmlMessage.SendPicsInfo sendPicsInfo) { this.sendPicsInfo = sendPicsInfo; } - public WxXmlMessage.SendLocationInfo getSendLocationInfo() { + public WxMpXmlMessage.SendLocationInfo getSendLocationInfo() { return sendLocationInfo; } - public void setSendLocationInfo(WxXmlMessage.SendLocationInfo sendLocationInfo) { + public void setSendLocationInfo(WxMpXmlMessage.SendLocationInfo sendLocationInfo) { this.sendLocationInfo = sendLocationInfo; } @@ -554,7 +553,7 @@ public class WxXmlMessage { @XmlRootElement(name = "item") @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "me.chanjar.weixin.bean.WxXmlMessage.SendPicsInfo.Item") + @XmlType(name = "WxXmlMessage.SendPicsInfo.Item") public static class Item { @XmlElement(name = "PicMd5Sum") diff --git a/src/main/java/me/chanjar/weixin/bean/WxXmlOutMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMessage.java similarity index 77% rename from src/main/java/me/chanjar/weixin/bean/WxXmlOutMessage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMessage.java index 7a03d29e..b9e4507d 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxXmlOutMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMessage.java @@ -1,4 +1,10 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; + +import me.chanjar.weixin.common.util.xml.AdapterCDATA; +import me.chanjar.weixin.mp.api.WxMpConfigStorage; +import me.chanjar.weixin.mp.bean.outxmlbuilder.*; +import me.chanjar.weixin.mp.util.crypto.WxMpCryptUtil; +import me.chanjar.weixin.mp.util.xml.XmlTransformer; import javax.xml.bind.JAXBException; import javax.xml.bind.annotation.XmlAccessType; @@ -7,20 +13,9 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import me.chanjar.weixin.api.WxConfigStorage; -import me.chanjar.weixin.bean.outxmlbuilder.ImageBuilder; -import me.chanjar.weixin.bean.outxmlbuilder.MusicBuilder; -import me.chanjar.weixin.bean.outxmlbuilder.NewsBuilder; -import me.chanjar.weixin.bean.outxmlbuilder.TextBuilder; -import me.chanjar.weixin.bean.outxmlbuilder.VideoBuilder; -import me.chanjar.weixin.bean.outxmlbuilder.VoiceBuilder; -import me.chanjar.weixin.util.crypto.WxCryptUtil; -import me.chanjar.weixin.util.xml.AdapterCDATA; -import me.chanjar.weixin.util.xml.XmlTransformer; - @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) -public class WxXmlOutMessage { +public class WxMpXmlOutMessage { @XmlElement(name="ToUserName") @XmlJavaTypeAdapter(AdapterCDATA.class) @@ -81,9 +76,9 @@ public class WxXmlOutMessage { * 转换成加密的xml格式 * @return */ - public String toEncryptedXml(WxConfigStorage wxConfigStorage) { + public String toEncryptedXml(WxMpConfigStorage wxMpConfigStorage) { String plainXml = toXml(); - WxCryptUtil pc = new WxCryptUtil(wxConfigStorage); + WxMpCryptUtil pc = new WxMpCryptUtil(wxMpConfigStorage); return pc.encrypt(plainXml); } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMewsMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMewsMessage.java new file mode 100644 index 00000000..527dc6a7 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMewsMessage.java @@ -0,0 +1,95 @@ +package me.chanjar.weixin.mp.bean; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.util.xml.AdapterCDATA; + +import javax.xml.bind.annotation.*; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.util.ArrayList; +import java.util.List; + +@XmlRootElement(name = "xml") +@XmlAccessorType(XmlAccessType.FIELD) +public class WxMpXmlOutMewsMessage extends WxMpXmlOutMessage { + + @XmlElement(name = "ArticleCount") + protected int articleCount; + + @XmlElementWrapper(name="Articles") + @XmlElement(name = "item") + protected final List articles = new ArrayList(); + + public WxMpXmlOutMewsMessage() { + this.msgType = WxConsts.XML_MSG_NEWS; + } + + public int getArticleCount() { + return articleCount; + } + + public void addArticle(Item item) { + this.articles.add(item); + this.articleCount = this.articles.size(); + } + + public List getArticles() { + return articles; + } + + + @XmlRootElement(name = "Item") + @XmlAccessorType(XmlAccessType.FIELD) + public static class Item { + + @XmlElement(name = "Title") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String Title; + + @XmlElement(name = "Description") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String Description; + + @XmlElement(name="PicUrl") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String PicUrl; + + @XmlElement(name="Url") + @XmlJavaTypeAdapter(AdapterCDATA.class) + private String Url; + + public String getTitle() { + return Title; + } + + public void setTitle(String title) { + Title = title; + } + + public String getDescription() { + return Description; + } + + public void setDescription(String description) { + Description = description; + } + + public String getPicUrl() { + return PicUrl; + } + + public void setPicUrl(String picUrl) { + PicUrl = picUrl; + } + + public String getUrl() { + return Url; + } + + public void setUrl(String url) { + Url = url; + } + + } + + +} diff --git a/src/main/java/me/chanjar/weixin/bean/WxXmlOutMusicMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMusicMessage.java similarity index 92% rename from src/main/java/me/chanjar/weixin/bean/WxXmlOutMusicMessage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMusicMessage.java index 40f6ebad..782b5dbb 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxXmlOutMusicMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMusicMessage.java @@ -1,4 +1,7 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.util.xml.AdapterCDATA; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -6,17 +9,14 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.util.xml.AdapterCDATA; - @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) -public class WxXmlOutMusicMessage extends WxXmlOutMessage { +public class WxMpXmlOutMusicMessage extends WxMpXmlOutMessage { @XmlElement(name = "Music") protected final Music music = new Music(); - public WxXmlOutMusicMessage() { + public WxMpXmlOutMusicMessage() { this.msgType = WxConsts.XML_MSG_MUSIC; } diff --git a/src/main/java/me/chanjar/weixin/bean/WxXmlOutTextMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutTextMessage.java similarity index 72% rename from src/main/java/me/chanjar/weixin/bean/WxXmlOutTextMessage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutTextMessage.java index 3e4d3c08..3542e07b 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxXmlOutTextMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutTextMessage.java @@ -1,4 +1,7 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.util.xml.AdapterCDATA; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -6,18 +9,15 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.util.xml.AdapterCDATA; - @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) -public class WxXmlOutTextMessage extends WxXmlOutMessage { +public class WxMpXmlOutTextMessage extends WxMpXmlOutMessage { @XmlElement(name="Content") @XmlJavaTypeAdapter(AdapterCDATA.class) private String content; - public WxXmlOutTextMessage() { + public WxMpXmlOutTextMessage() { this.msgType = WxConsts.XML_MSG_TEXT; } diff --git a/src/main/java/me/chanjar/weixin/bean/WxXmlOutVideoMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVideoMessage.java similarity index 88% rename from src/main/java/me/chanjar/weixin/bean/WxXmlOutVideoMessage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVideoMessage.java index 08498084..4b192cf7 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxXmlOutVideoMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVideoMessage.java @@ -1,4 +1,7 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.util.xml.AdapterCDATA; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -6,17 +9,14 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.util.xml.AdapterCDATA; - @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) -public class WxXmlOutVideoMessage extends WxXmlOutMessage { +public class WxMpXmlOutVideoMessage extends WxMpXmlOutMessage { @XmlElement(name = "Video") protected final Video video = new Video(); - public WxXmlOutVideoMessage() { + public WxMpXmlOutVideoMessage() { this.msgType = WxConsts.XML_MSG_VIDEO; } diff --git a/src/main/java/me/chanjar/weixin/bean/WxXmlOutVoiceMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVoiceMessage.java similarity index 71% rename from src/main/java/me/chanjar/weixin/bean/WxXmlOutVoiceMessage.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVoiceMessage.java index d7023268..59f164ba 100644 --- a/src/main/java/me/chanjar/weixin/bean/WxXmlOutVoiceMessage.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVoiceMessage.java @@ -1,4 +1,7 @@ -package me.chanjar.weixin.bean; +package me.chanjar.weixin.mp.bean; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.util.xml.MediaIdMarshaller; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; @@ -6,18 +9,15 @@ import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.util.xml.MediaIdMarshaller; - @XmlRootElement(name = "xml") @XmlAccessorType(XmlAccessType.FIELD) -public class WxXmlOutVoiceMessage extends WxXmlOutMessage { +public class WxMpXmlOutVoiceMessage extends WxMpXmlOutMessage { @XmlElement(name="Voice") @XmlJavaTypeAdapter(MediaIdMarshaller.class) private String mediaId; - public WxXmlOutVoiceMessage() { + public WxMpXmlOutVoiceMessage() { this.msgType = WxConsts.XML_MSG_VOICE; } diff --git a/src/main/java/me/chanjar/weixin/bean/custombuilder/BaseBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/BaseBuilder.java similarity index 57% rename from src/main/java/me/chanjar/weixin/bean/custombuilder/BaseBuilder.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/BaseBuilder.java index 5cb924b0..18bf81f9 100644 --- a/src/main/java/me/chanjar/weixin/bean/custombuilder/BaseBuilder.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/BaseBuilder.java @@ -1,6 +1,6 @@ -package me.chanjar.weixin.bean.custombuilder; +package me.chanjar.weixin.mp.bean.custombuilder; -import me.chanjar.weixin.bean.WxCustomMessage; +import me.chanjar.weixin.mp.bean.WxMpCustomMessage; public class BaseBuilder { protected String msgType; @@ -11,8 +11,8 @@ public class BaseBuilder { return (T) this; } - public WxCustomMessage build() { - WxCustomMessage m = new WxCustomMessage(); + public WxMpCustomMessage build() { + WxMpCustomMessage m = new WxMpCustomMessage(); m.setMsgType(this.msgType); m.setToUser(this.toUser); return m; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/ImageBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/ImageBuilder.java new file mode 100644 index 00000000..1c37b46e --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/ImageBuilder.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.mp.bean.custombuilder; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.bean.WxMpCustomMessage; + +/** + * 获得消息builder + *
+ * 用法: WxMpCustomMessage m = WxMpCustomMessage.IMAGE().mediaId(...).toUser(...).build();
+ * 
+ * @author chanjarster + * + */ +public final class ImageBuilder extends BaseBuilder { + private String mediaId; + + public ImageBuilder() { + this.msgType = WxConsts.CUSTOM_MSG_IMAGE; + } + + public ImageBuilder mediaId(String media_id) { + this.mediaId = media_id; + return this; + } + + public WxMpCustomMessage build() { + WxMpCustomMessage m = super.build(); + m.setMediaId(this.mediaId); + return m; + } +} diff --git a/src/main/java/me/chanjar/weixin/bean/custombuilder/MusicBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/MusicBuilder.java similarity index 82% rename from src/main/java/me/chanjar/weixin/bean/custombuilder/MusicBuilder.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/MusicBuilder.java index b0c587b3..5c1abe38 100644 --- a/src/main/java/me/chanjar/weixin/bean/custombuilder/MusicBuilder.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/MusicBuilder.java @@ -1,12 +1,12 @@ -package me.chanjar.weixin.bean.custombuilder; +package me.chanjar.weixin.mp.bean.custombuilder; -import me.chanjar.weixin.api.WxConsts; -import me.chanjar.weixin.bean.WxCustomMessage; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.bean.WxMpCustomMessage; /** * 音乐消息builder *
- * 用法: WxCustomMessage m = WxCustomMessage.MUSIC()
+ * 用法: WxMpCustomMessage m = WxMpCustomMessage.MUSIC()
  *                      .musicUrl(...)
  *                      .hqMusicUrl(...)
  *                      .title(...)
@@ -52,8 +52,8 @@ public final class MusicBuilder extends BaseBuilder {
     return this;
   }
 
-  public WxCustomMessage build() {
-    WxCustomMessage m = super.build();
+  public WxMpCustomMessage build() {
+    WxMpCustomMessage m = super.build();
     m.setMusicUrl(this.musicUrl);
     m.setHqMusicUrl(this.hqMusicUrl);
     m.setTitle(title);
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/NewsBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/NewsBuilder.java
new file mode 100644
index 00000000..825e72f8
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/NewsBuilder.java
@@ -0,0 +1,36 @@
+package me.chanjar.weixin.mp.bean.custombuilder;
+
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.mp.bean.WxMpCustomMessage;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 图文消息builder
+ * 
+ * 用法:
+ * WxMpCustomMessage m = WxMpCustomMessage.NEWS().addArticle(article).toUser(...).build();
+ * 
+ * @author chanjarster + * + */ +public final class NewsBuilder extends BaseBuilder { + + private List articles = new ArrayList(); + + public NewsBuilder() { + this.msgType = WxConsts.CUSTOM_MSG_NEWS; + } + + public NewsBuilder addArticle(WxMpCustomMessage.WxArticle article) { + this.articles.add(article); + return this; + } + + public WxMpCustomMessage build() { + WxMpCustomMessage m = super.build(); + m.setArticles(this.articles); + return m; + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/TextBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/TextBuilder.java new file mode 100644 index 00000000..11249d6d --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/TextBuilder.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.mp.bean.custombuilder; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.bean.WxMpCustomMessage; + +/** + * 文本消息builder + *
+ * 用法: WxMpCustomMessage m = WxMpCustomMessage.TEXT().content(...).toUser(...).build();
+ * 
+ * @author chanjarster + * + */ +public final class TextBuilder extends BaseBuilder { + private String content; + + public TextBuilder() { + this.msgType = WxConsts.CUSTOM_MSG_TEXT; + } + + public TextBuilder content(String content) { + this.content = content; + return this; + } + + public WxMpCustomMessage build() { + WxMpCustomMessage m = super.build(); + m.setContent(this.content); + return m; + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/VideoBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/VideoBuilder.java new file mode 100644 index 00000000..a871d491 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/VideoBuilder.java @@ -0,0 +1,58 @@ +package me.chanjar.weixin.mp.bean.custombuilder; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.bean.WxMpCustomMessage; + +/** + * 视频消息builder + *
+ * 用法: WxMpCustomMessage m = WxMpCustomMessage.VOICE()
+ *                              .mediaId(...)
+ *                              .title(...)
+ *                              .thumbMediaId(..)
+ *                              .description(..)
+ *                              .toUser(...)
+ *                              .build();
+ * 
+ * @author chanjarster + * + */ +public final class VideoBuilder extends BaseBuilder { + private String mediaId; + private String title; + private String description; + private String thumbMediaId; + + public VideoBuilder() { + this.msgType = WxConsts.CUSTOM_MSG_VIDEO; + } + + public VideoBuilder mediaId(String mediaId) { + this.mediaId = mediaId; + return this; + } + + public VideoBuilder title(String title) { + this.title = title; + return this; + } + + public VideoBuilder description(String description) { + this.description = description; + return this; + } + + public VideoBuilder thumbMediaId(String thumb_media_id) { + this.thumbMediaId = thumb_media_id; + return this; + } + + public WxMpCustomMessage build() { + WxMpCustomMessage m = super.build(); + m.setMediaId(this.mediaId); + m.setTitle(title); + m.setDescription(description); + m.setThumbMediaId(thumbMediaId); + return m; + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/VoiceBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/VoiceBuilder.java new file mode 100644 index 00000000..3f81aeb0 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/custombuilder/VoiceBuilder.java @@ -0,0 +1,31 @@ +package me.chanjar.weixin.mp.bean.custombuilder; + +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.bean.WxMpCustomMessage; + +/** + * 语音消息builder + *
+ * 用法: WxMpCustomMessage m = WxMpCustomMessage.VOICE().mediaId(...).toUser(...).build();
+ * 
+ * @author chanjarster + * + */ +public final class VoiceBuilder extends BaseBuilder { + private String mediaId; + + public VoiceBuilder() { + this.msgType = WxConsts.CUSTOM_MSG_VOICE; + } + + public VoiceBuilder mediaId(String media_id) { + this.mediaId = media_id; + return this; + } + + public WxMpCustomMessage build() { + WxMpCustomMessage m = super.build(); + m.setMediaId(this.mediaId); + return m; + } +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/BaseBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/BaseBuilder.java new file mode 100644 index 00000000..77db2617 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/BaseBuilder.java @@ -0,0 +1,29 @@ +package me.chanjar.weixin.mp.bean.outxmlbuilder; + +import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; + +public abstract class BaseBuilder { + + protected String toUserName; + + protected String fromUserName; + + public BuilderType toUser(String touser) { + this.toUserName = touser; + return (BuilderType) this; + } + + public BuilderType fromUser(String fromusername) { + this.fromUserName = fromusername; + return (BuilderType) this; + } + + public abstract ValueType build(); + + public void setCommon(WxMpXmlOutMessage m) { + m.setToUserName(this.toUserName); + m.setFromUserName(this.fromUserName); + m.setCreateTime(System.currentTimeMillis() / 1000l); + } + +} diff --git a/src/main/java/me/chanjar/weixin/bean/outxmlbuilder/ImageBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/ImageBuilder.java similarity index 56% rename from src/main/java/me/chanjar/weixin/bean/outxmlbuilder/ImageBuilder.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/ImageBuilder.java index 0cb18ccc..f127406a 100644 --- a/src/main/java/me/chanjar/weixin/bean/outxmlbuilder/ImageBuilder.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/ImageBuilder.java @@ -1,12 +1,12 @@ -package me.chanjar.weixin.bean.outxmlbuilder; +package me.chanjar.weixin.mp.bean.outxmlbuilder; -import me.chanjar.weixin.bean.WxXmlOutImageMessage; +import me.chanjar.weixin.mp.bean.WxMpMpXmlOutImageMessage; /** * 图片消息builder * @author chanjarster */ -public final class ImageBuilder extends BaseBuilder { +public final class ImageBuilder extends BaseBuilder { private String mediaId; @@ -15,8 +15,8 @@ public final class ImageBuilder extends BaseBuilder { +public final class MusicBuilder extends BaseBuilder { private String title; private String description; @@ -40,8 +40,8 @@ public final class MusicBuilder extends BaseBuilder { + + protected final List articles = new ArrayList(); + + public NewsBuilder addArticle(WxMpXmlOutMewsMessage.Item item) { + this.articles.add(item); + return this; + } + + public WxMpXmlOutMewsMessage build() { + WxMpXmlOutMewsMessage m = new WxMpXmlOutMewsMessage(); + for(WxMpXmlOutMewsMessage.Item item : articles) { + m.addArticle(item); + } + setCommon(m); + return m; + } + +} diff --git a/src/main/java/me/chanjar/weixin/bean/outxmlbuilder/TextBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/TextBuilder.java similarity index 57% rename from src/main/java/me/chanjar/weixin/bean/outxmlbuilder/TextBuilder.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/TextBuilder.java index 56393a33..8352ddea 100644 --- a/src/main/java/me/chanjar/weixin/bean/outxmlbuilder/TextBuilder.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/TextBuilder.java @@ -1,13 +1,13 @@ -package me.chanjar.weixin.bean.outxmlbuilder; +package me.chanjar.weixin.mp.bean.outxmlbuilder; -import me.chanjar.weixin.bean.WxXmlOutTextMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutTextMessage; /** * 文本消息builder * @author chanjarster * */ -public final class TextBuilder extends BaseBuilder { +public final class TextBuilder extends BaseBuilder { private String content; public TextBuilder content(String content) { @@ -15,8 +15,8 @@ public final class TextBuilder extends BaseBuilder { +public final class VideoBuilder extends BaseBuilder { private String mediaId; private String title; @@ -26,8 +26,8 @@ public final class VideoBuilder extends BaseBuilder { +public final class VoiceBuilder extends BaseBuilder { private String mediaId; @@ -15,8 +15,8 @@ public final class VoiceBuilder extends BaseBuilder @@ -13,7 +13,7 @@ import me.chanjar.weixin.util.json.WxGsonBuilder; * @author chanjarster * */ -public class WxMassSendResult { +public class WxMpMassSendResult { private String errorCode; private String errorMsg; @@ -43,8 +43,8 @@ public class WxMassSendResult { this.msgId = msgId; } - public static WxMassSendResult fromJson(String json) { - return WxGsonBuilder.create().fromJson(json, WxMassSendResult.class); + public static WxMpMassSendResult fromJson(String json) { + return WxMpGsonBuilder.create().fromJson(json, WxMpMassSendResult.class); } @Override diff --git a/src/main/java/me/chanjar/weixin/bean/result/WxMassUploadResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassUploadResult.java similarity index 73% rename from src/main/java/me/chanjar/weixin/bean/result/WxMassUploadResult.java rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassUploadResult.java index 074ea3d3..e0ed145b 100644 --- a/src/main/java/me/chanjar/weixin/bean/result/WxMassUploadResult.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassUploadResult.java @@ -1,6 +1,6 @@ -package me.chanjar.weixin.bean.result; +package me.chanjar.weixin.mp.bean.result; -import me.chanjar.weixin.util.json.WxGsonBuilder; +import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; /** *
@@ -10,7 +10,7 @@ import me.chanjar.weixin.util.json.WxGsonBuilder;
  * @author chanjarster
  *
  */
-public class WxMassUploadResult {
+public class WxMpMassUploadResult {
 
   private String type;
   private String mediaId;
@@ -40,8 +40,8 @@ public class WxMassUploadResult {
     this.createdAt = createdAt;
   }
 
-  public static WxMassUploadResult fromJson(String json) {
-    return WxGsonBuilder.create().fromJson(json, WxMassUploadResult.class);
+  public static WxMpMassUploadResult fromJson(String json) {
+    return WxMpGsonBuilder.create().fromJson(json, WxMpMassUploadResult.class);
   }
 
   @Override
diff --git a/src/main/java/me/chanjar/weixin/bean/result/WxQrCodeTicket.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpQrCodeTicket.java
similarity index 69%
rename from src/main/java/me/chanjar/weixin/bean/result/WxQrCodeTicket.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpQrCodeTicket.java
index d9da02aa..bf240c57 100644
--- a/src/main/java/me/chanjar/weixin/bean/result/WxQrCodeTicket.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpQrCodeTicket.java
@@ -1,13 +1,13 @@
-package me.chanjar.weixin.bean.result;
+package me.chanjar.weixin.mp.bean.result;
 
-import me.chanjar.weixin.util.json.WxGsonBuilder;
+import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 
 /**
  * 换取二维码的Ticket
  * 
  * @author chanjarster
  */
-public class WxQrCodeTicket {
+public class WxMpQrCodeTicket {
   
   protected String ticket;
   protected int expire_seconds = -1;
@@ -40,7 +40,7 @@ public class WxQrCodeTicket {
     this.url = url;
   }
 
-  public static WxQrCodeTicket fromJson(String json) {
-    return WxGsonBuilder.INSTANCE.create().fromJson(json, WxQrCodeTicket.class);
+  public static WxMpQrCodeTicket fromJson(String json) {
+    return WxMpGsonBuilder.INSTANCE.create().fromJson(json, WxMpQrCodeTicket.class);
   }
 }
diff --git a/src/main/java/me/chanjar/weixin/bean/result/WxUser.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpUser.java
similarity index 88%
rename from src/main/java/me/chanjar/weixin/bean/result/WxUser.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpUser.java
index a3103fc6..fa4aee6d 100644
--- a/src/main/java/me/chanjar/weixin/bean/result/WxUser.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpUser.java
@@ -1,13 +1,13 @@
-package me.chanjar.weixin.bean.result;
+package me.chanjar.weixin.mp.bean.result;
 
-import me.chanjar.weixin.util.json.WxGsonBuilder;
+import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 
 /**
  * 微信用户信息
  * @author chanjarster
  *
  */
-public class WxUser {
+public class WxMpUser {
 
   protected boolean subscribe;
   protected String openId;
@@ -88,8 +88,8 @@ public class WxUser {
     this.unionId = unionId;
   }
   
-  public static WxUser fromJson(String json) {
-    return WxGsonBuilder.INSTANCE.create().fromJson(json, WxUser.class);
+  public static WxMpUser fromJson(String json) {
+    return WxMpGsonBuilder.INSTANCE.create().fromJson(json, WxMpUser.class);
   }
   
 }
diff --git a/src/main/java/me/chanjar/weixin/bean/result/WxUserList.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpUserList.java
similarity index 75%
rename from src/main/java/me/chanjar/weixin/bean/result/WxUserList.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpUserList.java
index fbcce218..489d5bed 100644
--- a/src/main/java/me/chanjar/weixin/bean/result/WxUserList.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpUserList.java
@@ -1,17 +1,16 @@
-package me.chanjar.weixin.bean.result;
+package me.chanjar.weixin.mp.bean.result;
+
+import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 
 import java.util.ArrayList;
 import java.util.List;
 
-import me.chanjar.weixin.util.json.WxGsonBuilder;
-
-
 /**
  * 关注者列表
  * @author chanjarster
  *
  */
-public class WxUserList {
+public class WxMpUserList {
 
   protected int total = -1;
   protected int count = -1;
@@ -42,7 +41,7 @@ public class WxUserList {
     this.nextOpenId = nextOpenId;
   }
   
-  public static WxUserList fromJson(String json) {
-    return WxGsonBuilder.INSTANCE.create().fromJson(json, WxUserList.class);
+  public static WxMpUserList fromJson(String json) {
+    return WxMpGsonBuilder.INSTANCE.create().fromJson(json, WxMpUserList.class);
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java
new file mode 100755
index 00000000..39165b5c
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/crypto/WxMpCryptUtil.java
@@ -0,0 +1,41 @@
+/**
+ * 对公众平台发送给公众账号的消息加解密示例代码.
+ *
+ * @copyright Copyright (c) 1998-2014 Tencent Inc.
+ */
+
+// ------------------------------------------------------------------------
+
+/**
+ * 针对org.apache.commons.codec.binary.Base64,
+ * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
+ * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
+ */
+package me.chanjar.weixin.mp.util.crypto;
+
+import me.chanjar.weixin.mp.api.WxMpConfigStorage;
+import org.apache.commons.codec.binary.Base64;
+
+public class WxMpCryptUtil extends me.chanjar.weixin.common.util.crypto.WxCryptUtil {
+
+  /**
+   * 构造函数
+   *
+   * @param wxMpConfigStorage
+   */
+  public WxMpCryptUtil(WxMpConfigStorage wxMpConfigStorage) {
+    /*
+     * @param token          公众平台上,开发者设置的token
+     * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
+     * @param appId          公众平台appid
+     */
+    String encodingAesKey = wxMpConfigStorage.getAesKey();
+    String token = wxMpConfigStorage.getToken();
+    String appId = wxMpConfigStorage.getAppId();
+
+    this.token = token;
+    this.appidOrCorpid = appId;
+    this.aesKey = Base64.decodeBase64(encodingAesKey + "=");
+  }
+
+}
diff --git a/src/main/java/me/chanjar/weixin/util/http/QrCodeRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/QrCodeRequestExecutor.java
similarity index 72%
rename from src/main/java/me/chanjar/weixin/util/http/QrCodeRequestExecutor.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/QrCodeRequestExecutor.java
index b33fb501..554c9b8f 100644
--- a/src/main/java/me/chanjar/weixin/util/http/QrCodeRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/QrCodeRequestExecutor.java
@@ -1,31 +1,33 @@
-package me.chanjar.weixin.util.http;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URLEncoder;
-import java.util.UUID;
+package me.chanjar.weixin.mp.util.http;
 
+import me.chanjar.weixin.common.bean.result.WxError;
+import me.chanjar.weixin.common.util.fs.FileUtils;
+import me.chanjar.weixin.common.util.http.InputStreamResponseHandler;
+import me.chanjar.weixin.common.util.http.RequestExecutor;
+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.client.ClientProtocolException;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.entity.ContentType;
 
-import me.chanjar.weixin.bean.result.WxError;
-import me.chanjar.weixin.bean.result.WxQrCodeTicket;
-import me.chanjar.weixin.exception.WxErrorException;
-import me.chanjar.weixin.util.fs.FileUtils;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URLEncoder;
+import java.util.UUID;
 
 /**
  * 获得QrCode图片 请求执行器
  * @author chanjarster
  *
  */
-public class QrCodeRequestExecutor implements RequestExecutor {
+public class QrCodeRequestExecutor implements RequestExecutor {
 
   @Override
-  public File execute(String uri, WxQrCodeTicket ticket) throws WxErrorException, ClientProtocolException, IOException {
+  public File execute(String uri, WxMpQrCodeTicket ticket) throws WxErrorException, ClientProtocolException, IOException {
     if (ticket != null) {
       if (uri.indexOf('?') == -1) {
         uri += '?';
diff --git a/src/main/java/me/chanjar/weixin/util/json/WxCustomMessageGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpCustomMessageGsonAdapter.java
similarity index 82%
rename from src/main/java/me/chanjar/weixin/util/json/WxCustomMessageGsonAdapter.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpCustomMessageGsonAdapter.java
index d1dc55c6..6ff4b773 100644
--- a/src/main/java/me/chanjar/weixin/util/json/WxCustomMessageGsonAdapter.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpCustomMessageGsonAdapter.java
@@ -6,27 +6,22 @@
  * 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.util.json;
+package me.chanjar.weixin.mp.util.json;
 
-import java.lang.reflect.Type;
-
-import me.chanjar.weixin.api.WxConsts;
-import me.chanjar.weixin.bean.WxCustomMessage;
+import com.google.gson.*;
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.mp.bean.WxMpCustomMessage;
 
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
 
 /**
  * 
  * @author qianjia
  *
  */
-public class WxCustomMessageGsonAdapter implements JsonSerializer {
+public class WxMpCustomMessageGsonAdapter implements JsonSerializer {
 
-  public JsonElement serialize(WxCustomMessage message, Type typeOfSrc, JsonSerializationContext context) {
+  public JsonElement serialize(WxMpCustomMessage message, Type typeOfSrc, JsonSerializationContext context) {
     JsonObject messageJson = new JsonObject();
     messageJson.addProperty("touser", message.getToUser());
     messageJson.addProperty("msgtype", message.getMsgType());
@@ -70,7 +65,7 @@ public class WxCustomMessageGsonAdapter implements JsonSerializer, JsonDeserializer {
+public class WxMpGroupGsonAdapter implements JsonSerializer, JsonDeserializer {
 
-  public JsonElement serialize(WxGroup group, Type typeOfSrc, JsonSerializationContext context) {
+  public JsonElement serialize(WxMpGroup group, Type typeOfSrc, JsonSerializationContext context) {
     JsonObject json = new JsonObject();
     JsonObject groupJson = new JsonObject();
     groupJson.addProperty("name", group.getName());
@@ -37,8 +31,8 @@ public class WxGroupGsonAdapter implements JsonSerializer, JsonDeserial
     return json;
   }
 
-  public WxGroup deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
-    WxGroup group = new WxGroup();
+  public WxMpGroup deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+    WxMpGroup group = new WxMpGroup();
     JsonObject groupJson = json.getAsJsonObject();
     if (json.getAsJsonObject().get("group") != null) {
       groupJson = json.getAsJsonObject().get("group").getAsJsonObject();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java
new file mode 100644
index 00000000..021b660d
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java
@@ -0,0 +1,31 @@
+package me.chanjar.weixin.mp.util.json;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import me.chanjar.weixin.mp.bean.*;
+import me.chanjar.weixin.mp.bean.result.*;
+
+public class WxMpGsonBuilder {
+
+  public static final GsonBuilder INSTANCE = new GsonBuilder();
+  
+  static {
+    INSTANCE.disableHtmlEscaping();
+    INSTANCE.registerTypeAdapter(WxMpCustomMessage.class, new WxMpCustomMessageGsonAdapter());
+    INSTANCE.registerTypeAdapter(WxMpMassNews.class, new WxMpMassNewsGsonAdapter());
+    INSTANCE.registerTypeAdapter(WxMpMassGroupMessage.class, new WxMpMassMessageGsonAdapter());
+    INSTANCE.registerTypeAdapter(WxMpMassOpenIdsMessage.class, new WxMpMassOpenIdsMessageGsonAdapter());
+    INSTANCE.registerTypeAdapter(WxMpGroup.class, new WxMpGroupGsonAdapter());
+    INSTANCE.registerTypeAdapter(WxMpUser.class, new WxUserGsonAdapter());
+    INSTANCE.registerTypeAdapter(WxMpUserList.class, new WxUserListGsonAdapter());
+    INSTANCE.registerTypeAdapter(WxMpMassVideo.class, new WxMpMassVideoAdapter());
+    INSTANCE.registerTypeAdapter(WxMpMassSendResult.class, new WxMpMassSendResultAdapter());
+    INSTANCE.registerTypeAdapter(WxMpMassUploadResult.class, new WxMpMassUploadResultAdapter());
+    INSTANCE.registerTypeAdapter(WxMpQrCodeTicket.class, new WxQrCodeTicketAdapter());
+  }
+  
+  public static Gson create() {
+    return INSTANCE.create();
+  }
+  
+}
diff --git a/src/main/java/me/chanjar/weixin/util/json/WxMassMessageGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassMessageGsonAdapter.java
similarity index 85%
rename from src/main/java/me/chanjar/weixin/util/json/WxMassMessageGsonAdapter.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassMessageGsonAdapter.java
index cd985a46..5b9a9845 100644
--- a/src/main/java/me/chanjar/weixin/util/json/WxMassMessageGsonAdapter.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassMessageGsonAdapter.java
@@ -6,26 +6,25 @@
  * 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.util.json;
-
-import java.lang.reflect.Type;
-
-import me.chanjar.weixin.api.WxConsts;
-import me.chanjar.weixin.bean.WxMassGroupMessage;
+package me.chanjar.weixin.mp.util.json;
 
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonSerializationContext;
 import com.google.gson.JsonSerializer;
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.mp.bean.WxMpMassGroupMessage;
+
+import java.lang.reflect.Type;
 
 /**
  * 
  * @author qianjia
  *
  */
-public class WxMassMessageGsonAdapter implements JsonSerializer {
+public class WxMpMassMessageGsonAdapter implements JsonSerializer {
 
-  public JsonElement serialize(WxMassGroupMessage message, Type typeOfSrc, JsonSerializationContext context) {
+  public JsonElement serialize(WxMpMassGroupMessage message, Type typeOfSrc, JsonSerializationContext context) {
     JsonObject messageJson = new JsonObject();
     
     JsonObject filter = new JsonObject();
diff --git a/src/main/java/me/chanjar/weixin/util/json/WxMassNewsGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java
similarity index 73%
rename from src/main/java/me/chanjar/weixin/util/json/WxMassNewsGsonAdapter.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java
index fcaa4c1a..5ba78594 100644
--- a/src/main/java/me/chanjar/weixin/util/json/WxMassNewsGsonAdapter.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java
@@ -6,30 +6,25 @@
  * 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.util.json;
+package me.chanjar.weixin.mp.util.json;
 
-import java.lang.reflect.Type;
-
-import me.chanjar.weixin.bean.WxMassNews;
+import com.google.gson.*;
+import me.chanjar.weixin.mp.bean.WxMpMassNews;
 
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
 
 /**
  * 
  * @author qianjia
  *
  */
-public class WxMassNewsGsonAdapter implements JsonSerializer {
+public class WxMpMassNewsGsonAdapter implements JsonSerializer {
 
-  public JsonElement serialize(WxMassNews message, Type typeOfSrc, JsonSerializationContext context) {
+  public JsonElement serialize(WxMpMassNews message, Type typeOfSrc, JsonSerializationContext context) {
     JsonObject newsJson = new JsonObject();
     
     JsonArray articleJsonArray = new JsonArray();
-    for (WxMassNews.WxMassNewsArticle article : message.getArticles()) {
+    for (WxMpMassNews.WxMpMassNewsArticle article : message.getArticles()) {
       JsonObject articleJson = new JsonObject();
       articleJson.addProperty("thumb_media_id", article.getThumbMediaId());
       articleJson.addProperty("title", article.getTitle());
diff --git a/src/main/java/me/chanjar/weixin/util/json/WxMassOpenIdsMessageGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassOpenIdsMessageGsonAdapter.java
similarity index 77%
rename from src/main/java/me/chanjar/weixin/util/json/WxMassOpenIdsMessageGsonAdapter.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassOpenIdsMessageGsonAdapter.java
index 3cb1e7f6..cd03addf 100644
--- a/src/main/java/me/chanjar/weixin/util/json/WxMassOpenIdsMessageGsonAdapter.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassOpenIdsMessageGsonAdapter.java
@@ -6,28 +6,22 @@
  * 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.util.json;
+package me.chanjar.weixin.mp.util.json;
 
-import java.lang.reflect.Type;
-
-import me.chanjar.weixin.api.WxConsts;
-import me.chanjar.weixin.bean.WxMassOpenIdsMessage;
+import com.google.gson.*;
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage;
 
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
+import java.lang.reflect.Type;
 
 /**
  * 
  * @author qianjia
  *
  */
-public class WxMassOpenIdsMessageGsonAdapter implements JsonSerializer {
+public class WxMpMassOpenIdsMessageGsonAdapter implements JsonSerializer {
 
-  public JsonElement serialize(WxMassOpenIdsMessage message, Type typeOfSrc, JsonSerializationContext context) {
+  public JsonElement serialize(WxMpMassOpenIdsMessage message, Type typeOfSrc, JsonSerializationContext context) {
     JsonObject messageJson = new JsonObject();
     
     JsonArray toUsers = new JsonArray();
diff --git a/src/main/java/me/chanjar/weixin/util/json/WxMassSendResultAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassSendResultAdapter.java
similarity index 73%
rename from src/main/java/me/chanjar/weixin/util/json/WxMassSendResultAdapter.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassSendResultAdapter.java
index ad5dbd6e..99fc02b8 100644
--- a/src/main/java/me/chanjar/weixin/util/json/WxMassSendResultAdapter.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassSendResultAdapter.java
@@ -6,10 +6,11 @@
  * 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.util.json;
+package me.chanjar.weixin.mp.util.json;
 
 import com.google.gson.*;
-import me.chanjar.weixin.bean.result.WxMassSendResult;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+import me.chanjar.weixin.mp.bean.result.WxMpMassSendResult;
 
 import java.lang.reflect.Type;
 
@@ -18,10 +19,10 @@ import java.lang.reflect.Type;
  * @author Daniel Qian
  *
  */
-public class WxMassSendResultAdapter implements JsonDeserializer {
+public class WxMpMassSendResultAdapter implements JsonDeserializer {
 
-  public WxMassSendResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
-    WxMassSendResult sendResult = new WxMassSendResult();
+  public WxMpMassSendResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+    WxMpMassSendResult sendResult = new WxMpMassSendResult();
     JsonObject sendResultJsonObject = json.getAsJsonObject();
 
     if (sendResultJsonObject.get("errcode") != null && !sendResultJsonObject.get("errcode").isJsonNull()) {
diff --git a/src/main/java/me/chanjar/weixin/util/json/WxMassUploadResultAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassUploadResultAdapter.java
similarity index 73%
rename from src/main/java/me/chanjar/weixin/util/json/WxMassUploadResultAdapter.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassUploadResultAdapter.java
index c58f3524..73f03382 100644
--- a/src/main/java/me/chanjar/weixin/util/json/WxMassUploadResultAdapter.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassUploadResultAdapter.java
@@ -6,10 +6,11 @@
  * 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.util.json;
+package me.chanjar.weixin.mp.util.json;
 
 import com.google.gson.*;
-import me.chanjar.weixin.bean.result.WxMassUploadResult;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+import me.chanjar.weixin.mp.bean.result.WxMpMassUploadResult;
 
 import java.lang.reflect.Type;
 
@@ -18,10 +19,10 @@ import java.lang.reflect.Type;
  * @author Daniel Qian
  *
  */
-public class WxMassUploadResultAdapter implements JsonDeserializer {
+public class WxMpMassUploadResultAdapter implements JsonDeserializer {
 
-  public WxMassUploadResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
-    WxMassUploadResult uploadResult = new WxMassUploadResult();
+  public WxMpMassUploadResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+    WxMpMassUploadResult uploadResult = new WxMpMassUploadResult();
     JsonObject uploadResultJsonObject = json.getAsJsonObject();
 
     if (uploadResultJsonObject.get("type") != null && !uploadResultJsonObject.get("type").isJsonNull()) {
diff --git a/src/main/java/me/chanjar/weixin/util/json/WxMassVideoAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassVideoAdapter.java
similarity index 64%
rename from src/main/java/me/chanjar/weixin/util/json/WxMassVideoAdapter.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassVideoAdapter.java
index ba98acf8..d4b37b81 100644
--- a/src/main/java/me/chanjar/weixin/util/json/WxMassVideoAdapter.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassVideoAdapter.java
@@ -6,10 +6,13 @@
  * 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.util.json;
+package me.chanjar.weixin.mp.util.json;
 
-import com.google.gson.*;
-import me.chanjar.weixin.bean.WxMassVideo;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+import me.chanjar.weixin.mp.bean.WxMpMassVideo;
 
 import java.lang.reflect.Type;
 
@@ -18,9 +21,9 @@ import java.lang.reflect.Type;
  * @author Daniel Qian
  *
  */
-public class WxMassVideoAdapter implements JsonSerializer {
+public class WxMpMassVideoAdapter implements JsonSerializer {
 
-  public JsonElement serialize(WxMassVideo message, Type typeOfSrc, JsonSerializationContext context) {
+  public JsonElement serialize(WxMpMassVideo message, Type typeOfSrc, JsonSerializationContext context) {
     JsonObject messageJson = new JsonObject();
     messageJson.addProperty("media_id", message.getMediaId());
     messageJson.addProperty("description", message.getDescription());
diff --git a/src/main/java/me/chanjar/weixin/util/json/WxQrCodeTicketAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxQrCodeTicketAdapter.java
similarity index 77%
rename from src/main/java/me/chanjar/weixin/util/json/WxQrCodeTicketAdapter.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxQrCodeTicketAdapter.java
index f50d9fb0..522d70d0 100644
--- a/src/main/java/me/chanjar/weixin/util/json/WxQrCodeTicketAdapter.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxQrCodeTicketAdapter.java
@@ -6,11 +6,11 @@
  * 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.util.json;
+package me.chanjar.weixin.mp.util.json;
 
 import com.google.gson.*;
-import me.chanjar.weixin.bean.result.WxMassUploadResult;
-import me.chanjar.weixin.bean.result.WxQrCodeTicket;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
 
 import java.lang.reflect.Type;
 
@@ -19,10 +19,10 @@ import java.lang.reflect.Type;
  * @author Daniel Qian
  *
  */
-public class WxQrCodeTicketAdapter implements JsonDeserializer {
+public class WxQrCodeTicketAdapter implements JsonDeserializer {
 
-  public WxQrCodeTicket deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
-    WxQrCodeTicket ticket = new WxQrCodeTicket();
+  public WxMpQrCodeTicket deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+    WxMpQrCodeTicket ticket = new WxMpQrCodeTicket();
     JsonObject ticketJsonObject = json.getAsJsonObject();
 
     if (ticketJsonObject.get("ticket") != null && !ticketJsonObject.get("ticket").isJsonNull()) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserGsonAdapter.java
new file mode 100644
index 00000000..cbbc3080
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserGsonAdapter.java
@@ -0,0 +1,48 @@
+/*
+ * 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.WxMpUser;
+
+import java.lang.reflect.Type;
+
+/**
+ * 
+ * @author qianjia
+ *
+ */
+public class WxUserGsonAdapter implements JsonDeserializer {
+
+  public WxMpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+    JsonObject o = json.getAsJsonObject();
+    WxMpUser wxMpUser = new WxMpUser();
+    wxMpUser.setSubscribe(new Integer(0).equals(GsonHelper.getInteger(o, "subscribe")) ? false : true);
+    wxMpUser.setCity(GsonHelper.getString(o, "city"));
+    wxMpUser.setCountry(GsonHelper.getString(o, "country"));
+    wxMpUser.setHeadImgUrl(GsonHelper.getString(o, "headimgurl"));
+    wxMpUser.setLanguage(GsonHelper.getString(o, "language"));
+    wxMpUser.setNickname(GsonHelper.getString(o, "nickname"));
+    wxMpUser.setOpenId(GsonHelper.getString(o, "openid"));
+    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)) {
+      wxMpUser.setSex("男");
+    } else if (new Integer(2).equals(sex)) {
+      wxMpUser.setSex("女");
+    } else {
+      wxMpUser.setSex("未知");
+    }
+    return wxMpUser;
+  }
+
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserListGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserListGsonAdapter.java
new file mode 100644
index 00000000..ec65735e
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserListGsonAdapter.java
@@ -0,0 +1,37 @@
+/*
+ * 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.WxMpUserList;
+
+import java.lang.reflect.Type;
+
+/**
+ * 
+ * @author qianjia
+ *
+ */
+public class WxUserListGsonAdapter implements JsonDeserializer {
+
+  public WxMpUserList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+    JsonObject o = json.getAsJsonObject();
+    WxMpUserList wxMpUserList = new 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)));
+    }
+    return wxMpUserList;
+  }
+
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XmlTransformer.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XmlTransformer.java
new file mode 100644
index 00000000..5e619746
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XmlTransformer.java
@@ -0,0 +1,90 @@
+package me.chanjar.weixin.mp.util.xml;
+
+import com.sun.xml.bind.marshaller.CharacterEscapeHandler;
+import me.chanjar.weixin.mp.bean.*;
+import org.xml.sax.InputSource;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+import java.io.*;
+
+public class XmlTransformer {
+
+  protected static final JAXBContext JAXB_CONTEXT = initJAXBContext();
+
+  /**
+   * xml -> pojo
+   *
+   * @param clazz
+   * @param xml
+   * @return
+   * @throws JAXBException
+   */
+  @SuppressWarnings("unchecked")
+  public static  T fromXml(Class clazz, String xml) throws JAXBException {
+    Unmarshaller um = JAXB_CONTEXT.createUnmarshaller();
+    T object = (T) um.unmarshal(new StringReader(xml));
+    return object;
+  }
+
+  @SuppressWarnings("unchecked")
+  public static  T fromXml(Class clazz, InputStream is) throws JAXBException {
+    Unmarshaller um = JAXB_CONTEXT.createUnmarshaller();
+    InputSource inputSource = new InputSource(is);
+    inputSource.setEncoding("utf-8");
+    T object = (T) um.unmarshal(inputSource);
+    return object;
+  }
+
+  /**
+   * pojo -> xml
+   *
+   * @param clazz
+   * @param object
+   * @return
+   * @throws JAXBException
+   */
+  public static  String toXml(Class clazz, T object) throws JAXBException {
+    StringWriter stringWriter = new StringWriter();
+    toXml(clazz, object, stringWriter);
+    return stringWriter.getBuffer().toString();
+  }
+
+  public static  void toXml(Class clazz, T object, Writer writer) throws JAXBException {
+    Marshaller m = JAXB_CONTEXT.createMarshaller();
+    m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+    m.setProperty(CharacterEscapeHandler.class.getName(), CHAR_ESCAPE_HANDLER);
+    m.marshal(object, writer);
+  }
+
+  protected static final CharacterEscapeHandler CHAR_ESCAPE_HANDLER = new CharacterUnescapeHandler();
+
+  protected static class CharacterUnescapeHandler implements CharacterEscapeHandler {
+    public void escape(char[] ac, int i, int j, boolean flag, Writer writer) throws IOException {
+      writer.write(ac, i, j);
+    }
+  }
+
+  private static JAXBContext initJAXBContext() {
+    /*
+     * JAXBContext对象是线程安全的,根据官方文档的建议将对象作为全局实例
+     * https://jaxb.java.net/guide/Performance_and_thread_safety.html
+     */
+    try {
+      return JAXBContext.newInstance(
+          WxMpXmlOutMessage.class,
+          WxMpMpXmlOutImageMessage.class,
+          WxMpXmlOutMewsMessage.class,
+          WxMpXmlOutMusicMessage.class,
+          WxMpXmlOutTextMessage.class,
+          WxMpXmlOutVideoMessage.class,
+          WxMpXmlOutVoiceMessage.class,
+          WxMpXmlMessage.class);
+    } catch (JAXBException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+}
diff --git a/src/test/java/me/chanjar/weixin/api/ApiTestModule.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java
similarity index 73%
rename from src/test/java/me/chanjar/weixin/api/ApiTestModule.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java
index 68a9f8a2..ca4cbc13 100644
--- a/src/test/java/me/chanjar/weixin/api/ApiTestModule.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java
@@ -1,4 +1,4 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
 import java.io.InputStream;
 
@@ -9,10 +9,6 @@ import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlRootElement;
 
-import me.chanjar.weixin.api.WxInMemoryConfigStorage;
-import me.chanjar.weixin.api.WxServiceImpl;
-import me.chanjar.weixin.util.xml.XmlTransformer;
-
 import com.google.inject.Binder;
 import com.google.inject.Module;
 import org.xml.sax.InputSource;
@@ -23,12 +19,12 @@ public class ApiTestModule implements Module {
   public void configure(Binder binder) {
     try {
       InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml");
-      WxXmlConfigStorage config = fromXml(WxXmlConfigStorage.class, is1);
-      WxServiceImpl wxService = new WxServiceImpl();
-      wxService.setWxConfigStorage(config);
+      WxXmlMpInMemoryConfigStorage config = fromXml(WxXmlMpInMemoryConfigStorage.class, is1);
+      WxMpServiceImpl wxService = new WxMpServiceImpl();
+      wxService.setWxMpConfigStorage(config);
 
-      binder.bind(WxServiceImpl.class).toInstance(wxService);
-      binder.bind(WxConfigStorage.class).toInstance(config);
+      binder.bind(WxMpServiceImpl.class).toInstance(wxService);
+      binder.bind(WxMpConfigStorage.class).toInstance(config);
     } catch (JAXBException e) {
       throw new RuntimeException(e);
     }
@@ -44,7 +40,7 @@ public class ApiTestModule implements Module {
 
   @XmlRootElement(name = "xml")
   @XmlAccessorType(XmlAccessType.FIELD)
-  public static class WxXmlConfigStorage extends WxInMemoryConfigStorage {
+  public static class WxXmlMpInMemoryConfigStorage extends WxMpInMemoryConfigStorage {
     
     protected String openId;
 
diff --git a/src/test/java/me/chanjar/weixin/api/WxBaseAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxBaseAPITest.java
similarity index 76%
rename from src/test/java/me/chanjar/weixin/api/WxBaseAPITest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxBaseAPITest.java
index af524615..65d88928 100644
--- a/src/test/java/me/chanjar/weixin/api/WxBaseAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxBaseAPITest.java
@@ -1,11 +1,11 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
 import org.apache.commons.lang3.StringUtils;
 import org.testng.Assert;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import me.chanjar.weixin.exception.WxErrorException;
+import me.chanjar.weixin.common.exception.WxErrorException;
 
 import com.google.inject.Inject;
 
@@ -19,10 +19,10 @@ import com.google.inject.Inject;
 public class WxBaseAPITest {
 
   @Inject
-  protected WxServiceImpl wxService;
+  protected WxMpServiceImpl wxService;
 
   public void testRefreshAccessToken() throws WxErrorException {
-    WxConfigStorage configStorage = wxService.wxConfigStorage;
+    WxMpConfigStorage configStorage = wxService.wxMpConfigStorage;
     String before = configStorage.getAccessToken();
     wxService.accessTokenRefresh();
 
diff --git a/src/test/java/me/chanjar/weixin/api/WxCustomMessageAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpCustomMessageAPITest.java
similarity index 57%
rename from src/test/java/me/chanjar/weixin/api/WxCustomMessageAPITest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpCustomMessageAPITest.java
index d8a955a5..04e8400e 100644
--- a/src/test/java/me/chanjar/weixin/api/WxCustomMessageAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpCustomMessageAPITest.java
@@ -1,10 +1,11 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.mp.bean.WxMpCustomMessage;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import me.chanjar.weixin.bean.WxCustomMessage;
-import me.chanjar.weixin.exception.WxErrorException;
+import me.chanjar.weixin.common.exception.WxErrorException;
 
 import com.google.inject.Inject;
 
@@ -15,14 +16,14 @@ import com.google.inject.Inject;
  */
 @Test(groups="customMessageAPI", dependsOnGroups = "baseAPI")
 @Guice(modules = ApiTestModule.class)
-public class WxCustomMessageAPITest {
+public class WxMpCustomMessageAPITest {
 
   @Inject
-  protected WxServiceImpl wxService;
+  protected WxMpServiceImpl wxService;
 
   public void testSendCustomMessage() throws WxErrorException {
-    ApiTestModule.WxXmlConfigStorage configStorage = (ApiTestModule.WxXmlConfigStorage) wxService.wxConfigStorage;
-    WxCustomMessage message = new WxCustomMessage();
+    ApiTestModule.WxXmlMpInMemoryConfigStorage configStorage = (ApiTestModule.WxXmlMpInMemoryConfigStorage) wxService.wxMpConfigStorage;
+    WxMpCustomMessage message = new WxMpCustomMessage();
     message.setMsgType(WxConsts.CUSTOM_MSG_TEXT);
     message.setToUser(configStorage.getOpenId());
     message.setContent("欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World");
diff --git a/src/test/java/me/chanjar/weixin/api/WxGroupAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpGroupAPITest.java
similarity index 69%
rename from src/test/java/me/chanjar/weixin/api/WxGroupAPITest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpGroupAPITest.java
index 74abb563..ca6f20ed 100644
--- a/src/test/java/me/chanjar/weixin/api/WxGroupAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpGroupAPITest.java
@@ -1,13 +1,13 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
 import java.util.List;
 
+import me.chanjar.weixin.mp.bean.WxMpGroup;
 import org.testng.Assert;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import me.chanjar.weixin.bean.WxGroup;
-import me.chanjar.weixin.exception.WxErrorException;
+import me.chanjar.weixin.common.exception.WxErrorException;
 
 import com.google.inject.Inject;
 
@@ -18,24 +18,24 @@ import com.google.inject.Inject;
  */
 @Test(groups = "groupAPI", dependsOnGroups = "baseAPI")
 @Guice(modules = ApiTestModule.class)
-public class WxGroupAPITest {
+public class WxMpGroupAPITest {
 
   @Inject
-  protected WxServiceImpl wxService;
+  protected WxMpServiceImpl wxService;
 
-  protected WxGroup group;
+  protected WxMpGroup group;
   
   public void testGroupCreate() throws WxErrorException {
-    WxGroup res = wxService.groupCreate("测试分组1");
+    WxMpGroup res = wxService.groupCreate("测试分组1");
     Assert.assertEquals(res.getName(), "测试分组1");
   }
 
   @Test(dependsOnMethods="testGroupCreate")
   public void testGroupGet() throws WxErrorException {
-    List groupList = wxService.groupGet();
+    List groupList = wxService.groupGet();
     Assert.assertNotNull(groupList);
     Assert.assertTrue(groupList.size() > 0);
-    for (WxGroup g : groupList) {
+    for (WxMpGroup g : groupList) {
       group = g;
       Assert.assertNotNull(g.getName());
     }
diff --git a/src/test/java/me/chanjar/weixin/api/WxMassMessageAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMassMessageAPITest.java
similarity index 74%
rename from src/test/java/me/chanjar/weixin/api/WxMassMessageAPITest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMassMessageAPITest.java
index 26e8b352..78a48c2f 100644
--- a/src/test/java/me/chanjar/weixin/api/WxMassMessageAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMassMessageAPITest.java
@@ -1,23 +1,22 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
 import java.io.IOException;
 import java.io.InputStream;
 
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+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 org.testng.Assert;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import me.chanjar.weixin.api.ApiTestModule.WxXmlConfigStorage;
-import me.chanjar.weixin.bean.WxMassGroupMessage;
-import me.chanjar.weixin.bean.WxMassNews;
-import me.chanjar.weixin.bean.WxMassNews.WxMassNewsArticle;
-import me.chanjar.weixin.bean.WxMassOpenIdsMessage;
-import me.chanjar.weixin.bean.WxMassVideo;
-import me.chanjar.weixin.bean.result.WxMassSendResult;
-import me.chanjar.weixin.bean.result.WxMassUploadResult;
-import me.chanjar.weixin.bean.result.WxMediaUploadResult;
-import me.chanjar.weixin.exception.WxErrorException;
+import me.chanjar.weixin.common.exception.WxErrorException;
 
 import com.google.inject.Inject;
 
@@ -28,21 +27,21 @@ import com.google.inject.Inject;
  */
 @Test(groups = "massAPI", dependsOnGroups = { "baseAPI", "mediaAPI", "groupAPI"})
 @Guice(modules = ApiTestModule.class)
-public class WxMassMessageAPITest {
+public class WxMpMassMessageAPITest {
 
   @Inject
-  protected WxServiceImpl wxService;
+  protected WxMpServiceImpl wxService;
 
   @Test
   public void testTextMassOpenIdsMessageSend() throws WxErrorException {
     // 发送群发消息
-    WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigStorage;
-    WxMassOpenIdsMessage massMessage = new WxMassOpenIdsMessage();
+    ApiTestModule.WxXmlMpInMemoryConfigStorage configProvider = (ApiTestModule.WxXmlMpInMemoryConfigStorage) wxService.wxMpConfigStorage;
+    WxMpMassOpenIdsMessage massMessage = new WxMpMassOpenIdsMessage();
     massMessage.setMsgType(WxConsts.MASS_MSG_TEXT);
     massMessage.setContent("测试群发消息\n欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World");
     massMessage.getToUsers().add(configProvider.getOpenId());
 
-    WxMassSendResult massResult = wxService.massOpenIdsMessageSend(massMessage);
+    WxMpMassSendResult massResult = wxService.massOpenIdsMessageSend(massMessage);
     Assert.assertNotNull(massResult);
     Assert.assertNotNull(massResult.getMsgId());
   }
@@ -50,37 +49,37 @@ public class WxMassMessageAPITest {
   @Test(dataProvider="massMessages")
   public void testMediaMassOpenIdsMessageSend(String massMsgType, String mediaId) throws WxErrorException, IOException {
     // 发送群发消息
-    WxXmlConfigStorage configProvider = (WxXmlConfigStorage) wxService.wxConfigStorage;
-    WxMassOpenIdsMessage massMessage = new WxMassOpenIdsMessage();
+    ApiTestModule.WxXmlMpInMemoryConfigStorage configProvider = (ApiTestModule.WxXmlMpInMemoryConfigStorage) wxService.wxMpConfigStorage;
+    WxMpMassOpenIdsMessage massMessage = new WxMpMassOpenIdsMessage();
     massMessage.setMsgType(massMsgType);
     massMessage.setMediaId(mediaId);
     massMessage.getToUsers().add(configProvider.getOpenId());
 
-    WxMassSendResult massResult = wxService.massOpenIdsMessageSend(massMessage);
+    WxMpMassSendResult massResult = wxService.massOpenIdsMessageSend(massMessage);
     Assert.assertNotNull(massResult);
     Assert.assertNotNull(massResult.getMsgId());
   }
 
   @Test
   public void testTextMassGroupMessageSend() throws WxErrorException {
-    WxMassGroupMessage massMessage = new WxMassGroupMessage();
+    WxMpMassGroupMessage massMessage = new WxMpMassGroupMessage();
     massMessage.setMsgtype(WxConsts.MASS_MSG_TEXT);
     massMessage.setContent("测试群发消息\n欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World");
     massMessage.setGroupId(wxService.groupGet().get(0).getId());
     
-    WxMassSendResult massResult = wxService.massGroupMessageSend(massMessage);
+    WxMpMassSendResult massResult = wxService.massGroupMessageSend(massMessage);
     Assert.assertNotNull(massResult);
     Assert.assertNotNull(massResult.getMsgId());
   }
   
   @Test(dataProvider="massMessages")
   public void testMediaMassGroupMessageSend(String massMsgType, String mediaId) throws WxErrorException, IOException {
-    WxMassGroupMessage massMessage = new WxMassGroupMessage();
+    WxMpMassGroupMessage massMessage = new WxMpMassGroupMessage();
     massMessage.setMsgtype(massMsgType);
     massMessage.setMediaId(mediaId);
     massMessage.setGroupId(wxService.groupGet().get(0).getId());
 
-    WxMassSendResult massResult = wxService.massGroupMessageSend(massMessage);
+    WxMpMassSendResult massResult = wxService.massGroupMessageSend(massMessage);
     Assert.assertNotNull(massResult);
     Assert.assertNotNull(massResult.getMsgId());
   }
@@ -99,11 +98,11 @@ public class WxMassMessageAPITest {
       Assert.assertNotNull(uploadMediaRes.getMediaId());
       
       // 把视频变成可被群发的媒体
-      WxMassVideo video = new WxMassVideo();
+      WxMpMassVideo video = new WxMpMassVideo();
       video.setTitle("测试标题");
       video.setDescription("测试描述");
       video.setMediaId(uploadMediaRes.getMediaId());
-      WxMassUploadResult uploadResult = wxService.massVideoUpload(video);
+      WxMpMassUploadResult uploadResult = wxService.massVideoUpload(video);
       Assert.assertNotNull(uploadResult);
       Assert.assertNotNull(uploadResult.getMediaId());
       messages[0] = new Object[] { WxConsts.MASS_MSG_VIDEO, uploadResult.getMediaId() };
@@ -139,14 +138,14 @@ public class WxMassMessageAPITest {
       Assert.assertNotNull(uploadMediaRes.getMediaId());
       
       // 上传图文消息
-      WxMassNews news = new WxMassNews();
-      WxMassNewsArticle article1 = new WxMassNewsArticle();
+      WxMpMassNews news = new WxMpMassNews();
+      WxMpMassNews.WxMpMassNewsArticle article1 = new WxMpMassNews.WxMpMassNewsArticle();
       article1.setTitle("标题1");
       article1.setContent("内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1内容1");
       article1.setThumbMediaId(uploadMediaRes.getMediaId());
       news.addArticle(article1);
       
-      WxMassNewsArticle article2 = new WxMassNewsArticle();
+      WxMpMassNews.WxMpMassNewsArticle article2 = new WxMpMassNews.WxMpMassNewsArticle();
       article2.setTitle("标题2");
       article2.setContent("内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2内容2");
       article2.setThumbMediaId(uploadMediaRes.getMediaId());
@@ -156,7 +155,7 @@ public class WxMassMessageAPITest {
       article2.setDigest("摘要2");
       news.addArticle(article2);
       
-      WxMassUploadResult massUploadResult = wxService.massNewsUpload(news);
+      WxMpMassUploadResult massUploadResult = wxService.massNewsUpload(news);
       Assert.assertNotNull(massUploadResult);
       Assert.assertNotNull(uploadMediaRes.getMediaId());
       messages[3] = new Object[] { WxConsts.MASS_MSG_NEWS, massUploadResult.getMediaId() };
diff --git a/src/test/java/me/chanjar/weixin/api/WxMediaAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMediaAPITest.java
similarity index 88%
rename from src/test/java/me/chanjar/weixin/api/WxMediaAPITest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMediaAPITest.java
index f5d59594..e012df40 100644
--- a/src/test/java/me/chanjar/weixin/api/WxMediaAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMediaAPITest.java
@@ -1,17 +1,18 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
 import java.io.IOException;
 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;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import me.chanjar.weixin.bean.result.WxMediaUploadResult;
-import me.chanjar.weixin.exception.WxErrorException;
+import me.chanjar.weixin.common.exception.WxErrorException;
 
 import com.google.inject.Inject;
 
@@ -22,10 +23,10 @@ import com.google.inject.Inject;
  */
 @Test(groups="mediaAPI", dependsOnGroups="baseAPI")
 @Guice(modules = ApiTestModule.class)
-public class WxMediaAPITest {
+public class WxMpMediaAPITest {
 
   @Inject
-  protected WxServiceImpl wxService;
+  protected WxMpServiceImpl wxService;
 
   private List media_ids = new ArrayList();
   
diff --git a/src/test/java/me/chanjar/weixin/api/WxMenuAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMenuAPITest.java
similarity index 87%
rename from src/test/java/me/chanjar/weixin/api/WxMenuAPITest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMenuAPITest.java
index e0f3d3a3..335442b7 100644
--- a/src/test/java/me/chanjar/weixin/api/WxMenuAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMenuAPITest.java
@@ -1,7 +1,8 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
 import javax.xml.bind.JAXBException;
 
+import me.chanjar.weixin.common.api.WxConsts;
 import org.testng.Assert;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Guice;
@@ -9,9 +10,9 @@ import org.testng.annotations.Test;
 
 import com.google.inject.Inject;
 
-import me.chanjar.weixin.bean.WxMenu;
-import me.chanjar.weixin.bean.WxMenu.WxMenuButton;
-import me.chanjar.weixin.exception.WxErrorException;
+import me.chanjar.weixin.common.bean.WxMenu;
+import me.chanjar.weixin.common.bean.WxMenu.WxMenuButton;
+import me.chanjar.weixin.common.exception.WxErrorException;
 
 /**
  * 测试菜单
@@ -20,10 +21,10 @@ import me.chanjar.weixin.exception.WxErrorException;
  */
 @Test(groups="menuAPI", dependsOnGroups="baseAPI")
 @Guice(modules = ApiTestModule.class)
-public class WxMenuAPITest {
+public class WxMpMenuAPITest {
 
   @Inject
-  protected WxServiceImpl wxService;
+  protected WxMpServiceImpl wxService;
   
   @Test(dataProvider = "menu")
   public void testCreateMenu(WxMenu wxMenu) throws WxErrorException {
diff --git a/src/test/java/me/chanjar/weixin/api/WxMessageRouterTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java
similarity index 59%
rename from src/test/java/me/chanjar/weixin/api/WxMessageRouterTest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java
index f1d1163d..b43ca3c8 100644
--- a/src/test/java/me/chanjar/weixin/api/WxMessageRouterTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java
@@ -1,65 +1,62 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
 import java.util.Map;
 
-import me.chanjar.weixin.api.WxConsts;
-import me.chanjar.weixin.api.WxMessageHandler;
-import me.chanjar.weixin.api.WxMessageRouter;
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.mp.bean.WxMpXmlMessage;
+import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage;
 import org.testng.Assert;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
-import me.chanjar.weixin.bean.WxXmlMessage;
-import me.chanjar.weixin.bean.WxXmlOutMessage;
-
 /**
  * 测试消息路由器
  * @author chanjarster
  *
  */
 @Test
-public class WxMessageRouterTest {
+public class WxMpMessageRouterTest {
   
   @Test(enabled = false)
-  public void prepare(boolean async, StringBuffer sb, WxMessageRouter router) {
+  public void prepare(boolean async, StringBuffer sb, WxMpMessageRouter router) {
     router
       .rule()
         .async(async)
         .msgType(WxConsts.XML_MSG_TEXT).event(WxConsts.EVT_CLICK).eventKey("KEY_1").content("CONTENT_1")
-        .handler(new WxEchoMessageHandler(sb, "COMBINE_4"))
+        .handler(new WxEchoMpMessageHandler(sb, "COMBINE_4"))
       .end()
       .rule()
         .async(async)
         .msgType(WxConsts.XML_MSG_TEXT).event(WxConsts.EVT_CLICK).eventKey("KEY_1")
-        .handler(new WxEchoMessageHandler(sb, "COMBINE_3"))
+        .handler(new WxEchoMpMessageHandler(sb, "COMBINE_3"))
       .end()
       .rule()
         .async(async)
         .msgType(WxConsts.XML_MSG_TEXT).event(WxConsts.EVT_CLICK)
-        .handler(new WxEchoMessageHandler(sb, "COMBINE_2"))
+        .handler(new WxEchoMpMessageHandler(sb, "COMBINE_2"))
       .end()
-      .rule().async(async).msgType(WxConsts.XML_MSG_TEXT).handler(new WxEchoMessageHandler(sb, WxConsts.XML_MSG_TEXT)).end()
-      .rule().async(async).event(WxConsts.EVT_CLICK).handler(new WxEchoMessageHandler(sb, WxConsts.EVT_CLICK)).end()
-      .rule().async(async).eventKey("KEY_1").handler(new WxEchoMessageHandler(sb, "KEY_1")).end()
-      .rule().async(async).content("CONTENT_1").handler(new WxEchoMessageHandler(sb, "CONTENT_1")).end()
-      .rule().async(async).rContent(".*bc.*").handler(new WxEchoMessageHandler(sb, "abcd")).end()
-      .rule().async(async).handler(new WxEchoMessageHandler(sb, "ALL")).end();
+      .rule().async(async).msgType(WxConsts.XML_MSG_TEXT).handler(new WxEchoMpMessageHandler(sb, WxConsts.XML_MSG_TEXT)).end()
+      .rule().async(async).event(WxConsts.EVT_CLICK).handler(new WxEchoMpMessageHandler(sb, WxConsts.EVT_CLICK)).end()
+      .rule().async(async).eventKey("KEY_1").handler(new WxEchoMpMessageHandler(sb, "KEY_1")).end()
+      .rule().async(async).content("CONTENT_1").handler(new WxEchoMpMessageHandler(sb, "CONTENT_1")).end()
+      .rule().async(async).rContent(".*bc.*").handler(new WxEchoMpMessageHandler(sb, "abcd")).end()
+      .rule().async(async).handler(new WxEchoMpMessageHandler(sb, "ALL")).end();
     ;
   }
   
   @Test(dataProvider="messages-1")
-  public void testSync(WxXmlMessage message, String expected) {
+  public void testSync(WxMpXmlMessage message, String expected) {
     StringBuffer sb = new StringBuffer();
-    WxMessageRouter router = new WxMessageRouter();
+    WxMpMessageRouter router = new WxMpMessageRouter();
     prepare(false, sb, router);
     router.route(message);
     Assert.assertEquals(sb.toString(), expected);
   }
   
   @Test(dataProvider="messages-1")
-  public void testAsync(WxXmlMessage message, String expected) throws InterruptedException {
+  public void testAsync(WxMpXmlMessage message, String expected) throws InterruptedException {
     StringBuffer sb = new StringBuffer();
-    WxMessageRouter router = new WxMessageRouter();
+    WxMpMessageRouter router = new WxMpMessageRouter();
     prepare(true,  sb, router);
     router.route(message);
     Thread.sleep(500l);
@@ -67,15 +64,15 @@ public class WxMessageRouterTest {
   }
   
   public void testConcurrency() throws InterruptedException {
-    final WxMessageRouter router = new WxMessageRouter();
-    router.rule().handler(new WxMessageHandler() {
+    final WxMpMessageRouter router = new WxMpMessageRouter();
+    router.rule().handler(new WxMpMessageHandler() {
       @Override
-      public WxXmlOutMessage handle(WxXmlMessage wxMessage, Map context) {
+      public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context) {
         return null;
       }
     }).end();
     
-    final WxXmlMessage m = new WxXmlMessage();
+    final WxMpXmlMessage m = new WxMpXmlMessage();
     Runnable r = new Runnable() {
       @Override
       public void run() {
@@ -94,34 +91,34 @@ public class WxMessageRouterTest {
   }
   @DataProvider(name="messages-1")
   public Object[][] messages2() {
-    WxXmlMessage message1 = new WxXmlMessage();
+    WxMpXmlMessage message1 = new WxMpXmlMessage();
     message1.setMsgType(WxConsts.XML_MSG_TEXT);
   
-    WxXmlMessage message2 = new WxXmlMessage();
+    WxMpXmlMessage message2 = new WxMpXmlMessage();
     message2.setEvent(WxConsts.EVT_CLICK);
     
-    WxXmlMessage message3 = new WxXmlMessage();
+    WxMpXmlMessage message3 = new WxMpXmlMessage();
     message3.setEventKey("KEY_1");
     
-    WxXmlMessage message4 = new WxXmlMessage();
+    WxMpXmlMessage message4 = new WxMpXmlMessage();
     message4.setContent("CONTENT_1");
     
-    WxXmlMessage message5 = new WxXmlMessage();
+    WxMpXmlMessage message5 = new WxMpXmlMessage();
     message5.setContent("BLA");
     
-    WxXmlMessage message6 =  new WxXmlMessage();
+    WxMpXmlMessage message6 =  new WxMpXmlMessage();
     message6.setContent("abcd");
     
-    WxXmlMessage c2 = new WxXmlMessage();
+    WxMpXmlMessage c2 = new WxMpXmlMessage();
     c2.setMsgType(WxConsts.XML_MSG_TEXT);
     c2.setEvent(WxConsts.EVT_CLICK);
     
-    WxXmlMessage c3 = new WxXmlMessage();
+    WxMpXmlMessage c3 = new WxMpXmlMessage();
     c3.setMsgType(WxConsts.XML_MSG_TEXT);
     c3.setEvent(WxConsts.EVT_CLICK);
     c3.setEventKey("KEY_1");
     
-    WxXmlMessage c4 = new WxXmlMessage();
+    WxMpXmlMessage c4 = new WxMpXmlMessage();
     c4.setMsgType(WxConsts.XML_MSG_TEXT);
     c4.setEvent(WxConsts.EVT_CLICK);
     c4.setEventKey("KEY_1");
@@ -129,7 +126,7 @@ public class WxMessageRouterTest {
     
     return new Object[][] {
         new Object[] { message1, WxConsts.XML_MSG_TEXT + "," },
-        new Object[] { message2, WxConsts.EVT_CLICK + "," }, 
+        new Object[] { message2, WxConsts.EVT_CLICK + "," },
         new Object[] { message3, "KEY_1," },
         new Object[] { message4, "CONTENT_1," },
         new Object[] { message5, "ALL," },
@@ -141,18 +138,18 @@ public class WxMessageRouterTest {
     
   }
 
-  public static class WxEchoMessageHandler implements WxMessageHandler {
+  public static class WxEchoMpMessageHandler implements WxMpMessageHandler {
 
     private StringBuffer sb;
     private String echoStr;
 
-    public WxEchoMessageHandler(StringBuffer sb, String echoStr) {
+    public WxEchoMpMessageHandler(StringBuffer sb, String echoStr) {
       this.sb = sb;
       this.echoStr = echoStr;
     }
 
     @Override
-    public WxXmlOutMessage handle(WxXmlMessage wxMessage, Map context) {
+    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context) {
       sb.append(this.echoStr).append(',');
       return null;
     }
diff --git a/src/test/java/me/chanjar/weixin/api/WxQrCodeAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpQrCodeAPITest.java
similarity index 68%
rename from src/test/java/me/chanjar/weixin/api/WxQrCodeAPITest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpQrCodeAPITest.java
index 1c07072f..2a206d2e 100644
--- a/src/test/java/me/chanjar/weixin/api/WxQrCodeAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpQrCodeAPITest.java
@@ -1,13 +1,13 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
 import java.io.File;
 
+import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
 import org.testng.Assert;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import me.chanjar.weixin.bean.result.WxQrCodeTicket;
-import me.chanjar.weixin.exception.WxErrorException;
+import me.chanjar.weixin.common.exception.WxErrorException;
 
 import com.google.inject.Inject;
 
@@ -18,27 +18,27 @@ import com.google.inject.Inject;
  */
 @Test(groups = "qrCodeAPI", dependsOnGroups = { "baseAPI" })
 @Guice(modules = ApiTestModule.class)
-public class WxQrCodeAPITest {
+public class WxMpQrCodeAPITest {
 
   @Inject
-  protected WxServiceImpl wxService;
+  protected WxMpServiceImpl wxService;
 
   public void testQrCodeCreateTmpTicket() throws WxErrorException {
-    WxQrCodeTicket ticket = wxService.qrCodeCreateTmpTicket(1, null);
+    WxMpQrCodeTicket ticket = wxService.qrCodeCreateTmpTicket(1, null);
     Assert.assertNotNull(ticket.getUrl());
     Assert.assertNotNull(ticket.getTicket());
     Assert.assertTrue(ticket.getExpire_seconds() != -1);
   }
 
   public void testQrCodeCreateLastTicket() throws WxErrorException {
-    WxQrCodeTicket ticket = wxService.qrCodeCreateLastTicket(1);
+    WxMpQrCodeTicket ticket = wxService.qrCodeCreateLastTicket(1);
     Assert.assertNotNull(ticket.getUrl());
     Assert.assertNotNull(ticket.getTicket());
     Assert.assertTrue(ticket.getExpire_seconds() == -1);
   }
 
   public void testQrCodePicture() throws WxErrorException {
-    WxQrCodeTicket ticket = wxService.qrCodeCreateLastTicket(1);
+    WxMpQrCodeTicket ticket = wxService.qrCodeCreateLastTicket(1);
     File file = wxService.qrCodePicture(ticket);
     Assert.assertNotNull(file);
   }
diff --git a/src/test/java/me/chanjar/weixin/api/WxShortUrlAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpShortUrlAPITest.java
similarity index 73%
rename from src/test/java/me/chanjar/weixin/api/WxShortUrlAPITest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpShortUrlAPITest.java
index 3df6e217..e854ed08 100644
--- a/src/test/java/me/chanjar/weixin/api/WxShortUrlAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpShortUrlAPITest.java
@@ -1,10 +1,10 @@
-package me.chanjar.weixin.api;
+package me.chanjar.weixin.mp.api;
 
 import org.testng.Assert;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import me.chanjar.weixin.exception.WxErrorException;
+import me.chanjar.weixin.common.exception.WxErrorException;
 
 import com.google.inject.Inject;
 
@@ -15,10 +15,10 @@ import com.google.inject.Inject;
  */
 @Test(groups = "shortURLAPI", dependsOnGroups = { "baseAPI" })
 @Guice(modules = ApiTestModule.class)
-public class WxShortUrlAPITest {
+public class WxMpShortUrlAPITest {
 
   @Inject
-  protected WxServiceImpl wxService;
+  protected WxMpServiceImpl wxService;
 
   public void testShortUrl() throws WxErrorException {
     String shortUrl = wxService.shortUrl("www.baidu.com");
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpUserAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpUserAPITest.java
new file mode 100644
index 00000000..c29bda83
--- /dev/null
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpUserAPITest.java
@@ -0,0 +1,55 @@
+package me.chanjar.weixin.mp.api;
+
+import me.chanjar.weixin.mp.bean.result.WxMpUserList;
+import org.testng.Assert;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import me.chanjar.weixin.mp.bean.result.WxMpUser;
+import me.chanjar.weixin.common.exception.WxErrorException;
+
+import com.google.inject.Inject;
+
+/**
+ * 测试用户相关的接口
+ * @author chanjarster
+ *
+ */
+@Test(groups = "userAPI", dependsOnGroups = { "baseAPI", "groupAPI" })
+@Guice(modules = ApiTestModule.class)
+public class WxMpUserAPITest {
+
+  @Inject
+  protected WxMpServiceImpl wxService;
+
+  public void testUserUpdateRemark() throws WxErrorException {
+    ApiTestModule.WxXmlMpInMemoryConfigStorage configProvider = (ApiTestModule.WxXmlMpInMemoryConfigStorage) wxService.wxMpConfigStorage;
+    wxService.userUpdateRemark(configProvider.getOpenId(), "测试备注名");
+  }
+
+  public void testUserInfo() throws WxErrorException  {
+    ApiTestModule.WxXmlMpInMemoryConfigStorage configProvider = (ApiTestModule.WxXmlMpInMemoryConfigStorage) wxService.wxMpConfigStorage;
+    WxMpUser user = wxService.userInfo(configProvider.getOpenId(), null);
+    Assert.assertNotNull(user);
+  }
+  
+  public void testUserList() throws WxErrorException  {
+      WxMpUserList wxMpUserList = wxService.userList(null);
+      Assert.assertNotNull(wxMpUserList);
+      Assert.assertFalse(wxMpUserList.getCount() == -1);
+      Assert.assertFalse(wxMpUserList.getTotal() == -1);
+      Assert.assertFalse(wxMpUserList.getOpenIds().size() == -1);
+  }
+    
+  public void testGroupQueryUserGroup() throws WxErrorException {
+    ApiTestModule.WxXmlMpInMemoryConfigStorage configStorage = (ApiTestModule.WxXmlMpInMemoryConfigStorage) wxService.wxMpConfigStorage;
+    long groupid = wxService.userGetGroup(configStorage.getOpenId());
+    Assert.assertTrue(groupid != -1l);
+  }
+  
+  public void getGroupMoveUser() throws WxErrorException {
+    ApiTestModule.WxXmlMpInMemoryConfigStorage configStorage = (ApiTestModule.WxXmlMpInMemoryConfigStorage) wxService.wxMpConfigStorage;
+    wxService.userUpdateGroup(configStorage.getOpenId(), wxService.groupGet().get(3).getId());
+  }
+  
+}
diff --git a/src/test/java/me/chanjar/weixin/bean/WxCustomMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpCustomMessageTest.java
similarity index 80%
rename from src/test/java/me/chanjar/weixin/bean/WxCustomMessageTest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpCustomMessageTest.java
index 1d483898..68947b41 100644
--- a/src/test/java/me/chanjar/weixin/bean/WxCustomMessageTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpCustomMessageTest.java
@@ -1,16 +1,16 @@
-package me.chanjar.weixin.bean;
+package me.chanjar.weixin.mp.bean;
 
+import me.chanjar.weixin.common.api.WxConsts;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
-import me.chanjar.weixin.api.WxConsts;
-import me.chanjar.weixin.bean.WxCustomMessage.WxArticle;
+import me.chanjar.weixin.mp.bean.WxMpCustomMessage.WxArticle;
 
 @Test
-public class WxCustomMessageTest {
+public class WxMpCustomMessageTest {
 
   public void testTextReply() {
-    WxCustomMessage reply = new WxCustomMessage();
+    WxMpCustomMessage reply = new WxMpCustomMessage();
     reply.setToUser("OPENID");
     reply.setMsgType(WxConsts.CUSTOM_MSG_TEXT);
     reply.setContent("sfsfdsdf");
@@ -18,12 +18,12 @@ public class WxCustomMessageTest {
   }
   
   public void testTextBuild() {
-    WxCustomMessage reply = WxCustomMessage.TEXT().toUser("OPENID").content("sfsfdsdf").build();
+    WxMpCustomMessage reply = WxMpCustomMessage.TEXT().toUser("OPENID").content("sfsfdsdf").build();
     Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"sfsfdsdf\"}}");
   }
   
   public void testImageReply() {
-    WxCustomMessage reply = new WxCustomMessage();
+    WxMpCustomMessage reply = new WxMpCustomMessage();
     reply.setToUser("OPENID");
     reply.setMsgType(WxConsts.CUSTOM_MSG_IMAGE);
     reply.setMediaId("MEDIA_ID");
@@ -31,12 +31,12 @@ public class WxCustomMessageTest {
   }
   
   public void testImageBuild() {
-    WxCustomMessage reply = WxCustomMessage.IMAGE().toUser("OPENID").mediaId("MEDIA_ID").build();
+    WxMpCustomMessage reply = WxMpCustomMessage.IMAGE().toUser("OPENID").mediaId("MEDIA_ID").build();
     Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"image\",\"image\":{\"media_id\":\"MEDIA_ID\"}}");
   }
   
   public void testVoiceReply() {
-    WxCustomMessage reply = new WxCustomMessage();
+    WxMpCustomMessage reply = new WxMpCustomMessage();
     reply.setToUser("OPENID");
     reply.setMsgType(WxConsts.CUSTOM_MSG_VOICE);
     reply.setMediaId("MEDIA_ID");
@@ -44,12 +44,12 @@ public class WxCustomMessageTest {
   }
   
   public void testVoiceBuild() {
-    WxCustomMessage reply = WxCustomMessage.VOICE().toUser("OPENID").mediaId("MEDIA_ID").build();
+    WxMpCustomMessage reply = WxMpCustomMessage.VOICE().toUser("OPENID").mediaId("MEDIA_ID").build();
     Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"voice\",\"voice\":{\"media_id\":\"MEDIA_ID\"}}");
   }
   
   public void testVideoReply() {
-    WxCustomMessage reply = new WxCustomMessage();
+    WxMpCustomMessage reply = new WxMpCustomMessage();
     reply.setToUser("OPENID");
     reply.setMsgType(WxConsts.CUSTOM_MSG_VIDEO);
     reply.setMediaId("MEDIA_ID");
@@ -60,12 +60,12 @@ public class WxCustomMessageTest {
   }
   
   public void testVideoBuild() {
-    WxCustomMessage reply = WxCustomMessage.VIDEO().toUser("OPENID").title("TITLE").mediaId("MEDIA_ID").thumbMediaId("MEDIA_ID").description("DESCRIPTION").build();
+    WxMpCustomMessage reply = WxMpCustomMessage.VIDEO().toUser("OPENID").title("TITLE").mediaId("MEDIA_ID").thumbMediaId("MEDIA_ID").description("DESCRIPTION").build();
     Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"video\",\"video\":{\"media_id\":\"MEDIA_ID\",\"thumb_media_id\":\"MEDIA_ID\",\"title\":\"TITLE\",\"description\":\"DESCRIPTION\"}}");
   }
   
   public void testMusicReply() {
-    WxCustomMessage reply = new WxCustomMessage();
+    WxMpCustomMessage reply = new WxMpCustomMessage();
     reply.setToUser("OPENID");
     reply.setMsgType(WxConsts.CUSTOM_MSG_MUSIC);
     reply.setThumbMediaId("MEDIA_ID");
@@ -77,7 +77,7 @@ public class WxCustomMessageTest {
   }
   
   public void testMusicBuild() {
-    WxCustomMessage reply = WxCustomMessage.MUSIC()
+    WxMpCustomMessage reply = WxMpCustomMessage.MUSIC()
           .toUser("OPENID")
           .title("TITLE")
           .thumbMediaId("MEDIA_ID")
@@ -89,7 +89,7 @@ public class WxCustomMessageTest {
   }
   
   public void testNewsReply() {
-    WxCustomMessage reply = new WxCustomMessage();
+    WxMpCustomMessage reply = new WxMpCustomMessage();
     reply.setToUser("OPENID");
     reply.setMsgType(WxConsts.CUSTOM_MSG_NEWS);
     
@@ -124,7 +124,7 @@ public class WxCustomMessageTest {
     article2.setDescription("Is Really A Happy Day");
     article2.setTitle("Happy Day");
 
-    WxCustomMessage reply = WxCustomMessage.NEWS().toUser("OPENID").addArticle(article1).addArticle(article2).build();
+    WxMpCustomMessage reply = WxMpCustomMessage.NEWS().toUser("OPENID").addArticle(article1).addArticle(article2).build();
     
     Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"articles\":[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"},{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}");
   }
diff --git a/src/test/java/me/chanjar/weixin/bean/WxXmlMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpXmlMessageTest.java
similarity index 96%
rename from src/test/java/me/chanjar/weixin/bean/WxXmlMessageTest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpXmlMessageTest.java
index 8517a8b0..346681cb 100644
--- a/src/test/java/me/chanjar/weixin/bean/WxXmlMessageTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpXmlMessageTest.java
@@ -1,11 +1,11 @@
-package me.chanjar.weixin.bean;
+package me.chanjar.weixin.mp.bean;
 
-import me.chanjar.weixin.api.WxConsts;
+import me.chanjar.weixin.common.api.WxConsts;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
 @Test
-public class WxXmlMessageTest {
+public class WxMpXmlMessageTest {
 
   public void testFromXml() {
 
@@ -53,7 +53,7 @@ public class WxXmlMessageTest {
                 + "  \n"
                 + ""
                 + "";
-    WxXmlMessage wxMessage = WxXmlMessage.fromXml(xml);
+    WxMpXmlMessage wxMessage = WxMpXmlMessage.fromXml(xml);
     Assert.assertEquals(wxMessage.getToUserName(), "toUser");
     Assert.assertEquals(wxMessage.getFromUserName(), "fromUser");
     Assert.assertEquals(wxMessage.getCreateTime(), new Long(1348831860l));
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessageTest.java
new file mode 100644
index 00000000..e0377cb6
--- /dev/null
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessageTest.java
@@ -0,0 +1,50 @@
+package me.chanjar.weixin.mp.bean;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+@Test
+public class WxMpXmlOutImageMessageTest {
+
+  public void test() {
+    WxMpMpXmlOutImageMessage m = new WxMpMpXmlOutImageMessage();
+    m.setMediaId("ddfefesfsdfef");
+    m.setCreateTime(1122l);
+    m.setFromUserName("from");
+    m.setToUserName("to");
+    
+    String expected = "" 
+        + ""
+        + ""
+        + ""
+        + "1122"
+        + ""
+        + ""
+        + "";
+    System.out.println(m.toXml());
+    Assert.assertEquals(m.toXml().replaceAll("\\s", ""), expected.replaceAll("\\s", ""));
+  }
+  
+  public void testBuild() {
+    WxMpMpXmlOutImageMessage m = WxMpXmlOutMessage.IMAGE().mediaId("ddfefesfsdfef").fromUser("from").toUser("to").build();
+    String expected = "" 
+        + ""
+        + ""
+        + ""
+        + "1122"
+        + ""
+        + ""
+        + "";
+    System.out.println(m.toXml());
+    Assert.assertEquals(
+              m
+                .toXml()
+                .replaceAll("\\s", "")
+                .replaceAll(".*?", ""), 
+              expected
+                .replaceAll("\\s", "")
+                .replaceAll(".*?", "")
+              );
+    
+  }
+}
diff --git a/src/test/java/me/chanjar/weixin/bean/WxXmlOutMusicMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutMusicMessageTest.java
similarity index 94%
rename from src/test/java/me/chanjar/weixin/bean/WxXmlOutMusicMessageTest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutMusicMessageTest.java
index 9a9ef6b2..0471e2c3 100644
--- a/src/test/java/me/chanjar/weixin/bean/WxXmlOutMusicMessageTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutMusicMessageTest.java
@@ -1,4 +1,4 @@
-package me.chanjar.weixin.bean;
+package me.chanjar.weixin.mp.bean;
 
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -7,7 +7,7 @@ import org.testng.annotations.Test;
 public class WxXmlOutMusicMessageTest {
 
   public void test() {
-    WxXmlOutMusicMessage m = new WxXmlOutMusicMessage();
+    WxMpXmlOutMusicMessage m = new WxMpXmlOutMusicMessage();
     m.setTitle("title");
     m.setDescription("ddfff");
     m.setHqMusicUrl("hQMusicUrl");
@@ -36,7 +36,7 @@ public class WxXmlOutMusicMessageTest {
   }
   
   public void testBuild() {
-    WxXmlOutMusicMessage m = WxXmlOutMessage.MUSIC()
+    WxMpXmlOutMusicMessage m = WxMpXmlOutMessage.MUSIC()
           .fromUser("fromUser")
           .toUser("toUser")
           .title("title")
diff --git a/src/test/java/me/chanjar/weixin/bean/WxXmlOutNewsMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutNewsMessageTest.java
similarity index 91%
rename from src/test/java/me/chanjar/weixin/bean/WxXmlOutNewsMessageTest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutNewsMessageTest.java
index 3bac0da9..782a6abe 100644
--- a/src/test/java/me/chanjar/weixin/bean/WxXmlOutNewsMessageTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutNewsMessageTest.java
@@ -1,4 +1,4 @@
-package me.chanjar.weixin.bean;
+package me.chanjar.weixin.mp.bean;
 
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -7,12 +7,12 @@ import org.testng.annotations.Test;
 public class WxXmlOutNewsMessageTest {
 
   public void test() {
-    WxXmlOutMewsMessage m = new WxXmlOutMewsMessage();
+    WxMpXmlOutMewsMessage m = new WxMpXmlOutMewsMessage();
     m.setCreateTime(1122l);
     m.setFromUserName("fromUser");
     m.setToUserName("toUser");
     
-    WxXmlOutMewsMessage.Item item = new WxXmlOutMewsMessage.Item();
+    WxMpXmlOutMewsMessage.Item item = new WxMpXmlOutMewsMessage.Item();
     item.setDescription("description");
     item.setPicUrl("picUrl");
     item.setTitle("title");
@@ -46,13 +46,13 @@ public class WxXmlOutNewsMessageTest {
   }
   
   public void testBuild() {
-    WxXmlOutMewsMessage.Item item = new WxXmlOutMewsMessage.Item();
+    WxMpXmlOutMewsMessage.Item item = new WxMpXmlOutMewsMessage.Item();
     item.setDescription("description");
     item.setPicUrl("picUrl");
     item.setTitle("title");
     item.setUrl("url");
     
-    WxXmlOutMewsMessage m = WxXmlOutMessage.NEWS()
+    WxMpXmlOutMewsMessage m = WxMpXmlOutMessage.NEWS()
           .fromUser("fromUser")
           .toUser("toUser")
           .addArticle(item)
diff --git a/src/test/java/me/chanjar/weixin/bean/WxXmlOutTextMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutTextMessageTest.java
similarity index 87%
rename from src/test/java/me/chanjar/weixin/bean/WxXmlOutTextMessageTest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutTextMessageTest.java
index d3168ba6..a845abcd 100644
--- a/src/test/java/me/chanjar/weixin/bean/WxXmlOutTextMessageTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutTextMessageTest.java
@@ -1,4 +1,4 @@
-package me.chanjar.weixin.bean;
+package me.chanjar.weixin.mp.bean;
 
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -7,7 +7,7 @@ import org.testng.annotations.Test;
 public class WxXmlOutTextMessageTest {
 
   public void test() {
-    WxXmlOutTextMessage m = new WxXmlOutTextMessage();
+    WxMpXmlOutTextMessage m = new WxMpXmlOutTextMessage();
     m.setContent("content");
     m.setCreateTime(1122l);
     m.setFromUserName("from");
@@ -26,7 +26,7 @@ public class WxXmlOutTextMessageTest {
   }
   
   public void testBuild() {
-    WxXmlOutTextMessage m = WxXmlOutMessage.TEXT().content("content").fromUser("from").toUser("to").build();
+    WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content("content").fromUser("from").toUser("to").build();
     String expected = "" 
         + ""
         + ""
diff --git a/src/test/java/me/chanjar/weixin/bean/WxXmlOutVideoMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutVideoMessageTest.java
similarity index 92%
rename from src/test/java/me/chanjar/weixin/bean/WxXmlOutVideoMessageTest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutVideoMessageTest.java
index 5a61e347..b033b040 100644
--- a/src/test/java/me/chanjar/weixin/bean/WxXmlOutVideoMessageTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutVideoMessageTest.java
@@ -1,4 +1,4 @@
-package me.chanjar.weixin.bean;
+package me.chanjar.weixin.mp.bean;
 
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -7,7 +7,7 @@ import org.testng.annotations.Test;
 public class WxXmlOutVideoMessageTest {
 
   public void test() {
-    WxXmlOutVideoMessage m = new WxXmlOutVideoMessage();
+    WxMpXmlOutVideoMessage m = new WxMpXmlOutVideoMessage();
     m.setMediaId("media_id");
     m.setTitle("title");
     m.setDescription("ddfff");
@@ -32,7 +32,7 @@ public class WxXmlOutVideoMessageTest {
   }
   
   public void testBuild() {
-    WxXmlOutVideoMessage m = WxXmlOutMessage.VIDEO()
+    WxMpXmlOutVideoMessage m = WxMpXmlOutMessage.VIDEO()
           .mediaId("media_id")
           .fromUser("fromUser")
           .toUser("toUser")
diff --git a/src/test/java/me/chanjar/weixin/bean/WxXmlOutVoiceMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutVoiceMessageTest.java
similarity index 87%
rename from src/test/java/me/chanjar/weixin/bean/WxXmlOutVoiceMessageTest.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutVoiceMessageTest.java
index e5960875..65644f3d 100644
--- a/src/test/java/me/chanjar/weixin/bean/WxXmlOutVoiceMessageTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxXmlOutVoiceMessageTest.java
@@ -1,4 +1,4 @@
-package me.chanjar.weixin.bean;
+package me.chanjar.weixin.mp.bean;
 
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -7,7 +7,7 @@ import org.testng.annotations.Test;
 public class WxXmlOutVoiceMessageTest {
 
   public void test() {
-    WxXmlOutVoiceMessage m = new WxXmlOutVoiceMessage();
+    WxMpXmlOutVoiceMessage m = new WxMpXmlOutVoiceMessage();
     m.setMediaId("ddfefesfsdfef");
     m.setCreateTime(1122l);
     m.setFromUserName("from");
@@ -26,7 +26,7 @@ public class WxXmlOutVoiceMessageTest {
   }
   
   public void testBuild() {
-    WxXmlOutVoiceMessage m = WxXmlOutMessage.VOICE().mediaId("ddfefesfsdfef").fromUser("from").toUser("to").build();
+    WxMpXmlOutVoiceMessage m = WxMpXmlOutMessage.VOICE().mediaId("ddfefesfsdfef").fromUser("from").toUser("to").build();
     String expected = "" 
         + ""
         + ""
diff --git a/src/test/java/me/chanjar/weixin/demo/WxTestConfigStorage.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java
similarity index 63%
rename from src/test/java/me/chanjar/weixin/demo/WxTestConfigStorage.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java
index d5921e0c..430d327d 100644
--- a/src/test/java/me/chanjar/weixin/demo/WxTestConfigStorage.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java
@@ -1,6 +1,6 @@
-package me.chanjar.weixin.demo;
+package me.chanjar.weixin.mp.demo;
 
-import me.chanjar.weixin.api.WxInMemoryConfigStorage;
+import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
 import org.xml.sax.InputSource;
 
 import javax.xml.bind.JAXBContext;
@@ -16,7 +16,7 @@ import java.io.InputStream;
  */
 @XmlRootElement(name = "xml")
 @XmlAccessorType(XmlAccessType.FIELD)
-class WxTestConfigStorage extends WxInMemoryConfigStorage {
+class WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage {
 
   @Override
   public String toString() {
@@ -25,11 +25,11 @@ class WxTestConfigStorage extends WxInMemoryConfigStorage {
   }
 
 
-  public static WxTestConfigStorage fromXml(InputStream is) throws JAXBException {
-    Unmarshaller um = JAXBContext.newInstance(WxTestConfigStorage.class).createUnmarshaller();
+  public static WxMpDemoInMemoryConfigStorage fromXml(InputStream is) throws JAXBException {
+    Unmarshaller um = JAXBContext.newInstance(WxMpDemoInMemoryConfigStorage.class).createUnmarshaller();
     InputSource inputSource = new InputSource(is);
     inputSource.setEncoding("utf-8");
-    return (WxTestConfigStorage) um.unmarshal(inputSource);
+    return (WxMpDemoInMemoryConfigStorage) um.unmarshal(inputSource);
   }
 
 }
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java
new file mode 100644
index 00000000..e0bb6498
--- /dev/null
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java
@@ -0,0 +1,20 @@
+package me.chanjar.weixin.mp.demo;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.servlet.ServletHandler;
+
+/**
+ * @author Daniel Qian
+ */
+public class WxMpDemoServer {
+  public static void main(String[] args) throws Exception {
+    Server server = new Server(8080);
+
+    ServletHandler handler = new ServletHandler();
+    server.setHandler(handler);
+
+    handler.addServletWithMapping(WxMpDemoServlet.class, "/*");
+    server.start();
+    server.join();
+  }
+}
diff --git a/src/test/java/me/chanjar/weixin/demo/WxTestServlet.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServlet.java
similarity index 61%
rename from src/test/java/me/chanjar/weixin/demo/WxTestServlet.java
rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServlet.java
index aa042916..7ceb2e26 100644
--- a/src/test/java/me/chanjar/weixin/demo/WxTestServlet.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServlet.java
@@ -1,9 +1,9 @@
-package me.chanjar.weixin.demo;
+package me.chanjar.weixin.mp.demo;
 
-import me.chanjar.weixin.api.*;
-import me.chanjar.weixin.bean.WxXmlMessage;
-import me.chanjar.weixin.bean.WxXmlOutMessage;
-import me.chanjar.weixin.bean.WxXmlOutTextMessage;
+import me.chanjar.weixin.mp.api.*;
+import me.chanjar.weixin.mp.bean.WxMpXmlMessage;
+import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage;
+import me.chanjar.weixin.mp.bean.WxMpXmlOutTextMessage;
 import org.apache.commons.lang3.StringUtils;
 
 import javax.servlet.ServletException;
@@ -18,34 +18,34 @@ import java.util.Map;
 /**
  * @author Daniel Qian
  */
-public class WxTestServlet extends HttpServlet {
+public class WxMpDemoServlet extends HttpServlet {
 
-  protected WxService wxService;
-  protected WxConfigStorage wxConfigStorage;
-  protected WxMessageRouter wxMessageRouter;
+  protected WxMpService wxMpService;
+  protected WxMpConfigStorage wxMpConfigStorage;
+  protected WxMpMessageRouter wxMpMessageRouter;
 
   @Override public void init() throws ServletException {
     //
     super.init();
     try {
       InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml");
-      WxTestConfigStorage config = WxTestConfigStorage.fromXml(is1);
+      WxMpDemoInMemoryConfigStorage config = WxMpDemoInMemoryConfigStorage.fromXml(is1);
 
-      wxConfigStorage = config;
-      wxService = new WxServiceImpl();
-      wxService.setWxConfigStorage(config);
+      wxMpConfigStorage = config;
+      wxMpService = new WxMpServiceImpl();
+      wxMpService.setWxMpConfigStorage(config);
 
-      WxMessageHandler handler = new WxMessageHandler() {
-        @Override public WxXmlOutMessage handle(WxXmlMessage wxMessage, Map context) {
-          WxXmlOutTextMessage m
-              = WxXmlOutMessage.TEXT().content("测试加密消息").fromUser(wxMessage.getToUserName())
+      WxMpMessageHandler handler = new WxMpMessageHandler() {
+        @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context) {
+          WxMpXmlOutTextMessage m
+              = WxMpXmlOutMessage.TEXT().content("测试加密消息").fromUser(wxMessage.getToUserName())
               .toUser(wxMessage.getFromUserName()).build();
           return m;
         }
       };
 
-      wxMessageRouter = new WxMessageRouter();
-      wxMessageRouter
+      wxMpMessageRouter = new WxMpMessageRouter();
+      wxMpMessageRouter
           .rule()
           .async(false)
           .content("哈哈") // 拦截内容为“哈哈”的消息
@@ -67,7 +67,7 @@ public class WxTestServlet extends HttpServlet {
     response.setContentType("text/html;charset=utf-8");
     response.setStatus(HttpServletResponse.SC_OK);
 
-    if (!wxService.checkSignature(timestamp, nonce, signature)) {
+    if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
       // 消息签名不正确,说明不是公众平台发过来的消息
       response.getWriter().println("非法请求");
       return;
@@ -84,30 +84,27 @@ public class WxTestServlet extends HttpServlet {
         "raw" :
         request.getParameter("encrypt_type");
 
-    WxXmlMessage inMessage = null;
+    WxMpXmlMessage inMessage = null;
 
     if ("raw".equals(encryptType)) {
       // 明文传输的消息
-      inMessage = WxXmlMessage.fromXml(request.getInputStream());
+      inMessage = WxMpXmlMessage.fromXml(request.getInputStream());
     } else if ("aes".equals(encryptType)) {
       // 是aes加密的消息
       String msgSignature = request.getParameter("msg_signature");
-      inMessage = WxXmlMessage.fromEncryptedXml(
-          request.getInputStream(),
-          wxConfigStorage,
-          timestamp, nonce, msgSignature);
+      inMessage = WxMpXmlMessage.fromEncryptedXml(request.getInputStream(), wxMpConfigStorage, timestamp, nonce, msgSignature);
     } else {
       response.getWriter().println("不可识别的加密类型");
       return;
     }
 
-    WxXmlOutMessage outMessage = wxMessageRouter.route(inMessage);
+    WxMpXmlOutMessage outMessage = wxMpMessageRouter.route(inMessage);
 
     if (outMessage != null) {
       if ("raw".equals(encryptType)) {
         response.getWriter().write(outMessage.toXml());
       } else if ("aes".equals(encryptType)) {
-        response.getWriter().write(outMessage.toEncryptedXml(wxConfigStorage));
+        response.getWriter().write(outMessage.toEncryptedXml(wxMpConfigStorage));
       }
       return;
     }
diff --git a/weixin-java-mp/src/test/resources/mm.jpeg b/weixin-java-mp/src/test/resources/mm.jpeg
new file mode 100644
index 00000000..183699e9
Binary files /dev/null and b/weixin-java-mp/src/test/resources/mm.jpeg differ
diff --git a/weixin-java-mp/src/test/resources/mm.mp3 b/weixin-java-mp/src/test/resources/mm.mp3
new file mode 100644
index 00000000..d818e510
Binary files /dev/null and b/weixin-java-mp/src/test/resources/mm.mp3 differ
diff --git a/weixin-java-mp/src/test/resources/mm.mp4 b/weixin-java-mp/src/test/resources/mm.mp4
new file mode 100644
index 00000000..ff74317a
Binary files /dev/null and b/weixin-java-mp/src/test/resources/mm.mp4 differ
diff --git a/src/test/resources/test-config.sample.xml b/weixin-java-mp/src/test/resources/test-config.sample.xml
similarity index 100%
rename from src/test/resources/test-config.sample.xml
rename to weixin-java-mp/src/test/resources/test-config.sample.xml
diff --git a/weixin-java-mp/src/test/resources/testng.xml b/weixin-java-mp/src/test/resources/testng.xml
new file mode 100644
index 00000000..8987f762
--- /dev/null
+++ b/weixin-java-mp/src/test/resources/testng.xml
@@ -0,0 +1,31 @@
+
+
+
+	
+		
+			
+			
+			
+			
+			
+			
+			
+			
+			
+			
+		
+	
+
+	
+		
+			
+			
+			
+			
+			
+			
+			
+			
+		
+	
+