| @@ -16,6 +16,6 @@ target | |||
| .project | |||
| .classpath | |||
| src/test/resources/test-config.xml | |||
| sw-pom.xml | |||
| *.iml | |||
| test-config.xml | |||
| @@ -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 | |||
| <dependency> | |||
| <groupId>me.chanjar</groupId> | |||
| <artifactId>weixin-java-tools</artifactId> | |||
| <version>1.0.2</version> | |||
| <artifactId>weixin-java-mp</artifactId> | |||
| <version>1.0.3</version> | |||
| </dependency> | |||
| ``` | |||
| 如果要使用``*-SNAPSHOT``版,则需要在你的``pom.xml``中添加这段: | |||
| 如果要开发企业号应用,在你的maven项目中添加: | |||
| ```xml | |||
| <repositories> | |||
| <repository> | |||
| <snapshots /> | |||
| <id>sonatype snapshots</id> | |||
| <url>https://oss.sonatype.org/content/repositories/snapshots/</url> | |||
| </repository> | |||
| </repositories> | |||
| ``` | |||
| ## 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); | |||
| <dependency> | |||
| <groupId>me.chanjar</groupId> | |||
| <artifactId>weixin-java-cp</artifactId> | |||
| <version>1.0.3</version> | |||
| </dependency> | |||
| ``` | |||
| @@ -4,10 +4,11 @@ | |||
| xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <groupId>me.chanjar</groupId> | |||
| <artifactId>weixin-java-tools</artifactId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>1.0.3-SNAPSHOT</version> | |||
| <name>WeiXin Java Tools</name> | |||
| <description>用于开发微信公众号的Java工具</description> | |||
| <packaging>pom</packaging> | |||
| <name>WeiXin Java Tools - Parent</name> | |||
| <description>微信公众号、企业号上级POM</description> | |||
| <url>https://github.com/chanjarster/weixin-java-tools</url> | |||
| <licenses> | |||
| @@ -30,19 +31,20 @@ | |||
| <url>https://github.com/chanjarster/weixin-java-tools</url> | |||
| </scm> | |||
| <modules> | |||
| <module>weixin-java-common</module> | |||
| <module>weixin-java-cp</module> | |||
| <module>weixin-java-mp</module> | |||
| </modules> | |||
| <properties> | |||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||
| <downloadJavadocs>true</downloadJavadocs> | |||
| <downloadSources>true</downloadSources> | |||
| <httpclient.version>4.3.5</httpclient.version> | |||
| </properties> | |||
| <dependencies> | |||
| <dependency> | |||
| <groupId>junit</groupId> | |||
| <artifactId>junit</artifactId> | |||
| <version>4.11</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.apache.httpcomponents</groupId> | |||
| <artifactId>fluent-hc</artifactId> | |||
| @@ -63,34 +65,11 @@ | |||
| <artifactId>jaxb-impl</artifactId> | |||
| <version>2.2.7</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.apache.oltu.oauth2</groupId> | |||
| <artifactId>org.apache.oltu.oauth2.client</artifactId> | |||
| <version>1.0.0</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.google.code.gson</groupId> | |||
| <artifactId>gson</artifactId> | |||
| <version>2.2.2</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.testng</groupId> | |||
| <artifactId>testng</artifactId> | |||
| <version>6.8.7</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.mockito</groupId> | |||
| <artifactId>mockito-all</artifactId> | |||
| <version>1.9.5</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.google.inject</groupId> | |||
| <artifactId>guice</artifactId> | |||
| <version>3.0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.apache.commons</groupId> | |||
| <artifactId>commons-lang3</artifactId> | |||
| @@ -106,20 +85,49 @@ | |||
| <artifactId>commons-io</artifactId> | |||
| <version>2.4</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.eclipse.jetty</groupId> | |||
| <artifactId>jetty-server</artifactId> | |||
| <version>9.3.0.M0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.eclipse.jetty</groupId> | |||
| <artifactId>jetty-servlet</artifactId> | |||
| <version>9.3.0.M0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| </dependencies> | |||
| <dependencyManagement> | |||
| <dependencies> | |||
| <dependency> | |||
| <groupId>junit</groupId> | |||
| <artifactId>junit</artifactId> | |||
| <version>4.11</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.google.inject</groupId> | |||
| <artifactId>guice</artifactId> | |||
| <version>3.0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.testng</groupId> | |||
| <artifactId>testng</artifactId> | |||
| <version>6.8.7</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.mockito</groupId> | |||
| <artifactId>mockito-all</artifactId> | |||
| <version>1.9.5</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.eclipse.jetty</groupId> | |||
| <artifactId>jetty-server</artifactId> | |||
| <version>9.3.0.M0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.eclipse.jetty</groupId> | |||
| <artifactId>jetty-servlet</artifactId> | |||
| <version>9.3.0.M0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| </dependencies> | |||
| </dependencyManagement> | |||
| <distributionManagement> | |||
| <snapshotRepository> | |||
| <id>ossrh</id> | |||
| @@ -186,17 +194,17 @@ | |||
| </profiles> | |||
| <build> | |||
| <pluginManagement> | |||
| <plugins> | |||
| <plugin> | |||
| <groupId>org.apache.maven.plugins</groupId> | |||
| <artifactId>maven-surefire-plugin</artifactId> | |||
| <version>2.17</version> | |||
| </plugin> | |||
| </plugins> | |||
| </pluginManagement> | |||
| <plugins> | |||
| <plugin> | |||
| <groupId>org.apache.maven.plugins</groupId> | |||
| <artifactId>maven-surefire-plugin</artifactId> | |||
| <version>2.17</version> | |||
| <configuration> | |||
| <suiteXmlFiles> | |||
| <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> | |||
| </suiteXmlFiles> | |||
| </configuration> | |||
| </plugin> | |||
| <plugin> | |||
| <groupId>org.sonatype.plugins</groupId> | |||
| <artifactId>nexus-staging-maven-plugin</artifactId> | |||
| @@ -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<Integer, String> errMap = new HashMap<Integer, String>(); | |||
| 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(); | |||
| } | |||
| } | |||
| @@ -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(); | |||
| } | |||
| } | |||
| @@ -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<WxUser> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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<WxUserList> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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()); | |||
| } | |||
| } | |||
| @@ -1,35 +0,0 @@ | |||
| <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > | |||
| <suite name="Weixin-java-tool-suite" verbose="1"> | |||
| <test name="API_Test"> | |||
| <classes> | |||
| <class name="me.chanjar.weixin.api.WxBaseAPITest" /> | |||
| <class name="me.chanjar.weixin.api.WxCustomMessageAPITest" /> | |||
| <class name="me.chanjar.weixin.api.WxMenuAPITest" /> | |||
| <class name="me.chanjar.weixin.api.WxGroupAPITest" /> | |||
| <class name="me.chanjar.weixin.api.WxMassMessageAPITest" /> | |||
| <class name="me.chanjar.weixin.api.WxMediaAPITest" /> | |||
| <class name="me.chanjar.weixin.api.WxUserAPITest" /> | |||
| <class name="me.chanjar.weixin.api.WxQrCodeAPITest" /> | |||
| <class name="me.chanjar.weixin.api.WxShortUrlAPITest" /> | |||
| <class name="me.chanjar.weixin.api.WxMessageRouterTest" /> | |||
| </classes> | |||
| </test> | |||
| <test name="Bean_Test"> | |||
| <classes> | |||
| <class name="me.chanjar.weixin.bean.WxAccessTokenTest" /> | |||
| <class name="me.chanjar.weixin.bean.WxCustomMessageTest" /> | |||
| <class name="me.chanjar.weixin.bean.WxErrorTest" /> | |||
| <class name="me.chanjar.weixin.bean.WxMenuTest" /> | |||
| <class name="me.chanjar.weixin.bean.WxXmlMessageTest" /> | |||
| <class name="me.chanjar.weixin.bean.WxXmlOutImageMessageTest" /> | |||
| <class name="me.chanjar.weixin.bean.WxXmlOutMusicMessageTest" /> | |||
| <class name="me.chanjar.weixin.bean.WxXmlOutNewsMessageTest" /> | |||
| <class name="me.chanjar.weixin.bean.WxXmlOutVideoMessageTest" /> | |||
| <class name="me.chanjar.weixin.bean.WxXmlOutVoiceMessageTest" /> | |||
| <class name="me.chanjar.weixin.bean.WxXmlOutTextMessageTest" /> | |||
| <class name="me.chanjar.weixin.util.crypto.WxCryptUtilTest" /> | |||
| </classes> | |||
| </test> | |||
| </suite> | |||
| @@ -0,0 +1,68 @@ | |||
| <?xml version="1.0"?> | |||
| <project | |||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||
| xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <parent> | |||
| <groupId>me.chanjar</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>1.0.3-SNAPSHOT</version> | |||
| </parent> | |||
| <artifactId>weixin-java-common</artifactId> | |||
| <name>WeiXin Java Tools - Common</name> | |||
| <description>微信公众号、企业号Java SDK Common</description> | |||
| <dependencies> | |||
| <dependency> | |||
| <groupId>junit</groupId> | |||
| <artifactId>junit</artifactId> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.testng</groupId> | |||
| <artifactId>testng</artifactId> | |||
| <version>6.8.7</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.mockito</groupId> | |||
| <artifactId>mockito-all</artifactId> | |||
| <version>1.9.5</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.google.inject</groupId> | |||
| <artifactId>guice</artifactId> | |||
| <version>3.0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.eclipse.jetty</groupId> | |||
| <artifactId>jetty-server</artifactId> | |||
| <version>9.3.0.M0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.eclipse.jetty</groupId> | |||
| <artifactId>jetty-servlet</artifactId> | |||
| <version>9.3.0.M0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| </dependencies> | |||
| <build> | |||
| <plugins> | |||
| <plugin> | |||
| <groupId>org.apache.maven.plugins</groupId> | |||
| <artifactId>maven-surefire-plugin</artifactId> | |||
| <configuration> | |||
| <suiteXmlFiles> | |||
| <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> | |||
| </suiteXmlFiles> | |||
| </configuration> | |||
| </plugin> | |||
| </plugins> | |||
| </build> | |||
| </project> | |||
| @@ -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"; | |||
| /////////////////////// | |||
| // 文件类型 | |||
| @@ -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; | |||
| @@ -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 { | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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 | |||
| @@ -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 { | |||
| @@ -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<Byte> byteContainer = new ArrayList<Byte>(); | |||
| 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(); | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| @@ -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 { | |||
| @@ -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不正确"); | |||
| } | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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<File, String> { | |||
| @@ -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<WxMediaUploadResult, File> { | |||
| @@ -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 <T> 返回值类型 | |||
| * @param <E> 请求参数类型 | |||
| @@ -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<String, String> { | |||
| @@ -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<String, String> { | |||
| @@ -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<String> { | |||
| @@ -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; | |||
| @@ -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; | |||
| @@ -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<WxError> { | |||
| if (wxErrorJsonObject.get("errmsg") != null && !wxErrorJsonObject.get("errmsg").isJsonNull()) { | |||
| wxError.setErrorMsg(GsonHelper.getAsString(wxErrorJsonObject.get("errmsg"))); | |||
| } | |||
| wxError.setJson(json.toString()); | |||
| return wxError; | |||
| } | |||
| @@ -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(); | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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<WxMenu>, JsonDeserializer<WxMenu> { | |||
| @@ -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<String, String> { | |||
| @@ -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<String, String> { | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.bean; | |||
| package me.chanjar.weixin.common.bean; | |||
| import org.testng.Assert; | |||
| import org.testng.annotations.Test; | |||
| @@ -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 { | |||
| @@ -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 { | |||
| @@ -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); | |||
| @@ -0,0 +1,12 @@ | |||
| <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > | |||
| <suite name="Weixin-java-tool-suite" verbose="1"> | |||
| <test name="Bean_Test"> | |||
| <classes> | |||
| <class name="me.chanjar.weixin.common.bean.WxAccessTokenTest" /> | |||
| <class name="me.chanjar.weixin.common.bean.WxErrorTest" /> | |||
| <class name="me.chanjar.weixin.common.bean.WxMenuTest" /> | |||
| <class name="me.chanjar.weixin.common.util.crypto.WxCryptUtilTest" /> | |||
| </classes> | |||
| </test> | |||
| </suite> | |||
| @@ -0,0 +1,73 @@ | |||
| <?xml version="1.0"?> | |||
| <project | |||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||
| xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <parent> | |||
| <groupId>me.chanjar</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>1.0.3-SNAPSHOT</version> | |||
| </parent> | |||
| <artifactId>weixin-java-cp</artifactId> | |||
| <name>WeiXin Java Tools - CP</name> | |||
| <description>微信企业号Java SDK</description> | |||
| <dependencies> | |||
| <dependency> | |||
| <groupId>me.chanjar</groupId> | |||
| <artifactId>weixin-java-common</artifactId> | |||
| <version>${project.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>junit</groupId> | |||
| <artifactId>junit</artifactId> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.testng</groupId> | |||
| <artifactId>testng</artifactId> | |||
| <version>6.8.7</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.mockito</groupId> | |||
| <artifactId>mockito-all</artifactId> | |||
| <version>1.9.5</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.google.inject</groupId> | |||
| <artifactId>guice</artifactId> | |||
| <version>3.0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.eclipse.jetty</groupId> | |||
| <artifactId>jetty-server</artifactId> | |||
| <version>9.3.0.M0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.eclipse.jetty</groupId> | |||
| <artifactId>jetty-servlet</artifactId> | |||
| <version>9.3.0.M0</version> | |||
| <scope>test</scope> | |||
| </dependency> | |||
| </dependencies> | |||
| <build> | |||
| <plugins> | |||
| <plugin> | |||
| <groupId>org.apache.maven.plugins</groupId> | |||
| <artifactId>maven-surefire-plugin</artifactId> | |||
| <configuration> | |||
| <suiteXmlFiles> | |||
| <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> | |||
| </suiteXmlFiles> | |||
| </configuration> | |||
| </plugin> | |||
| </plugins> | |||
| </build> | |||
| </project> | |||
| @@ -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(); | |||
| } | |||
| @@ -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 + | |||
| '}'; | |||
| } | |||
| } | |||
| @@ -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<String, Object> context); | |||
| } | |||
| @@ -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<String, Object> context); | |||
| } | |||
| @@ -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; | |||
| /** | |||
| * <pre> | |||
| * 微信消息路由器,通过代码化的配置,把来自微信的消息交给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); | |||
| * | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public class WxCpMessageRouter { | |||
| private final List<Rule> rules = new ArrayList<Rule>(); | |||
| 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<Rule> matchRules = new ArrayList<Rule>(); | |||
| // 收集匹配的规则 | |||
| 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<WxCpMessageHandler> handlers = new ArrayList<WxCpMessageHandler>(); | |||
| private List<WxCpMessageInterceptor> interceptors = new ArrayList<WxCpMessageInterceptor>(); | |||
| 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<String, Object> context = new HashMap<String, Object>(); | |||
| // 如果拦截器不通过 | |||
| 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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 { | |||
| /** | |||
| * <pre> | |||
| * 验证推送过来的消息的正确性 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=验证消息真实性 | |||
| * </pre> | |||
| * | |||
| * @param msgSignature | |||
| * @param timestamp | |||
| * @param nonce | |||
| * @param data 微信传输过来的数据,有可能是echoStr,有可能是xml消息 | |||
| * @return | |||
| */ | |||
| public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data); | |||
| /** | |||
| * <pre> | |||
| * 用在二次验证的时候 | |||
| * 企业在员工验证成功后,调用本方法告诉企业号平台该员工关注成功。 | |||
| * </pre> | |||
| * | |||
| * @param userId | |||
| */ | |||
| public void userAuthenticated(String userId) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获取access_token,本方法线程安全 | |||
| * 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限 | |||
| * 另:本service的所有方法都会在access_token过期是调用此方法 | |||
| * 程序员在非必要情况下尽量不要主动调用此方法 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | |||
| * </pre> | |||
| * | |||
| * @throws me.chanjar.weixin.common.exception.WxErrorException | |||
| */ | |||
| public void accessTokenRefresh() throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 上传多媒体文件 | |||
| * 上传的多媒体文件有格式和大小限制,如下: | |||
| * 图片(image): 1M,支持JPG格式 | |||
| * 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | |||
| * 视频(video):10MB,支持MP4格式 | |||
| * 缩略图(thumb):64KB,支持JPG格式 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||
| * </pre> | |||
| * | |||
| * @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; | |||
| /** | |||
| * <pre> | |||
| * 下载多媒体文件 | |||
| * 根据微信文档,视频文件下载不了,会返回null | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||
| * </pre> | |||
| * | |||
| * @return 保存到本地的临时文件 | |||
| * @throws WxErrorException | |||
| * @params media_id | |||
| */ | |||
| public File mediaDownload(String media_id) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 发送消息 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=发送消息 | |||
| * </pre> | |||
| * | |||
| * @param message | |||
| * @throws WxErrorException | |||
| */ | |||
| public void messageSend(WxCpMessage message) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 自定义菜单创建接口 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口 | |||
| * </pre> | |||
| * | |||
| * @param menu | |||
| * @throws WxErrorException | |||
| */ | |||
| public void menuCreate(WxMenu menu) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 自定义菜单删除接口 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口 | |||
| * </pre> | |||
| * | |||
| * @throws WxErrorException | |||
| */ | |||
| public void menuDelete() throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 自定义菜单查询接口 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口 | |||
| * </pre> | |||
| * | |||
| * @return | |||
| * @throws WxErrorException | |||
| */ | |||
| public WxMenu menuGet() throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 部门管理接口 - 创建部门 | |||
| * 最多支持创建500个部门 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||
| * </pre> | |||
| * | |||
| * @param depart 部门 | |||
| * @return 部门id | |||
| * @throws WxErrorException | |||
| */ | |||
| public Integer departCreate(WxCpDepart depart) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 部门管理接口 - 查询所有部门 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||
| * </pre> | |||
| * | |||
| * @return | |||
| * @throws WxErrorException | |||
| */ | |||
| public List<WxCpDepart> departGet() throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 部门管理接口 - 修改部门名 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口 | |||
| * 如果id为0(未部门),1(黑名单),2(星标组),或者不存在的id,微信会返回系统繁忙的错误 | |||
| * </pre> | |||
| * | |||
| * @param group 要更新的group,group的id,name必须设置 | |||
| * @throws WxErrorException | |||
| */ | |||
| public void departUpdate(WxCpDepart group) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 部门管理接口 - 删除部门 | |||
| * </pre> | |||
| * | |||
| * @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<WxCpUser> 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<WxCpTag> tagGet() throws WxErrorException; | |||
| /** | |||
| * 获取标签成员 | |||
| * | |||
| * @param tagId | |||
| * @return | |||
| */ | |||
| public List<WxCpUser> tagGetUsers(String tagId) throws WxErrorException; | |||
| /** | |||
| * 增加标签成员 | |||
| * | |||
| * @param tagId | |||
| * @param userIds | |||
| */ | |||
| public void tagAddUsers(String tagId, List<String> userIds) throws WxErrorException; | |||
| /** | |||
| * 移除标签成员 | |||
| * | |||
| * @param tagId | |||
| * @param userIds | |||
| */ | |||
| public void tagRemoveUsers(String tagId, List<String> userIds) throws WxErrorException; | |||
| /** | |||
| * 注入 {@link WxCpConfigStorage} 的实现 | |||
| * | |||
| * @param wxConfigProvider | |||
| */ | |||
| public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider); | |||
| } | |||
| @@ -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<Integer> retryTimes = new ThreadLocal<Integer>(); | |||
| 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<WxCpDepart> 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<List<WxCpDepart>>() { }.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<WxCpUser> 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<List<WxCpUser>>() { }.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<WxCpTag> 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<List<WxCpTag>>() { }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| public List<WxCpUser> 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<List<WxCpUser>>() { }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| public void tagAddUsers(String tagId, List<String> 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<String> 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, E> T execute(RequestExecutor<T, E> 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; | |||
| } | |||
| } | |||
| @@ -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 + | |||
| '}'; | |||
| } | |||
| } | |||
| @@ -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<WxArticle> articles = new ArrayList<WxArticle>(); | |||
| 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; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 请使用 | |||
| * {@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} | |||
| * </pre> | |||
| * @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<WxArticle> getArticles() { | |||
| return articles; | |||
| } | |||
| public void setArticles(List<WxArticle> 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(); | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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<Attr> extAttrs = new ArrayList<Attr>(); | |||
| 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<Attr> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| /** | |||
| * <pre> | |||
| * 微信推送过来的消息,也是同步回复给用户的消息,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=接收语音识别结果 | |||
| * </pre> | |||
| * | |||
| * @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; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 当接受用户消息时,可能会获得以下值: | |||
| * {@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} | |||
| * </pre> | |||
| * | |||
| * @return | |||
| */ | |||
| public String getMsgType() { | |||
| return msgType; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 当发送消息的时候使用: | |||
| * {@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} | |||
| * </pre> | |||
| * | |||
| * @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<Item> picList = new ArrayList<Item>(); | |||
| public Long getCount() { | |||
| return count; | |||
| } | |||
| public void setCount(Long count) { | |||
| this.count = count; | |||
| } | |||
| public List<Item> 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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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(); | |||
| } | |||
| } | |||
| @@ -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<Item> articles = new ArrayList<Item>(); | |||
| public WxXmlOutMewsMessage() { | |||
| this.msgType = WxConsts.XML_MSG_NEWS; | |||
| public WxCpXmlOutMewsMessage() { | |||
| this.msgType = WxCpConsts.XML_MSG_NEWS; | |||
| } | |||
| public int getArticleCount() { | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| } | |||
| } | |||
| @@ -0,0 +1,42 @@ | |||
| package me.chanjar.weixin.cp.bean.messagebuilder; | |||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| public class BaseBuilder<T> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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 | |||
| * <pre> | |||
| * 用法: WxCustomMessage m = WxCustomMessage.FILE().mediaId(...).toUser(...).build(); | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public final class FileBuilder extends BaseBuilder<FileBuilder> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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 | |||
| * <pre> | |||
| * 用法: WxCustomMessage m = WxCustomMessage.IMAGE().mediaId(...).toUser(...).build(); | |||
| * </pre> | |||
| * @author chanjarster | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public final class ImageBuilder extends BaseBuilder<ImageBuilder> { | |||
| @@ -23,8 +23,8 @@ public final class ImageBuilder extends BaseBuilder<ImageBuilder> { | |||
| return this; | |||
| } | |||
| public WxCustomMessage build() { | |||
| WxCustomMessage m = super.build(); | |||
| public WxCpMessage build() { | |||
| WxCpMessage m = super.build(); | |||
| m.setMediaId(this.mediaId); | |||
| return m; | |||
| } | |||
| @@ -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 | |||
| * <pre> | |||
| * 用法: | |||
| * WxCustomMessage m = WxCustomMessage.NEWS().addArticle(article).toUser(...).build(); | |||
| * </pre> | |||
| * @author chanjarster | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public final class NewsBuilder extends BaseBuilder<NewsBuilder> { | |||
| private List<WxCustomMessage.WxArticle> articles = new ArrayList<WxCustomMessage.WxArticle>(); | |||
| private List<WxCpMessage.WxArticle> articles = new ArrayList<WxCpMessage.WxArticle>(); | |||
| 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; | |||
| } | |||
| @@ -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 | |||
| * <pre> | |||
| * 用法: WxCustomMessage m = WxCustomMessage.TEXT().content(...).toUser(...).build(); | |||
| * </pre> | |||
| * @author chanjarster | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public final class TextBuilder extends BaseBuilder<TextBuilder> { | |||
| @@ -23,8 +23,8 @@ public final class TextBuilder extends BaseBuilder<TextBuilder> { | |||
| return this; | |||
| } | |||
| public WxCustomMessage build() { | |||
| WxCustomMessage m = super.build(); | |||
| public WxCpMessage build() { | |||
| WxCpMessage m = super.build(); | |||
| m.setContent(this.content); | |||
| return m; | |||
| } | |||
| @@ -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(); | |||
| * </pre> | |||
| * @author chanjarster | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public final class VideoBuilder extends BaseBuilder<VideoBuilder> { | |||
| @@ -47,8 +47,8 @@ public final class VideoBuilder extends BaseBuilder<VideoBuilder> { | |||
| 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); | |||
| @@ -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 | |||
| * <pre> | |||
| * 用法: WxCustomMessage m = WxCustomMessage.VOICE().mediaId(...).toUser(...).build(); | |||
| * </pre> | |||
| * @author chanjarster | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public final class VoiceBuilder extends BaseBuilder<VoiceBuilder> { | |||
| @@ -23,8 +23,8 @@ public final class VoiceBuilder extends BaseBuilder<VoiceBuilder> { | |||
| return this; | |||
| } | |||
| public WxCustomMessage build() { | |||
| WxCustomMessage m = super.build(); | |||
| public WxCpMessage build() { | |||
| WxCpMessage m = super.build(); | |||
| m.setMediaId(this.mediaId); | |||
| return m; | |||
| } | |||
| @@ -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<BuilderType, ValueType> { | |||
| @@ -21,7 +20,7 @@ public abstract class BaseBuilder<BuilderType, ValueType> { | |||
| 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); | |||
| @@ -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<ImageBuilder, WxCpXmlOutImageMessage> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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<NewsBuilder, WxXmlOutMewsMessage> { | |||
| public final class NewsBuilder extends BaseBuilder<NewsBuilder, WxCpXmlOutMewsMessage> { | |||
| protected final List<Item> articles = new ArrayList<Item>(); | |||
| @@ -19,8 +19,8 @@ public final class NewsBuilder extends BaseBuilder<NewsBuilder, WxXmlOutMewsMess | |||
| return this; | |||
| } | |||
| public WxXmlOutMewsMessage build() { | |||
| WxXmlOutMewsMessage m = new WxXmlOutMewsMessage(); | |||
| public WxCpXmlOutMewsMessage build() { | |||
| WxCpXmlOutMewsMessage m = new WxCpXmlOutMewsMessage(); | |||
| for(Item item : articles) { | |||
| m.addArticle(item); | |||
| } | |||
| @@ -0,0 +1,24 @@ | |||
| package me.chanjar.weixin.cp.bean.outxmlbuilder; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage; | |||
| /** | |||
| * 文本消息builder | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public final class TextBuilder extends BaseBuilder<TextBuilder, WxCpXmlOutTextMessage> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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<VideoBuilder, WxCpXmlOutVideoMessage> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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<VoiceBuilder, WxCpXmlOutVoiceMessage> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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 + "="); | |||
| } | |||
| } | |||
| @@ -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<WxCpDepart>, JsonDeserializer<WxCpDepart> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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(); | |||
| } | |||
| } | |||
| @@ -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<WxCpMessage> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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<WxCpTag>, JsonDeserializer<WxCpTag> { | |||
| 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")); | |||
| } | |||
| } | |||
| @@ -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<WxCpUser>, JsonSerializer<WxCpUser> { | |||
| 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; | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| @@ -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> T fromXml(Class<T> 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 + '\'' + | |||
| '}'; | |||
| } | |||
| } | |||
| } | |||
| @@ -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)); | |||
| } | |||
| } | |||
| @@ -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<WxCpDepart> 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()); | |||
| } | |||
| } | |||
| @@ -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<String> media_ids = new ArrayList<String>(); | |||
| @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; | |||
| } | |||
| } | |||
| @@ -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超链接:<a href=\"http://www.baidu.com\">Hello World</a>"); | |||
| wxService.messageSend(message1); | |||
| WxCpMessage message2 = WxCpMessage | |||
| .TEXT() | |||
| .agentId(configStorage.getAgentId()) | |||
| .toUser(configStorage.getUserId()) | |||
| .content("欢迎欢迎,热烈欢迎\n换行测试\n超链接:<a href=\"http://www.baidu.com\">Hello World</a>") | |||
| .build(); | |||
| wxService.messageSend(message2); | |||
| } | |||
| } | |||
| @@ -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<String, Object> 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<String, Object> context) { | |||
| sb.append(this.echoStr).append(','); | |||
| return null; | |||
| } | |||
| } | |||
| } | |||
| @@ -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<WxCpTag> tags = wxService.tagGet(); | |||
| Assert.assertNotEquals(tags.size(), 0); | |||
| } | |||
| @Test(dependsOnMethods = "testTagGet") | |||
| public void testTagAddUsers() throws Exception { | |||
| List<String> userIds = new ArrayList<String>(); | |||
| userIds.add(((ApiTestModule.WxXmlCpInMemoryConfigStorage)configStorage).getUserId()); | |||
| wxService.tagAddUsers(tagId, userIds); | |||
| } | |||
| @Test(dependsOnMethods = "testTagAddUsers") | |||
| public void testTagGetUsers() throws Exception { | |||
| List<WxCpUser> users = wxService.tagGetUsers(tagId); | |||
| Assert.assertNotEquals(users.size(), 0); | |||
| } | |||
| @Test(dependsOnMethods = "testTagGetUsers") | |||
| public void testTagRemoveUsers() throws Exception { | |||
| List<String> userIds = new ArrayList<String>(); | |||
| userIds.add(((ApiTestModule.WxXmlCpInMemoryConfigStorage)configStorage).getUserId()); | |||
| wxService.tagRemoveUsers(tagId, userIds); | |||
| } | |||
| @Test(dependsOnMethods = "testTagRemoveUsers") | |||
| public void testTagDelete() throws Exception { | |||
| wxService.tagDelete(tagId); | |||
| } | |||
| } | |||
| @@ -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<WxCpUser> users = wxCpService.departGetUsers(1, true, 0); | |||
| Assert.assertNotEquals(users.size(), 0); | |||
| } | |||
| @Test(dependsOnMethods = "testDepartGetUsers") | |||
| public void testUserDelete() throws WxErrorException { | |||
| wxCpService.userDelete("xiaohe.yang"); | |||
| } | |||
| } | |||
| @@ -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 | |||
| } | |||
| }; | |||
| } | |||
| } | |||
| @@ -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\"}]}"); | |||
| } | |||
| } | |||
| @@ -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 = "<xml>" | |||
| + "<ToUserName><![CDATA[toUser]]></ToUserName>" | |||
| + "<FromUserName><![CDATA[fromUser]]></FromUserName> " | |||
| + "<CreateTime>1348831860</CreateTime>" | |||
| + "<MsgType><![CDATA[text]]></MsgType>" | |||
| + "<Content><![CDATA[this is a test]]></Content>" | |||
| + "<MsgId>1234567890123456</MsgId>" | |||
| + "<PicUrl><![CDATA[this is a url]]></PicUrl>" | |||
| + "<MediaId><![CDATA[media_id]]></MediaId>" | |||
| + "<Format><![CDATA[Format]]></Format>" | |||
| + "<ThumbMediaId><![CDATA[thumb_media_id]]></ThumbMediaId>" | |||
| + "<Location_X>23.134521</Location_X>" | |||
| + "<Location_Y>113.358803</Location_Y>" | |||
| + "<Scale>20</Scale>" | |||
| + "<Label><![CDATA[位置信息]]></Label>" | |||
| + "<Description><![CDATA[公众平台官网链接]]></Description>" | |||
| + "<Url><![CDATA[url]]></Url>" | |||
| + "<Title><![CDATA[公众平台官网链接]]></Title>" | |||
| + "<Event><![CDATA[subscribe]]></Event>" | |||
| + "<EventKey><![CDATA[qrscene_123123]]></EventKey>" | |||
| + "<Ticket><![CDATA[TICKET]]></Ticket>" | |||
| + "<Latitude>23.137466</Latitude>" | |||
| + "<Longitude>113.352425</Longitude>" | |||
| + "<Precision>119.385040</Precision>" | |||
| + "<ScanCodeInfo>" | |||
| + " <ScanType><![CDATA[qrcode]]></ScanType>" | |||
| + " <ScanResult><![CDATA[1]]></ScanResult>" | |||
| + "</ScanCodeInfo>" | |||
| + "<SendPicsInfo>" | |||
| + " <Count>1</Count>\n" | |||
| + " <PicList>" | |||
| + " <item>" | |||
| + " <PicMd5Sum><![CDATA[1b5f7c23b5bf75682a53e7b6d163e185]]></PicMd5Sum>" | |||
| + " </item>" | |||
| + " </PicList>" | |||
| + "</SendPicsInfo>" | |||
| + "<SendLocationInfo>" | |||
| + " <Location_X><![CDATA[23]]></Location_X>\n" | |||
| + " <Location_Y><![CDATA[113]]></Location_Y>\n" | |||
| + " <Scale><![CDATA[15]]></Scale>\n" | |||
| + " <Label><![CDATA[ 广州市海珠区客村艺苑路 106号]]></Label>\n" | |||
| + " <Poiname><![CDATA[wo de poi]]></Poiname>\n" | |||
| + "</SendLocationInfo>" | |||
| + "</xml>"; | |||
| 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"); | |||
| } | |||
| } | |||
| @@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" | |||
| + "<xml>" | |||
| + "<ToUserName><![CDATA[to]]></ToUserName>" | |||
| @@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" | |||
| + "<xml>" | |||
| + "<ToUserName><![CDATA[toUser]]></ToUserName>" | |||
| + "<FromUserName><![CDATA[fromUser]]></FromUserName>" | |||
| + "<CreateTime>1122</CreateTime>" | |||
| + "<MsgType><![CDATA[news]]></MsgType>" | |||
| + " <ArticleCount>2</ArticleCount>" | |||
| + " <Articles>" | |||
| + " <item>" | |||
| + " <Title><![CDATA[title]]></Title>" | |||
| + " <Description><![CDATA[description]]></Description>" | |||
| + " <PicUrl><![CDATA[picUrl]]></PicUrl>" | |||
| + " <Url><![CDATA[url]]></Url>" | |||
| + " </item>" | |||
| + " <item>" | |||
| + " <Title><![CDATA[title]]></Title>" | |||
| + " <Description><![CDATA[description]]></Description>" | |||
| + " <PicUrl><![CDATA[picUrl]]></PicUrl>" | |||
| + " <Url><![CDATA[url]]></Url>" | |||
| + " </item>" | |||
| + " </Articles>" | |||
| + "</xml>"; | |||
| 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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" | |||
| + "<xml>" | |||
| + "<ToUserName><![CDATA[toUser]]></ToUserName>" | |||
| + "<FromUserName><![CDATA[fromUser]]></FromUserName>" | |||
| + "<CreateTime>1122</CreateTime>" | |||
| + "<MsgType><![CDATA[news]]></MsgType>" | |||
| + " <ArticleCount>2</ArticleCount>" | |||
| + " <Articles>" | |||
| + " <item>" | |||
| + " <Title><![CDATA[title]]></Title>" | |||
| + " <Description><![CDATA[description]]></Description>" | |||
| + " <PicUrl><![CDATA[picUrl]]></PicUrl>" | |||
| + " <Url><![CDATA[url]]></Url>" | |||
| + " </item>" | |||
| + " <item>" | |||
| + " <Title><![CDATA[title]]></Title>" | |||
| + " <Description><![CDATA[description]]></Description>" | |||
| + " <PicUrl><![CDATA[picUrl]]></PicUrl>" | |||
| + " <Url><![CDATA[url]]></Url>" | |||
| + " </item>" | |||
| + " </Articles>" | |||
| + "</xml>"; | |||
| System.out.println(m.toXml()); | |||
| Assert.assertEquals( | |||
| m | |||
| .toXml() | |||
| .replaceAll("\\s", "") | |||
| .replaceAll("<CreateTime>.*?</CreateTime>", ""), | |||
| expected | |||
| .replaceAll("\\s", "") | |||
| .replaceAll("<CreateTime>.*?</CreateTime>", "") | |||
| ); | |||
| } | |||
| } | |||
| @@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" | |||
| + "<xml>" | |||
| + "<ToUserName><![CDATA[to]]></ToUserName>" | |||
| + "<FromUserName><![CDATA[from]]></FromUserName>" | |||
| + "<CreateTime>1122</CreateTime>" | |||
| + "<MsgType><![CDATA[text]]></MsgType>" | |||
| + "<Content><![CDATA[content]]></Content>" | |||
| + "</xml>"; | |||
| 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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" | |||
| + "<xml>" | |||
| + "<ToUserName><![CDATA[to]]></ToUserName>" | |||
| + "<FromUserName><![CDATA[from]]></FromUserName>" | |||
| + "<CreateTime>1122</CreateTime>" | |||
| + "<MsgType><![CDATA[text]]></MsgType>" | |||
| + "<Content><![CDATA[content]]></Content>" | |||
| + "</xml>"; | |||
| System.out.println(m.toXml()); | |||
| Assert.assertEquals( | |||
| m | |||
| .toXml() | |||
| .replaceAll("\\s", "") | |||
| .replaceAll("<CreateTime>.*?</CreateTime>", ""), | |||
| expected | |||
| .replaceAll("\\s", "") | |||
| .replaceAll("<CreateTime>.*?</CreateTime>", "") | |||
| ); | |||
| } | |||
| } | |||
| @@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" | |||
| + "<xml>" | |||
| + "<ToUserName><![CDATA[toUser]]></ToUserName>" | |||
| + "<FromUserName><![CDATA[fromUser]]></FromUserName>" | |||
| + "<CreateTime>1122</CreateTime>" | |||
| + "<MsgType><![CDATA[video]]></MsgType>" | |||
| + "<Video>" | |||
| + "<MediaId><![CDATA[media_id]]></MediaId>" | |||
| + "<Title><![CDATA[title]]></Title>" | |||
| + "<Description><![CDATA[ddfff]]></Description>" | |||
| + "</Video> " | |||
| + "</xml>"; | |||
| 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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" | |||
| + "<xml>" | |||
| + "<ToUserName><![CDATA[toUser]]></ToUserName>" | |||
| + "<FromUserName><![CDATA[fromUser]]></FromUserName>" | |||
| + "<CreateTime>1122</CreateTime>" | |||
| + "<MsgType><![CDATA[video]]></MsgType>" | |||
| + "<Video>" | |||
| + "<MediaId><![CDATA[media_id]]></MediaId>" | |||
| + "<Title><![CDATA[title]]></Title>" | |||
| + "<Description><![CDATA[ddfff]]></Description>" | |||
| + "</Video> " | |||
| + "</xml>"; | |||
| System.out.println(m.toXml()); | |||
| Assert.assertEquals( | |||
| m | |||
| .toXml() | |||
| .replaceAll("\\s", "") | |||
| .replaceAll("<CreateTime>.*?</CreateTime>", ""), | |||
| expected | |||
| .replaceAll("\\s", "") | |||
| .replaceAll("<CreateTime>.*?</CreateTime>", "") | |||
| ); | |||
| } | |||
| } | |||
| @@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" | |||
| + "<xml>" | |||
| + "<ToUserName><![CDATA[to]]></ToUserName>" | |||
| + "<FromUserName><![CDATA[from]]></FromUserName>" | |||
| + "<CreateTime>1122</CreateTime>" | |||
| + "<MsgType><![CDATA[voice]]></MsgType>" | |||
| + "<Voice><MediaId><![CDATA[ddfefesfsdfef]]></MediaId></Voice>" | |||
| + "</xml>"; | |||
| 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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" | |||
| + "<xml>" | |||
| + "<ToUserName><![CDATA[to]]></ToUserName>" | |||
| + "<FromUserName><![CDATA[from]]></FromUserName>" | |||
| + "<CreateTime>1122</CreateTime>" | |||
| + "<MsgType><![CDATA[voice]]></MsgType>" | |||
| + "<Voice><MediaId><![CDATA[ddfefesfsdfef]]></MediaId></Voice>" | |||
| + "</xml>"; | |||
| System.out.println(m.toXml()); | |||
| Assert.assertEquals( | |||
| m | |||
| .toXml() | |||
| .replaceAll("\\s", "") | |||
| .replaceAll("<CreateTime>.*?</CreateTime>", ""), | |||
| expected | |||
| .replaceAll("\\s", "") | |||
| .replaceAll("<CreateTime>.*?</CreateTime>", "") | |||
| ); | |||
| } | |||
| } | |||
| @@ -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); | |||
| } | |||
| } | |||
| @@ -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(); | |||
| } | |||
| @@ -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<String, Object> 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; | |||
| } | |||
| } | |||