@@ -16,6 +16,6 @@ target | |||||
.project | .project | ||||
.classpath | .classpath | ||||
src/test/resources/test-config.xml | |||||
sw-pom.xml | sw-pom.xml | ||||
*.iml | *.iml | ||||
test-config.xml |
@@ -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,56 @@ | |||||
weixin-java-tools | |||||
=========== | |||||
微信java开发工具集,本项目主要分为两大块:微信消息路由器、微信Java API | |||||
特性列表: | |||||
1. 不基于Servlet、和其他MVC框架,仅作为工具使用,提供更多的灵活性 | |||||
2. 详尽的单元测试代码,可以拿来当example用 | |||||
3. 详尽的javadoc | |||||
4. access token过期自动刷新的功能 | |||||
5. 微信服务端繁忙自动重试的功能 | |||||
6. 提供微信错误信息的异常处理机制 | |||||
详细文档请看 [wiki](https://github.com/chanjarster/weixin-java-tools/wiki) | |||||
# Quickstart | |||||
在你的maven项目中添加: | |||||
```xml | |||||
<dependency> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-tools</artifactId> | |||||
<version>1.0.2</version> | |||||
</dependency> | |||||
``` | |||||
如果要使用``*-SNAPSHOT``版,则需要在你的``pom.xml``中添加这段: | |||||
```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 corpSecret | |||||
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); | |||||
``` |
@@ -0,0 +1,226 @@ | |||||
<?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> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-enterprise-tools</artifactId> | |||||
<version>1.0.3-SNAPSHOT</version> | |||||
<name>WeiXin Java Enterprise Tools</name> | |||||
<description>微信企业号Java SDK</description> | |||||
<url>https://github.com/chanjarster/weixin-java-tools</url> | |||||
<licenses> | |||||
<license> | |||||
<name>The Apache License, Version 2.0</name> | |||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> | |||||
</license> | |||||
</licenses> | |||||
<developers> | |||||
<developer> | |||||
<name>Daniel Qian</name> | |||||
<email>chanjarster@gmail.com</email> | |||||
</developer> | |||||
</developers> | |||||
<scm> | |||||
<connection>scm:git:https://github.com/chanjarster/weixin-java-tools.git</connection> | |||||
<developerConnection>scm:git:git@github.com:chanjarster/weixin-java-tools.git</developerConnection> | |||||
<url>https://github.com/chanjarster/weixin-java-tools</url> | |||||
</scm> | |||||
<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> | |||||
<version>${httpclient.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.apache.httpcomponents</groupId> | |||||
<artifactId>httpmime</artifactId> | |||||
<version>${httpclient.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>javax.xml.bind</groupId> | |||||
<artifactId>jaxb-api</artifactId> | |||||
<version>2.2.7</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.sun.xml.bind</groupId> | |||||
<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> | |||||
<version>3.1</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>commons-codec</groupId> | |||||
<artifactId>commons-codec</artifactId> | |||||
<version>1.9</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>commons-io</groupId> | |||||
<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> | |||||
<distributionManagement> | |||||
<snapshotRepository> | |||||
<id>ossrh</id> | |||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url> | |||||
</snapshotRepository> | |||||
<repository> | |||||
<id>ossrh</id> | |||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> | |||||
</repository> | |||||
</distributionManagement> | |||||
<profiles> | |||||
<profile> | |||||
<id>release</id> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-source-plugin</artifactId> | |||||
<version>2.2.1</version> | |||||
<executions> | |||||
<execution> | |||||
<id>attach-sources</id> | |||||
<goals> | |||||
<goal>jar-no-fork</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-javadoc-plugin</artifactId> | |||||
<version>2.9.1</version> | |||||
<executions> | |||||
<execution> | |||||
<id>attach-javadocs</id> | |||||
<goals> | |||||
<goal>jar</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
<configuration> | |||||
<charset>UTF-8</charset> | |||||
<locale>zh_CN</locale> | |||||
</configuration> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-gpg-plugin</artifactId> | |||||
<version>1.5</version> | |||||
<executions> | |||||
<execution> | |||||
<id>sign-artifacts</id> | |||||
<phase>verify</phase> | |||||
<goals> | |||||
<goal>sign</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</profile> | |||||
</profiles> | |||||
<build> | |||||
<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> | |||||
<version>1.6.3</version> | |||||
<extensions>true</extensions> | |||||
<configuration> | |||||
<serverId>ossrh</serverId> | |||||
<nexusUrl>https://oss.sonatype.org/</nexusUrl> | |||||
<autoReleaseAfterClose>false</autoReleaseAfterClose> | |||||
</configuration> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-release-plugin</artifactId> | |||||
<version>2.5</version> | |||||
<configuration> | |||||
<autoVersionSubmodules>true</autoVersionSubmodules> | |||||
<useReleaseProfile>false</useReleaseProfile> | |||||
<releaseProfiles>release</releaseProfiles> | |||||
<goals>deploy</goals> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</project> |
@@ -0,0 +1,128 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
public class WxConsts { | |||||
/////////////////////// | |||||
// 微信推送过来的消息的类型,和发送给微信xml格式消息的消息类型 | |||||
/////////////////////// | |||||
public static final String XML_MSG_TEXT = "text"; | |||||
public static final String XML_MSG_IMAGE = "image"; | |||||
public static final String XML_MSG_VOICE = "voice"; | |||||
public static final String XML_MSG_VIDEO = "video"; | |||||
public static final String XML_MSG_NEWS = "news"; | |||||
public static final String XML_MSG_MUSIC = "music"; | |||||
public static final String XML_MSG_LOCATION = "location"; | |||||
public static final String XML_MSG_LINK = "link"; | |||||
public static final String XML_MSG_EVENT = "event"; | |||||
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"; | |||||
public static final String CUSTOM_MSG_VOICE = "voice"; | |||||
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 MASS_MSG_NEWS = "mpnews"; | |||||
public static final String MASS_MSG_TEXT = "text"; | |||||
public static final String MASS_MSG_VOICE = "voice"; | |||||
public static final String MASS_MSG_IMAGE = "image"; | |||||
public static final String MASS_MSG_VIDEO = "mpvideo"; | |||||
/////////////////////// | |||||
// 群发消息后微信端推送给服务器的反馈消息 | |||||
/////////////////////// | |||||
public static final String MASS_ST_SUCCESS = "send success"; | |||||
public static final String MASS_ST_FAIL = "send fail"; | |||||
public static final String MASS_ST_涉嫌广告 = "err(10001)"; | |||||
public static final String MASS_ST_涉嫌政治 = "err(20001)"; | |||||
public static final String MASS_ST_涉嫌社会 = "err(20004)"; | |||||
public static final String MASS_ST_涉嫌色情 = "err(20002)"; | |||||
public static final String MASS_ST_涉嫌违法犯罪 = "err(20006)"; | |||||
public static final String MASS_ST_涉嫌欺诈 = "err(20008)"; | |||||
public static final String MASS_ST_涉嫌版权 = "err(20013)"; | |||||
public static final String MASS_ST_涉嫌互推_互相宣传 = "err(22000)"; | |||||
public static final String MASS_ST_涉嫌其他 = "err(21000)"; | |||||
/** | |||||
* 群发反馈消息代码所对应的文字描述 | |||||
*/ | |||||
public static final Map<String, String> MASS_ST_2_DESC = new HashMap<String, String>(); | |||||
static { | |||||
MASS_ST_2_DESC.put(MASS_ST_SUCCESS, "发送成功"); | |||||
MASS_ST_2_DESC.put(MASS_ST_FAIL, "发送失败"); | |||||
MASS_ST_2_DESC.put(MASS_ST_涉嫌广告, "涉嫌广告"); | |||||
MASS_ST_2_DESC.put(MASS_ST_涉嫌政治, "涉嫌政治"); | |||||
MASS_ST_2_DESC.put(MASS_ST_涉嫌社会, "涉嫌社会"); | |||||
MASS_ST_2_DESC.put(MASS_ST_涉嫌色情, "涉嫌色情"); | |||||
MASS_ST_2_DESC.put(MASS_ST_涉嫌违法犯罪, "涉嫌违法犯罪"); | |||||
MASS_ST_2_DESC.put(MASS_ST_涉嫌欺诈, "涉嫌欺诈"); | |||||
MASS_ST_2_DESC.put(MASS_ST_涉嫌版权, "涉嫌版权"); | |||||
MASS_ST_2_DESC.put(MASS_ST_涉嫌互推_互相宣传, "涉嫌互推_互相宣传"); | |||||
MASS_ST_2_DESC.put(MASS_ST_涉嫌其他, "涉嫌其他"); | |||||
} | |||||
/////////////////////// | |||||
// 微信端推送过来的事件类型 | |||||
/////////////////////// | |||||
public static final String EVT_SUBSCRIBE = "subscribe"; | |||||
public static final String EVT_UNSUBSCRIBE = "unsubscribe"; | |||||
public static final String EVT_SCAN = "SCAN"; | |||||
public static final String EVT_LOCATION = "LOCATION"; | |||||
public static final String EVT_CLICK = "CLICK"; | |||||
public static final String EVT_VIEW = "VIEW"; | |||||
public static final String EVT_MASS_SEND_JOB_FINISH = "MASSSENDJOBFINISH"; | |||||
public static final String EVT_SCANCODE_PUSH = "scancode_push"; | |||||
public static final String EVT_SCANCODE_WAITMSG = "scancode_waitmsg"; | |||||
public static final String EVT_PIC_SYSPHOTO = "pic_sysphoto"; | |||||
public static final String EVT_PIC_PHOTO_OR_ALBUM = "pic_photo_or_album"; | |||||
public static final String EVT_PIC_WEIXIN = "pic_weixin"; | |||||
public static final String EVT_LOCATION_SELECT = "location_select"; | |||||
/////////////////////// | |||||
// 上传多媒体文件的类型 | |||||
/////////////////////// | |||||
public static final String MEDIA_IMAGE = "image"; | |||||
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 FILE_JPG = "jpeg"; | |||||
public static final String FILE_MP3 = "mp3"; | |||||
public static final String FILE_ARM = "arm"; | |||||
public static final String FILE_MP4 = "mp4"; | |||||
/////////////////////// | |||||
// 自定义菜单的按钮类型 | |||||
/////////////////////// | |||||
/** 点击推事件 */ | |||||
public static final String BUTTON_CLICK = "click"; | |||||
/** 跳转URL */ | |||||
public static final String BUTTON_VIEW = "view"; | |||||
/** 扫码推事件 */ | |||||
public static final String BUTTON_SCANCODE_PUSH = "scancode_push"; | |||||
/** 扫码推事件且弹出“消息接收中”提示框 */ | |||||
public static final String BUTTON_SCANCODE_WAITMSG = "scancode_waitmsg"; | |||||
/** 弹出系统拍照发图 */ | |||||
public static final String PIC_SYSPHOTO = "pic_sysphoto"; | |||||
/** 弹出拍照或者相册发图 */ | |||||
public static final String PIC_PHOTO_OR_ALBUM = "pic_photo_or_album"; | |||||
/** 弹出微信相册发图器 */ | |||||
public static final String PIC_WEIXIN = "pic_weixin"; | |||||
/** 弹出地理位置选择器 */ | |||||
public static final String LOCATION_SELECT = "location_select"; | |||||
} |
@@ -0,0 +1,30 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import me.chanjar.weixin.enterprise.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,23 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import java.util.Map; | |||||
import me.chanjar.weixin.enterprise.bean.WxCpXmlMessage; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMessage; | |||||
/** | |||||
* 处理微信推送消息的处理器接口 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public interface WxCpMessageHandler { | |||||
/** | |||||
* | |||||
* @param wxMessage | |||||
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 | |||||
* @return xml格式的消息,如果在异步规则里处理的话,可以返回null | |||||
*/ | |||||
public WxXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context); | |||||
} |
@@ -0,0 +1,22 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import java.util.Map; | |||||
import me.chanjar.weixin.enterprise.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,293 @@ | |||||
package me.chanjar.weixin.enterprise.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.enterprise.bean.WxCpXmlMessage; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMessage; | |||||
/** | |||||
* <pre> | |||||
* 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理 | |||||
* | |||||
* 说明: | |||||
* 1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理 | |||||
* 2. 默认情况下消息只会被处理一次,除非使用 {@link Rule#next()} | |||||
* 3. 规则的结束必须用{@link Rule#end()}或者{@link Rule#next()},否则不会生效 | |||||
* | |||||
* 使用方法: | |||||
* WxMessageRouter router = new WxMessageRouter(); | |||||
* 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 WxXmlOutMessage 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; | |||||
} | |||||
WxXmlOutMessage 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 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; | |||||
} | |||||
/** | |||||
* 如果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.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 WxXmlOutMessage service(WxCpXmlMessage wxMessage) { | |||||
Map<String, Object> context = new HashMap<String, Object>(); | |||||
// 如果拦截器不通过 | |||||
for (WxCpMessageInterceptor interceptor : this.interceptors) { | |||||
if (!interceptor.intercept(wxMessage, context)) { | |||||
return null; | |||||
} | |||||
} | |||||
// 交给handler处理 | |||||
WxXmlOutMessage res = null; | |||||
for (WxCpMessageHandler handler : this.handlers) { | |||||
// 返回最后handler的结果 | |||||
res = handler.handle(wxMessage, context); | |||||
} | |||||
return res; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,183 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import java.io.File; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.enterprise.bean.*; | |||||
import me.chanjar.weixin.enterprise.bean.WxDepartment; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxMediaUploadResult; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxUser; | |||||
import me.chanjar.weixin.enterprise.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> | |||||
* 获取access_token,本方法线程安全 | |||||
* 且在多线程同时刷新时只刷新一次,避免超出2000次/日的调用次数上限 | |||||
* | |||||
* 另:本service的所有方法都会在access_token过期是调用此方法 | |||||
* | |||||
* 程序员在非必要情况下尽量不要主动调用此方法 | |||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | |||||
* </pre> | |||||
* @throws me.chanjar.weixin.enterprise.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 WxConsts} | |||||
* @param fileType 文件类型,请看{@link WxConsts} | |||||
* @param inputStream 输入流 | |||||
* @throws WxErrorException | |||||
*/ | |||||
public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) throws WxErrorException, IOException; | |||||
/** | |||||
* @see #mediaUpload(String, String, InputStream) | |||||
* @param mediaType | |||||
* @param file | |||||
* @throws WxErrorException | |||||
*/ | |||||
public WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException; | |||||
/** | |||||
* <pre> | |||||
* 下载多媒体文件 | |||||
* 根据微信文档,视频文件下载不了,会返回null | |||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | |||||
* </pre> | |||||
* @params media_id | |||||
* @return 保存到本地的临时文件 | |||||
* @throws WxErrorException | |||||
*/ | |||||
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(WxCustomMessage 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 name 分组名字(30个字符以内) | |||||
* @throws WxErrorException | |||||
*/ | |||||
public WxDepartment departmentCreate(String name) throws WxErrorException; | |||||
/** | |||||
* <pre> | |||||
* 分组管理接口 - 查询所有分组 | |||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=分组管理接口 | |||||
* </pre> | |||||
* @return | |||||
* @throws WxErrorException | |||||
*/ | |||||
public List<WxDepartment> departmentGet() 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 departmentUpdate(WxDepartment group) throws WxErrorException; | |||||
/** | |||||
* <pre> | |||||
* 部门管理接口 - 删除部门 | |||||
* | |||||
* </pre> | |||||
* @param department | |||||
* @throws WxErrorException | |||||
*/ | |||||
public void departmentDelete(WxDepartment department) throws WxErrorException; | |||||
public void userCreate(WxUser user) throws WxErrorException; | |||||
public void userUpdate(WxUser user) throws WxErrorException; | |||||
public void userDelete(String userid) throws WxErrorException; | |||||
public WxUser userGet(String userid) throws WxErrorException; | |||||
public List<WxUser> userGetByDepartment(String departmentId) throws WxErrorException; | |||||
/** | |||||
* 注入 {@link WxCpConfigStorage} 的实现 | |||||
* @param wxConfigProvider | |||||
*/ | |||||
public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider); | |||||
} |
@@ -0,0 +1,278 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import java.io.File; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.io.StringReader; | |||||
import java.util.List; | |||||
import java.util.UUID; | |||||
import java.util.concurrent.atomic.AtomicBoolean; | |||||
import me.chanjar.weixin.enterprise.bean.*; | |||||
import me.chanjar.weixin.enterprise.util.crypto.WxCryptUtil; | |||||
import me.chanjar.weixin.enterprise.util.http.SimpleGetRequestExecutor; | |||||
import me.chanjar.weixin.enterprise.util.crypto.SHA1; | |||||
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.enterprise.bean.WxDepartment; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxError; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxMediaUploadResult; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxUser; | |||||
import me.chanjar.weixin.enterprise.exception.WxErrorException; | |||||
import me.chanjar.weixin.enterprise.util.fs.FileUtils; | |||||
import me.chanjar.weixin.enterprise.util.http.MediaDownloadRequestExecutor; | |||||
import me.chanjar.weixin.enterprise.util.http.MediaUploadRequestExecutor; | |||||
import me.chanjar.weixin.enterprise.util.http.RequestExecutor; | |||||
import me.chanjar.weixin.enterprise.util.http.SimplePostRequestExecutor; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
import com.google.gson.JsonElement; | |||||
import com.google.gson.JsonObject; | |||||
import com.google.gson.internal.Streams; | |||||
import com.google.gson.reflect.TypeToken; | |||||
import com.google.gson.stream.JsonReader; | |||||
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 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(WxCustomMessage message) throws WxErrorException { | |||||
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | |||||
execute(new SimplePostRequestExecutor(), url, message.toJson()); | |||||
} | |||||
public void menuCreate(WxMenu menu) throws WxErrorException { | |||||
String url = "https://api.weixin.qq.com/cgi-bin/menu/create"; | |||||
execute(new SimplePostRequestExecutor(), url, menu.toJson()); | |||||
} | |||||
public void menuDelete() throws WxErrorException { | |||||
String url = "https://api.weixin.qq.com/cgi-bin/menu/delete"; | |||||
execute(new SimpleGetRequestExecutor(), url, null); | |||||
} | |||||
public WxMenu menuGet() throws WxErrorException { | |||||
String url = "https://api.weixin.qq.com/cgi-bin/menu/get"; | |||||
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 = "http://file.api.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 = "http://file.api.weixin.qq.com/cgi-bin/media/get"; | |||||
return execute(new MediaDownloadRequestExecutor(), url, "media_id=" + media_id); | |||||
} | |||||
public WxDepartment departmentCreate(String name) throws WxErrorException { | |||||
// TODO | |||||
String url = "https://api.weixin.qq.com/cgi-bin/groups/create"; | |||||
JsonObject json = new JsonObject(); | |||||
JsonObject groupJson = new JsonObject(); | |||||
json.add("group", groupJson); | |||||
groupJson.addProperty("name", name); | |||||
String responseContent = execute( | |||||
new SimplePostRequestExecutor(), | |||||
url, | |||||
json.toString()); | |||||
return WxDepartment.fromJson(responseContent); | |||||
} | |||||
public void departmentUpdate(WxDepartment group) throws WxErrorException { | |||||
// TODO | |||||
String url = "https://api.weixin.qq.com/cgi-bin/groups/update"; | |||||
execute(new SimplePostRequestExecutor(), url, group.toJson()); | |||||
} | |||||
public List<WxDepartment> departmentGet() throws WxErrorException { | |||||
// TODO | |||||
String url = "https://api.weixin.qq.com/cgi-bin/groups/get"; | |||||
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 WxGsonBuilder.INSTANCE.create() | |||||
.fromJson(tmpJsonElement.getAsJsonObject().get("groups"), new TypeToken<List<WxDepartment>>() { | |||||
}.getType()); | |||||
} | |||||
public void departmentDelete(WxDepartment department) throws WxErrorException { | |||||
// TODO | |||||
} | |||||
@Override | |||||
public void userCreate(WxUser user) throws WxErrorException { | |||||
// TODO | |||||
} | |||||
@Override | |||||
public void userUpdate(WxUser user) throws WxErrorException { | |||||
// TODO | |||||
} | |||||
@Override | |||||
public void userDelete(String userid) throws WxErrorException { | |||||
// TODO | |||||
} | |||||
@Override | |||||
public WxUser userGet(String userid) throws WxErrorException { | |||||
return null; | |||||
} | |||||
@Override | |||||
public List<WxUser> userGetByDepartment(String departmentId) throws WxErrorException { | |||||
return null; | |||||
} | |||||
/** | |||||
* 向微信端发送请求,在这里执行的策略是当发生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,85 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import me.chanjar.weixin.enterprise.bean.WxAccessToken; | |||||
/** | |||||
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxInMemoryCpConfigStorage 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; | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
public class WxAccessToken { | |||||
private String accessToken; | |||||
private int expiresIn = -1; | |||||
public String getAccessToken() { | |||||
return accessToken; | |||||
} | |||||
public void setAccessToken(String accessToken) { | |||||
this.accessToken = accessToken; | |||||
} | |||||
public int getExpiresIn() { | |||||
return expiresIn; | |||||
} | |||||
public void setExpiresIn(int expiresIn) { | |||||
this.expiresIn = expiresIn; | |||||
} | |||||
public static WxAccessToken fromJson(String json) { | |||||
return WxGsonBuilder.create().fromJson(json, WxAccessToken.class); | |||||
} | |||||
} |
@@ -0,0 +1,639 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.api.WxCpConfigStorage; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.util.xml.AdapterCDATA; | |||||
import me.chanjar.weixin.enterprise.util.xml.XmlTransformer; | |||||
import me.chanjar.weixin.enterprise.util.crypto.WxCryptUtil; | |||||
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 = "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 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 WxConsts#XML_MSG_TEXT} | |||||
* {@link WxConsts#XML_MSG_IMAGE} | |||||
* {@link WxConsts#XML_MSG_VOICE} | |||||
* {@link WxConsts#XML_MSG_VIDEO} | |||||
* {@link WxConsts#XML_MSG_LOCATION} | |||||
* {@link WxConsts#XML_MSG_LINK} | |||||
* {@link WxConsts#XML_MSG_EVENT} | |||||
* </pre> | |||||
* | |||||
* @return | |||||
*/ | |||||
public String getMsgType() { | |||||
return msgType; | |||||
} | |||||
/** | |||||
* <pre> | |||||
* 当发送消息的时候使用: | |||||
* {@link WxConsts#XML_MSG_TEXT} | |||||
* {@link WxConsts#XML_MSG_IMAGE} | |||||
* {@link WxConsts#XML_MSG_VOICE} | |||||
* {@link WxConsts#XML_MSG_VIDEO} | |||||
* {@link WxConsts#XML_MSG_NEWS} | |||||
* {@link WxConsts#XML_MSG_MUSIC} | |||||
* </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; | |||||
} | |||||
public static WxCpXmlMessage fromXml(String xml) { | |||||
try { | |||||
return XmlTransformer.fromXml(WxCpXmlMessage.class, xml); | |||||
} catch (JAXBException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
public 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) { | |||||
WxCryptUtil cryptUtil = new WxCryptUtil(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,193 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.custombuilder.ImageBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.custombuilder.MusicBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.custombuilder.NewsBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.custombuilder.TextBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.custombuilder.VideoBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.custombuilder.VoiceBuilder; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* 客服消息 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxCustomMessage { | |||||
private String toUser; | |||||
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 getMsgType() { | |||||
return msgType; | |||||
} | |||||
/** | |||||
* <pre> | |||||
* 请使用 | |||||
* {@link WxConsts#CUSTOM_MSG_TEXT} | |||||
* {@link WxConsts#CUSTOM_MSG_IMAGE} | |||||
* {@link WxConsts#CUSTOM_MSG_VOICE} | |||||
* {@link WxConsts#CUSTOM_MSG_MUSIC} | |||||
* {@link WxConsts#CUSTOM_MSG_VIDEO} | |||||
* {@link WxConsts#CUSTOM_MSG_NEWS} | |||||
* </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 WxGsonBuilder.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 MusicBuilder MUSIC() { | |||||
return new MusicBuilder(); | |||||
} | |||||
/** | |||||
* 获得图文消息builder | |||||
* @return | |||||
*/ | |||||
public static NewsBuilder NEWS() { | |||||
return new NewsBuilder(); | |||||
} | |||||
} |
@@ -0,0 +1,46 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* 微信用户分组 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxDepartment { | |||||
private long id = -1; | |||||
private String name; | |||||
private long count; | |||||
public long getId() { | |||||
return id; | |||||
} | |||||
public void setId(long id) { | |||||
this.id = id; | |||||
} | |||||
public String getName() { | |||||
return name; | |||||
} | |||||
public void setName(String name) { | |||||
this.name = name; | |||||
} | |||||
public long getCount() { | |||||
return count; | |||||
} | |||||
public void setCount(long count) { | |||||
this.count = count; | |||||
} | |||||
public static WxDepartment fromJson(String json) { | |||||
return WxGsonBuilder.create().fromJson(json, WxDepartment.class); | |||||
} | |||||
public String toJson() { | |||||
return WxGsonBuilder.create().toJson(this); | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "WxGroup [id=" + id + ", name=" + name + ", count=" + count + "]"; | |||||
} | |||||
} |
@@ -0,0 +1,70 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* 分组群发的消息 | |||||
* | |||||
* @author Daniel Qian | |||||
*/ | |||||
public class WxMassGroupMessage { | |||||
private long groupId; | |||||
private String msgtype; | |||||
private String content; | |||||
private String mediaId; | |||||
public WxMassGroupMessage() { | |||||
super(); | |||||
} | |||||
public String getMsgtype() { | |||||
return msgtype; | |||||
} | |||||
/** | |||||
* <pre> | |||||
* 请使用 | |||||
* {@link WxConsts#MASS_MSG_IMAGE} | |||||
* {@link WxConsts#MASS_MSG_NEWS} | |||||
* {@link WxConsts#MASS_MSG_TEXT} | |||||
* {@link WxConsts#MASS_MSG_VIDEO} | |||||
* {@link WxConsts#MASS_MSG_VOICE} | |||||
* 如果msgtype和media_id不匹配的话,会返回系统繁忙的错误 | |||||
* </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 toJson() { | |||||
return WxGsonBuilder.INSTANCE.create().toJson(this); | |||||
} | |||||
public long getGroupId() { | |||||
return groupId; | |||||
} | |||||
public void setGroupId(long groupId) { | |||||
this.groupId = groupId; | |||||
} | |||||
} |
@@ -0,0 +1,117 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* 群发时用到的图文消息素材 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMassNews { | |||||
private List<WxMassNewsArticle> articles = new ArrayList<WxMassNewsArticle>(); | |||||
public List<WxMassNewsArticle> getArticles() { | |||||
return articles; | |||||
} | |||||
public void addArticle(WxMassNewsArticle article) { | |||||
this.articles.add(article); | |||||
} | |||||
public String toJson() { | |||||
return WxGsonBuilder.INSTANCE.create().toJson(this); | |||||
} | |||||
/** | |||||
* <pre> | |||||
* 群发图文消息article | |||||
* 1. thumbMediaId (必填) 图文消息缩略图的media_id,可以在基础支持-上传多媒体文件接口中获得 | |||||
* 2. author 图文消息的作者 | |||||
* 3. title (必填) 图文消息的标题 | |||||
* 4. contentSourceUrl 在图文消息页面点击“阅读原文”后的页面链接 | |||||
* 5. content (必填) 图文消息页面的内容,支持HTML标签 | |||||
* 6. digest 图文消息的描述 | |||||
* 7, showCoverPic 是否显示封面,true为显示,false为不显示 | |||||
* </pre> | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public static class WxMassNewsArticle { | |||||
/** | |||||
* (必填) 图文消息缩略图的media_id,可以在基础支持-上传多媒体文件接口中获得 | |||||
*/ | |||||
private String thumbMediaId; | |||||
/** | |||||
* 图文消息的作者 | |||||
*/ | |||||
private String author; | |||||
/** | |||||
* (必填) 图文消息的标题 | |||||
*/ | |||||
private String title; | |||||
/** | |||||
* 在图文消息页面点击“阅读原文”后的页面链接 | |||||
*/ | |||||
private String contentSourceUrl; | |||||
/** | |||||
* (必填) 图文消息页面的内容,支持HTML标签 | |||||
*/ | |||||
private String content; | |||||
/** | |||||
* 图文消息的描述 | |||||
*/ | |||||
private String digest; | |||||
/** | |||||
* 是否显示封面,true为显示,false为不显示 | |||||
*/ | |||||
private boolean showCoverPic; | |||||
public String getThumbMediaId() { | |||||
return thumbMediaId; | |||||
} | |||||
public void setThumbMediaId(String thumbMediaId) { | |||||
this.thumbMediaId = thumbMediaId; | |||||
} | |||||
public String getAuthor() { | |||||
return author; | |||||
} | |||||
public void setAuthor(String author) { | |||||
this.author = author; | |||||
} | |||||
public String getTitle() { | |||||
return title; | |||||
} | |||||
public void setTitle(String title) { | |||||
this.title = title; | |||||
} | |||||
public String getContentSourceUrl() { | |||||
return contentSourceUrl; | |||||
} | |||||
public void setContentSourceUrl(String contentSourceUrl) { | |||||
this.contentSourceUrl = contentSourceUrl; | |||||
} | |||||
public String getContent() { | |||||
return content; | |||||
} | |||||
public void setContent(String content) { | |||||
this.content = content; | |||||
} | |||||
public String getDigest() { | |||||
return digest; | |||||
} | |||||
public void setDigest(String digest) { | |||||
this.digest = digest; | |||||
} | |||||
public boolean isShowCoverPic() { | |||||
return showCoverPic; | |||||
} | |||||
public void setShowCoverPic(boolean showCoverPic) { | |||||
this.showCoverPic = showCoverPic; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,79 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* OpenId列表群发的消息 | |||||
* | |||||
* @author Daniel Qian | |||||
*/ | |||||
public class WxMassOpenIdsMessage { | |||||
private List<String> toUsers = new ArrayList<String>(); | |||||
private String msgType; | |||||
private String content; | |||||
private String mediaId; | |||||
public WxMassOpenIdsMessage() { | |||||
super(); | |||||
} | |||||
public String getMsgType() { | |||||
return msgType; | |||||
} | |||||
/** | |||||
* <pre> | |||||
* 请使用 | |||||
* {@link me.chanjar.weixin.enterprise.api.WxConsts#MASS_MSG_IMAGE} | |||||
* {@link me.chanjar.weixin.enterprise.api.WxConsts#MASS_MSG_NEWS} | |||||
* {@link me.chanjar.weixin.enterprise.api.WxConsts#MASS_MSG_TEXT} | |||||
* {@link me.chanjar.weixin.enterprise.api.WxConsts#MASS_MSG_VIDEO} | |||||
* {@link me.chanjar.weixin.enterprise.api.WxConsts#MASS_MSG_VOICE} | |||||
* 如果msgtype和media_id不匹配的话,会返回系统繁忙的错误 | |||||
* </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 toJson() { | |||||
return WxGsonBuilder.INSTANCE.create().toJson(this); | |||||
} | |||||
/** | |||||
* OpenId列表,最多支持10,000个 | |||||
* @return | |||||
*/ | |||||
public List<String> getToUsers() { | |||||
return toUsers; | |||||
} | |||||
/** | |||||
* 添加OpenId,最多支持10,000个 | |||||
* @param openId | |||||
*/ | |||||
public void addUser(String openId) { | |||||
this.toUsers.add(openId); | |||||
} | |||||
} |
@@ -0,0 +1,43 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* 群发时用到的视频素材 | |||||
* | |||||
* @author Daniel Qian | |||||
*/ | |||||
public class WxMassVideo { | |||||
private String mediaId; | |||||
private String title; | |||||
private String description; | |||||
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 getMediaId() { | |||||
return mediaId; | |||||
} | |||||
public void setMediaId(String mediaId) { | |||||
this.mediaId = mediaId; | |||||
} | |||||
public String toJson() { | |||||
return WxGsonBuilder.INSTANCE.create().toJson(this); | |||||
} | |||||
} |
@@ -0,0 +1,90 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import java.io.InputStream; | |||||
import java.io.InputStreamReader; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* 公众号菜单 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMenu { | |||||
private List<WxMenuButton> buttons = new ArrayList<WxMenuButton>(); | |||||
public List<WxMenuButton> getButtons() { | |||||
return buttons; | |||||
} | |||||
public void setButtons(List<WxMenuButton> buttons) { | |||||
this.buttons = buttons; | |||||
} | |||||
public String toJson() { | |||||
return WxGsonBuilder.create().toJson(this); | |||||
} | |||||
public static WxMenu fromJson(String json) { | |||||
return WxGsonBuilder.create().fromJson(json, WxMenu.class); | |||||
} | |||||
public static WxMenu fromJson(InputStream is) { | |||||
return WxGsonBuilder.create().fromJson(new InputStreamReader(is), WxMenu.class); | |||||
} | |||||
public static class WxMenuButton { | |||||
private String type; | |||||
private String name; | |||||
private String key; | |||||
private String url; | |||||
private List<WxMenuButton> subButtons = new ArrayList<WxMenuButton>(); | |||||
public String getType() { | |||||
return type; | |||||
} | |||||
public void setType(String type) { | |||||
this.type = type; | |||||
} | |||||
public String getName() { | |||||
return name; | |||||
} | |||||
public void setName(String name) { | |||||
this.name = name; | |||||
} | |||||
public String getKey() { | |||||
return key; | |||||
} | |||||
public void setKey(String key) { | |||||
this.key = key; | |||||
} | |||||
public String getUrl() { | |||||
return url; | |||||
} | |||||
public void setUrl(String url) { | |||||
this.url = url; | |||||
} | |||||
public List<WxMenuButton> getSubButtons() { | |||||
return subButtons; | |||||
} | |||||
public void setSubButtons(List<WxMenuButton> subButtons) { | |||||
this.subButtons = subButtons; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
package me.chanjar.weixin.enterprise.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.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.util.xml.MediaIdMarshaller; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public class WxXmlOutImageMessage extends WxXmlOutMessage { | |||||
@XmlElement(name="Image") | |||||
@XmlJavaTypeAdapter(MediaIdMarshaller.class) | |||||
private String mediaId; | |||||
public WxXmlOutImageMessage() { | |||||
this.msgType = WxConsts.XML_MSG_IMAGE; | |||||
} | |||||
public String getMediaId() { | |||||
return mediaId; | |||||
} | |||||
public void setMediaId(String mediaId) { | |||||
this.mediaId = mediaId; | |||||
} | |||||
} |
@@ -0,0 +1,137 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
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; | |||||
import me.chanjar.weixin.enterprise.api.WxCpConfigStorage; | |||||
import me.chanjar.weixin.enterprise.bean.outxmlbuilder.ImageBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.outxmlbuilder.MusicBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.outxmlbuilder.NewsBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.outxmlbuilder.TextBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.outxmlbuilder.VideoBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.outxmlbuilder.VoiceBuilder; | |||||
import me.chanjar.weixin.enterprise.util.crypto.WxCryptUtil; | |||||
import me.chanjar.weixin.enterprise.util.xml.AdapterCDATA; | |||||
import me.chanjar.weixin.enterprise.util.xml.XmlTransformer; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public class WxXmlOutMessage { | |||||
@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; | |||||
} | |||||
public 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(); | |||||
WxCryptUtil pc = new WxCryptUtil(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 MusicBuilder MUSIC() { | |||||
return new MusicBuilder(); | |||||
} | |||||
/** | |||||
* 获得图文消息builder | |||||
* @return | |||||
*/ | |||||
public static NewsBuilder NEWS() { | |||||
return new NewsBuilder(); | |||||
} | |||||
} |
@@ -0,0 +1,100 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlElementWrapper; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.util.xml.AdapterCDATA; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public class WxXmlOutMewsMessage extends WxXmlOutMessage { | |||||
@XmlElement(name = "ArticleCount") | |||||
protected int articleCount; | |||||
@XmlElementWrapper(name="Articles") | |||||
@XmlElement(name = "item") | |||||
protected final List<Item> articles = new ArrayList<Item>(); | |||||
public WxXmlOutMewsMessage() { | |||||
this.msgType = WxConsts.XML_MSG_NEWS; | |||||
} | |||||
public int getArticleCount() { | |||||
return articleCount; | |||||
} | |||||
public void addArticle(Item item) { | |||||
this.articles.add(item); | |||||
this.articleCount = this.articles.size(); | |||||
} | |||||
public List<Item> getArticles() { | |||||
return articles; | |||||
} | |||||
@XmlRootElement(name = "Item") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public static class Item { | |||||
@XmlElement(name = "Title") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String Title; | |||||
@XmlElement(name = "Description") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String Description; | |||||
@XmlElement(name="PicUrl") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String PicUrl; | |||||
@XmlElement(name="Url") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String Url; | |||||
public String getTitle() { | |||||
return Title; | |||||
} | |||||
public void setTitle(String title) { | |||||
Title = title; | |||||
} | |||||
public String getDescription() { | |||||
return Description; | |||||
} | |||||
public void setDescription(String description) { | |||||
Description = description; | |||||
} | |||||
public String getPicUrl() { | |||||
return PicUrl; | |||||
} | |||||
public void setPicUrl(String picUrl) { | |||||
PicUrl = picUrl; | |||||
} | |||||
public String getUrl() { | |||||
return Url; | |||||
} | |||||
public void setUrl(String url) { | |||||
Url = url; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,129 @@ | |||||
package me.chanjar.weixin.enterprise.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.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.util.xml.AdapterCDATA; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public class WxXmlOutMusicMessage extends WxXmlOutMessage { | |||||
@XmlElement(name = "Music") | |||||
protected final Music music = new Music(); | |||||
public WxXmlOutMusicMessage() { | |||||
this.msgType = WxConsts.XML_MSG_MUSIC; | |||||
} | |||||
public String getTitle() { | |||||
return music.getTitle(); | |||||
} | |||||
public void setTitle(String title) { | |||||
music.setTitle(title); | |||||
} | |||||
public String getDescription() { | |||||
return music.getDescription(); | |||||
} | |||||
public void setDescription(String description) { | |||||
music.setDescription(description); | |||||
} | |||||
public String getThumbMediaId() { | |||||
return music.getThumbMediaId(); | |||||
} | |||||
public void setThumbMediaId(String thumbMediaId) { | |||||
music.setThumbMediaId(thumbMediaId); | |||||
} | |||||
public String getMusicUrl() { | |||||
return music.getMusicUrl(); | |||||
} | |||||
public void setMusicUrl(String musicUrl) { | |||||
music.setMusicUrl(musicUrl); | |||||
} | |||||
public String getHqMusicUrl() { | |||||
return music.getHqMusicUrl(); | |||||
} | |||||
public void setHqMusicUrl(String hqMusicUrl) { | |||||
music.setHqMusicUrl(hqMusicUrl); | |||||
} | |||||
@XmlRootElement(name = "Music") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
private static class Music { | |||||
@XmlElement(name = "Title") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String title; | |||||
@XmlElement(name = "Description") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String description; | |||||
@XmlElement(name="ThumbMediaId") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String thumbMediaId; | |||||
@XmlElement(name="MusicUrl") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String musicUrl; | |||||
@XmlElement(name="HQMusicUrl") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String hqMusicUrl; | |||||
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 getThumbMediaId() { | |||||
return thumbMediaId; | |||||
} | |||||
public void setThumbMediaId(String thumbMediaId) { | |||||
this.thumbMediaId = thumbMediaId; | |||||
} | |||||
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; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
package me.chanjar.weixin.enterprise.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.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.util.xml.AdapterCDATA; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public class WxXmlOutTextMessage extends WxXmlOutMessage { | |||||
@XmlElement(name="Content") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String content; | |||||
public WxXmlOutTextMessage() { | |||||
this.msgType = WxConsts.XML_MSG_TEXT; | |||||
} | |||||
public String getContent() { | |||||
return content; | |||||
} | |||||
public void setContent(String content) { | |||||
this.content = content; | |||||
} | |||||
} |
@@ -0,0 +1,90 @@ | |||||
package me.chanjar.weixin.enterprise.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.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.util.xml.AdapterCDATA; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public class WxXmlOutVideoMessage extends WxXmlOutMessage { | |||||
@XmlElement(name = "Video") | |||||
protected final Video video = new Video(); | |||||
public WxXmlOutVideoMessage() { | |||||
this.msgType = WxConsts.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.enterprise.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.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.util.xml.MediaIdMarshaller; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public class WxXmlOutVoiceMessage extends WxXmlOutMessage { | |||||
@XmlElement(name="Voice") | |||||
@XmlJavaTypeAdapter(MediaIdMarshaller.class) | |||||
private String mediaId; | |||||
public WxXmlOutVoiceMessage() { | |||||
this.msgType = WxConsts.XML_MSG_VOICE; | |||||
} | |||||
public String getMediaId() { | |||||
return mediaId; | |||||
} | |||||
public void setMediaId(String mediaId) { | |||||
this.mediaId = mediaId; | |||||
} | |||||
} |
@@ -0,0 +1,20 @@ | |||||
package me.chanjar.weixin.enterprise.bean.custombuilder; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage; | |||||
public class BaseBuilder<T> { | |||||
protected String msgType; | |||||
protected String toUser; | |||||
public T toUser(String toUser) { | |||||
this.toUser = toUser; | |||||
return (T) this; | |||||
} | |||||
public WxCustomMessage build() { | |||||
WxCustomMessage m = new WxCustomMessage(); | |||||
m.setMsgType(this.msgType); | |||||
m.setToUser(this.toUser); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
package me.chanjar.weixin.enterprise.bean.custombuilder; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage; | |||||
/** | |||||
* 获得消息builder | |||||
* <pre> | |||||
* 用法: WxCustomMessage m = WxCustomMessage.IMAGE().mediaId(...).toUser(...).build(); | |||||
* </pre> | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public final class ImageBuilder extends BaseBuilder<ImageBuilder> { | |||||
private String mediaId; | |||||
public ImageBuilder() { | |||||
this.msgType = WxConsts.CUSTOM_MSG_IMAGE; | |||||
} | |||||
public ImageBuilder mediaId(String media_id) { | |||||
this.mediaId = media_id; | |||||
return this; | |||||
} | |||||
public WxCustomMessage build() { | |||||
WxCustomMessage m = super.build(); | |||||
m.setMediaId(this.mediaId); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,64 @@ | |||||
package me.chanjar.weixin.enterprise.bean.custombuilder; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage; | |||||
/** | |||||
* 音乐消息builder | |||||
* <pre> | |||||
* 用法: WxCustomMessage m = WxCustomMessage.MUSIC() | |||||
* .musicUrl(...) | |||||
* .hqMusicUrl(...) | |||||
* .title(...) | |||||
* .thumbMediaId(..) | |||||
* .description(..) | |||||
* .toUser(...) | |||||
* .build(); | |||||
* </pre> | |||||
*/ | |||||
public final class MusicBuilder extends BaseBuilder<MusicBuilder> { | |||||
private String title; | |||||
private String description; | |||||
private String thumbMediaId; | |||||
private String musicUrl; | |||||
private String hqMusicUrl; | |||||
public MusicBuilder() { | |||||
this.msgType = WxConsts.CUSTOM_MSG_MUSIC; | |||||
} | |||||
public MusicBuilder musicUrl(String musicurl) { | |||||
this.musicUrl = musicurl; | |||||
return this; | |||||
} | |||||
public MusicBuilder hqMusicUrl(String hqMusicurl) { | |||||
this.hqMusicUrl = hqMusicurl; | |||||
return this; | |||||
} | |||||
public MusicBuilder title(String title) { | |||||
this.title = title; | |||||
return this; | |||||
} | |||||
public MusicBuilder description(String description) { | |||||
this.description = description; | |||||
return this; | |||||
} | |||||
public MusicBuilder thumbMediaId(String thumb_media_id) { | |||||
this.thumbMediaId = thumb_media_id; | |||||
return this; | |||||
} | |||||
public WxCustomMessage build() { | |||||
WxCustomMessage m = super.build(); | |||||
m.setMusicUrl(this.musicUrl); | |||||
m.setHqMusicUrl(this.hqMusicUrl); | |||||
m.setTitle(title); | |||||
m.setDescription(description); | |||||
m.setThumbMediaId(thumbMediaId); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
package me.chanjar.weixin.enterprise.bean.custombuilder; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage; | |||||
/** | |||||
* 图文消息builder | |||||
* <pre> | |||||
* 用法: | |||||
* WxCustomMessage m = WxCustomMessage.NEWS().addArticle(article).toUser(...).build(); | |||||
* </pre> | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public final class NewsBuilder extends BaseBuilder<NewsBuilder> { | |||||
private List<WxCustomMessage.WxArticle> articles = new ArrayList<WxCustomMessage.WxArticle>(); | |||||
public NewsBuilder() { | |||||
this.msgType = WxConsts.CUSTOM_MSG_NEWS; | |||||
} | |||||
public NewsBuilder addArticle(WxCustomMessage.WxArticle article) { | |||||
this.articles.add(article); | |||||
return this; | |||||
} | |||||
public WxCustomMessage build() { | |||||
WxCustomMessage m = super.build(); | |||||
m.setArticles(this.articles); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
package me.chanjar.weixin.enterprise.bean.custombuilder; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage; | |||||
/** | |||||
* 文本消息builder | |||||
* <pre> | |||||
* 用法: WxCustomMessage m = WxCustomMessage.TEXT().content(...).toUser(...).build(); | |||||
* </pre> | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public final class TextBuilder extends BaseBuilder<TextBuilder> { | |||||
private String content; | |||||
public TextBuilder() { | |||||
this.msgType = WxConsts.CUSTOM_MSG_TEXT; | |||||
} | |||||
public TextBuilder content(String content) { | |||||
this.content = content; | |||||
return this; | |||||
} | |||||
public WxCustomMessage build() { | |||||
WxCustomMessage m = super.build(); | |||||
m.setContent(this.content); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,58 @@ | |||||
package me.chanjar.weixin.enterprise.bean.custombuilder; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage; | |||||
/** | |||||
* 视频消息builder | |||||
* <pre> | |||||
* 用法: WxCustomMessage m = WxCustomMessage.VOICE() | |||||
* .mediaId(...) | |||||
* .title(...) | |||||
* .thumbMediaId(..) | |||||
* .description(..) | |||||
* .toUser(...) | |||||
* .build(); | |||||
* </pre> | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public final class VideoBuilder extends BaseBuilder<VideoBuilder> { | |||||
private String mediaId; | |||||
private String title; | |||||
private String description; | |||||
private String thumbMediaId; | |||||
public VideoBuilder() { | |||||
this.msgType = WxConsts.CUSTOM_MSG_VIDEO; | |||||
} | |||||
public VideoBuilder mediaId(String mediaId) { | |||||
this.mediaId = mediaId; | |||||
return this; | |||||
} | |||||
public VideoBuilder title(String title) { | |||||
this.title = title; | |||||
return this; | |||||
} | |||||
public VideoBuilder description(String description) { | |||||
this.description = description; | |||||
return this; | |||||
} | |||||
public VideoBuilder thumbMediaId(String thumb_media_id) { | |||||
this.thumbMediaId = thumb_media_id; | |||||
return this; | |||||
} | |||||
public WxCustomMessage build() { | |||||
WxCustomMessage m = super.build(); | |||||
m.setMediaId(this.mediaId); | |||||
m.setTitle(title); | |||||
m.setDescription(description); | |||||
m.setThumbMediaId(thumbMediaId); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
package me.chanjar.weixin.enterprise.bean.custombuilder; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage; | |||||
/** | |||||
* 语音消息builder | |||||
* <pre> | |||||
* 用法: WxCustomMessage m = WxCustomMessage.VOICE().mediaId(...).toUser(...).build(); | |||||
* </pre> | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public final class VoiceBuilder extends BaseBuilder<VoiceBuilder> { | |||||
private String mediaId; | |||||
public VoiceBuilder() { | |||||
this.msgType = WxConsts.CUSTOM_MSG_VOICE; | |||||
} | |||||
public VoiceBuilder mediaId(String media_id) { | |||||
this.mediaId = media_id; | |||||
return this; | |||||
} | |||||
public WxCustomMessage build() { | |||||
WxCustomMessage m = super.build(); | |||||
m.setMediaId(this.mediaId); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,29 @@ | |||||
package me.chanjar.weixin.enterprise.bean.outxmlbuilder; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMessage; | |||||
public abstract class BaseBuilder<BuilderType, ValueType> { | |||||
protected String toUserName; | |||||
protected String fromUserName; | |||||
public BuilderType toUser(String touser) { | |||||
this.toUserName = touser; | |||||
return (BuilderType) this; | |||||
} | |||||
public BuilderType fromUser(String fromusername) { | |||||
this.fromUserName = fromusername; | |||||
return (BuilderType) this; | |||||
} | |||||
public abstract ValueType build(); | |||||
public void setCommon(WxXmlOutMessage m) { | |||||
m.setToUserName(this.toUserName); | |||||
m.setFromUserName(this.fromUserName); | |||||
m.setCreateTime(System.currentTimeMillis() / 1000l); | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
package me.chanjar.weixin.enterprise.bean.outxmlbuilder; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutImageMessage; | |||||
/** | |||||
* 图片消息builder | |||||
* @author Daniel Qian | |||||
*/ | |||||
public final class ImageBuilder extends BaseBuilder<ImageBuilder, WxXmlOutImageMessage> { | |||||
private String mediaId; | |||||
public ImageBuilder mediaId(String media_id) { | |||||
this.mediaId = media_id; | |||||
return this; | |||||
} | |||||
public WxXmlOutImageMessage build() { | |||||
WxXmlOutImageMessage m = new WxXmlOutImageMessage(); | |||||
setCommon(m); | |||||
m.setMediaId(this.mediaId); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,54 @@ | |||||
package me.chanjar.weixin.enterprise.bean.outxmlbuilder; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMusicMessage; | |||||
/** | |||||
* 音乐消息builder | |||||
* | |||||
* @author Daniel Qian | |||||
*/ | |||||
public final class MusicBuilder extends BaseBuilder<MusicBuilder, WxXmlOutMusicMessage> { | |||||
private String title; | |||||
private String description; | |||||
private String hqMusicUrl; | |||||
private String musicUrl; | |||||
private String thumbMediaId; | |||||
public MusicBuilder title(String title) { | |||||
this.title = title; | |||||
return this; | |||||
} | |||||
public MusicBuilder description(String description) { | |||||
this.description = description; | |||||
return this; | |||||
} | |||||
public MusicBuilder hqMusicUrl(String hqMusicUrl) { | |||||
this.hqMusicUrl = hqMusicUrl; | |||||
return this; | |||||
} | |||||
public MusicBuilder musicUrl(String musicUrl) { | |||||
this.musicUrl = musicUrl; | |||||
return this; | |||||
} | |||||
public MusicBuilder thumbMediaId(String thumbMediaId) { | |||||
this.thumbMediaId = thumbMediaId; | |||||
return this; | |||||
} | |||||
public WxXmlOutMusicMessage build() { | |||||
WxXmlOutMusicMessage m = new WxXmlOutMusicMessage(); | |||||
setCommon(m); | |||||
m.setTitle(title); | |||||
m.setDescription(description); | |||||
m.setHqMusicUrl(hqMusicUrl); | |||||
m.setMusicUrl(musicUrl); | |||||
m.setThumbMediaId(thumbMediaId); | |||||
return m; | |||||
} | |||||
} |
@@ -1,14 +1,14 @@ | |||||
package me.chanjar.weixin.bean.outxmlbuilder; | |||||
package me.chanjar.weixin.enterprise.bean.outxmlbuilder; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | import java.util.List; | ||||
import me.chanjar.weixin.bean.WxXmlOutMewsMessage; | |||||
import me.chanjar.weixin.bean.WxXmlOutMewsMessage.Item; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMewsMessage; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMewsMessage.Item; | |||||
/** | /** | ||||
* 图文消息builder | * 图文消息builder | ||||
* @author chanjarster | |||||
* @author Daniel Qian | |||||
*/ | */ | ||||
public final class NewsBuilder extends BaseBuilder<NewsBuilder, WxXmlOutMewsMessage> { | public final class NewsBuilder extends BaseBuilder<NewsBuilder, WxXmlOutMewsMessage> { | ||||
@@ -0,0 +1,24 @@ | |||||
package me.chanjar.weixin.enterprise.bean.outxmlbuilder; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutTextMessage; | |||||
/** | |||||
* 文本消息builder | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public final class TextBuilder extends BaseBuilder<TextBuilder, WxXmlOutTextMessage> { | |||||
private String content; | |||||
public TextBuilder content(String content) { | |||||
this.content = content; | |||||
return this; | |||||
} | |||||
public WxXmlOutTextMessage build() { | |||||
WxXmlOutTextMessage m = new WxXmlOutTextMessage(); | |||||
setCommon(m); | |||||
m.setContent(this.content); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,38 @@ | |||||
package me.chanjar.weixin.enterprise.bean.outxmlbuilder; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutVideoMessage; | |||||
/** | |||||
* 视频消息builder | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public final class VideoBuilder extends BaseBuilder<VideoBuilder, WxXmlOutVideoMessage> { | |||||
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 WxXmlOutVideoMessage build() { | |||||
WxXmlOutVideoMessage m = new WxXmlOutVideoMessage(); | |||||
setCommon(m); | |||||
m.setTitle(title); | |||||
m.setDescription(description); | |||||
m.setMediaId(mediaId); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,25 @@ | |||||
package me.chanjar.weixin.enterprise.bean.outxmlbuilder; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutVoiceMessage; | |||||
/** | |||||
* 语音消息builder | |||||
* @author Daniel Qian | |||||
*/ | |||||
public final class VoiceBuilder extends BaseBuilder<VoiceBuilder, WxXmlOutVoiceMessage> { | |||||
private String mediaId; | |||||
public VoiceBuilder mediaId(String mediaId) { | |||||
this.mediaId = mediaId; | |||||
return this; | |||||
} | |||||
public WxXmlOutVoiceMessage build() { | |||||
WxXmlOutVoiceMessage m = new WxXmlOutVoiceMessage(); | |||||
setCommon(m); | |||||
m.setMediaId(mediaId); | |||||
return m; | |||||
} | |||||
} |
@@ -0,0 +1,136 @@ | |||||
package me.chanjar.weixin.enterprise.bean.result; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* 微信错误码说明 | |||||
* http://mp.weixin.qq.com/wiki/index.php?title=全局返回码说明 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
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(); | |||||
} | |||||
} |
@@ -0,0 +1,55 @@ | |||||
package me.chanjar.weixin.enterprise.bean.result; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* <pre> | |||||
* 群发消息一发送就返回的结果 | |||||
* | |||||
* 真正的群发消息是否发送成功要看 | |||||
* http://mp.weixin.qq.com/wiki/index.php?title=高级群发接口#.E4.BA.8B.E4.BB.B6.E6.8E.A8.E9.80.81.E7.BE.A4.E5.8F.91.E7.BB.93.E6.9E.9C | |||||
* | |||||
* </pre> | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMassSendResult { | |||||
private String errorCode; | |||||
private String errorMsg; | |||||
private String msgId; | |||||
public String getErrorCode() { | |||||
return errorCode; | |||||
} | |||||
public void setErrorCode(String errorCode) { | |||||
this.errorCode = errorCode; | |||||
} | |||||
public String getErrorMsg() { | |||||
return errorMsg; | |||||
} | |||||
public void setErrorMsg(String errorMsg) { | |||||
this.errorMsg = errorMsg; | |||||
} | |||||
public String getMsgId() { | |||||
return msgId; | |||||
} | |||||
public void setMsgId(String msgId) { | |||||
this.msgId = msgId; | |||||
} | |||||
public static WxMassSendResult fromJson(String json) { | |||||
return WxGsonBuilder.create().fromJson(json, WxMassSendResult.class); | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "WxMassSendResult [errcode=" + errorCode + ", errmsg=" + errorMsg + ", msg_id=" + msgId + "]"; | |||||
} | |||||
} |
@@ -0,0 +1,52 @@ | |||||
package me.chanjar.weixin.enterprise.bean.result; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* <pre> | |||||
* 上传群发用的素材的结果 | |||||
* 视频和图文消息需要在群发前上传素材 | |||||
* </pre> | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMassUploadResult { | |||||
private String type; | |||||
private String mediaId; | |||||
private long createdAt; | |||||
public String getType() { | |||||
return type; | |||||
} | |||||
public void setType(String type) { | |||||
this.type = type; | |||||
} | |||||
public String getMediaId() { | |||||
return mediaId; | |||||
} | |||||
public void setMediaId(String mediaId) { | |||||
this.mediaId = mediaId; | |||||
} | |||||
public long getCreatedAt() { | |||||
return createdAt; | |||||
} | |||||
public void setCreatedAt(long createdAt) { | |||||
this.createdAt = createdAt; | |||||
} | |||||
public static WxMassUploadResult fromJson(String json) { | |||||
return WxGsonBuilder.create().fromJson(json, WxMassUploadResult.class); | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "WxUploadResult [type=" + type + ", media_id=" + mediaId + ", created_at=" + createdAt + "]"; | |||||
} | |||||
} |
@@ -0,0 +1,54 @@ | |||||
package me.chanjar.weixin.enterprise.bean.result; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
public class WxMediaUploadResult { | |||||
private String type; | |||||
private String mediaId; | |||||
private String thumbMediaId; | |||||
private long createdAt; | |||||
public String getType() { | |||||
return type; | |||||
} | |||||
public void setType(String type) { | |||||
this.type = type; | |||||
} | |||||
public String getMediaId() { | |||||
return mediaId; | |||||
} | |||||
public void setMediaId(String mediaId) { | |||||
this.mediaId = mediaId; | |||||
} | |||||
public long getCreatedAt() { | |||||
return createdAt; | |||||
} | |||||
public void setCreatedAt(long createdAt) { | |||||
this.createdAt = createdAt; | |||||
} | |||||
public static WxMediaUploadResult fromJson(String json) { | |||||
return WxGsonBuilder.create().fromJson(json, WxMediaUploadResult.class); | |||||
} | |||||
public String getThumbMediaId() { | |||||
return thumbMediaId; | |||||
} | |||||
public void setThumbMediaId(String thumbMediaId) { | |||||
this.thumbMediaId = thumbMediaId; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "WxUploadResult [type=" + type + ", media_id=" + mediaId + ", thumb_media_id=" + thumbMediaId | |||||
+ ", created_at=" + createdAt + "]"; | |||||
} | |||||
} |
@@ -0,0 +1,46 @@ | |||||
package me.chanjar.weixin.enterprise.bean.result; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* 换取二维码的Ticket | |||||
* | |||||
* @author Daniel Qian | |||||
*/ | |||||
public class WxQrCodeTicket { | |||||
protected String ticket; | |||||
protected int expire_seconds = -1; | |||||
protected String url; | |||||
public String getTicket() { | |||||
return ticket; | |||||
} | |||||
public void setTicket(String ticket) { | |||||
this.ticket = ticket; | |||||
} | |||||
/** | |||||
* 如果返回-1说明是永久 | |||||
*/ | |||||
public int getExpire_seconds() { | |||||
return expire_seconds; | |||||
} | |||||
public void setExpire_seconds(int expire_seconds) { | |||||
this.expire_seconds = expire_seconds; | |||||
} | |||||
public String getUrl() { | |||||
return url; | |||||
} | |||||
public void setUrl(String url) { | |||||
this.url = url; | |||||
} | |||||
public static WxQrCodeTicket fromJson(String json) { | |||||
return WxGsonBuilder.INSTANCE.create().fromJson(json, WxQrCodeTicket.class); | |||||
} | |||||
} |
@@ -0,0 +1,95 @@ | |||||
package me.chanjar.weixin.enterprise.bean.result; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* 微信用户信息 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxUser { | |||||
protected boolean subscribe; | |||||
protected String openId; | |||||
protected String nickname; | |||||
protected String sex; | |||||
protected String language; | |||||
protected String city; | |||||
protected String province; | |||||
protected String country; | |||||
protected String headImgUrl; | |||||
protected long subscribeTime; | |||||
protected String unionId; | |||||
public boolean isSubscribe() { | |||||
return subscribe; | |||||
} | |||||
public void setSubscribe(boolean subscribe) { | |||||
this.subscribe = subscribe; | |||||
} | |||||
public String getOpenId() { | |||||
return openId; | |||||
} | |||||
public void setOpenId(String openId) { | |||||
this.openId = openId; | |||||
} | |||||
public String getNickname() { | |||||
return nickname; | |||||
} | |||||
public void setNickname(String nickname) { | |||||
this.nickname = nickname; | |||||
} | |||||
public String getSex() { | |||||
return sex; | |||||
} | |||||
public void setSex(String sex) { | |||||
this.sex = sex; | |||||
} | |||||
public String getLanguage() { | |||||
return language; | |||||
} | |||||
public void setLanguage(String language) { | |||||
this.language = language; | |||||
} | |||||
public String getCity() { | |||||
return city; | |||||
} | |||||
public void setCity(String city) { | |||||
this.city = city; | |||||
} | |||||
public String getProvince() { | |||||
return province; | |||||
} | |||||
public void setProvince(String province) { | |||||
this.province = province; | |||||
} | |||||
public String getCountry() { | |||||
return country; | |||||
} | |||||
public void setCountry(String country) { | |||||
this.country = country; | |||||
} | |||||
public String getHeadImgUrl() { | |||||
return headImgUrl; | |||||
} | |||||
public void setHeadImgUrl(String headImgUrl) { | |||||
this.headImgUrl = headImgUrl; | |||||
} | |||||
public long getSubscribeTime() { | |||||
return subscribeTime; | |||||
} | |||||
public void setSubscribeTime(long subscribeTime) { | |||||
this.subscribeTime = subscribeTime; | |||||
} | |||||
public String getUnionId() { | |||||
return unionId; | |||||
} | |||||
public void setUnionId(String unionId) { | |||||
this.unionId = unionId; | |||||
} | |||||
public static WxUser fromJson(String json) { | |||||
return WxGsonBuilder.INSTANCE.create().fromJson(json, WxUser.class); | |||||
} | |||||
} |
@@ -0,0 +1,47 @@ | |||||
package me.chanjar.weixin.enterprise.bean.result; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.enterprise.util.json.WxGsonBuilder; | |||||
/** | |||||
* 关注者列表 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxUserList { | |||||
protected int total = -1; | |||||
protected int count = -1; | |||||
protected List<String> openIds = new ArrayList<String>(); | |||||
protected String nextOpenId; | |||||
public int getTotal() { | |||||
return total; | |||||
} | |||||
public void setTotal(int total) { | |||||
this.total = total; | |||||
} | |||||
public int getCount() { | |||||
return count; | |||||
} | |||||
public void setCount(int count) { | |||||
this.count = count; | |||||
} | |||||
public List<String> getOpenIds() { | |||||
return openIds; | |||||
} | |||||
public void setOpenIds(List<String> openIds) { | |||||
this.openIds = openIds; | |||||
} | |||||
public String getNextOpenId() { | |||||
return nextOpenId; | |||||
} | |||||
public void setNextOpenId(String nextOpenId) { | |||||
this.nextOpenId = nextOpenId; | |||||
} | |||||
public static WxUserList fromJson(String json) { | |||||
return WxGsonBuilder.INSTANCE.create().fromJson(json, WxUserList.class); | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
package me.chanjar.weixin.enterprise.exception; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxError; | |||||
public class WxErrorException extends Exception { | |||||
private static final long serialVersionUID = -6357149550353160810L; | |||||
private WxError error; | |||||
public WxErrorException(WxError error) { | |||||
super(error.toString()); | |||||
this.error = error; | |||||
} | |||||
public WxError getError() { | |||||
return error; | |||||
} | |||||
} |
@@ -0,0 +1,26 @@ | |||||
package me.chanjar.weixin.enterprise.util.crypto; | |||||
import java.util.ArrayList; | |||||
class ByteGroup { | |||||
ArrayList<Byte> byteContainer = new ArrayList<Byte>(); | |||||
byte[] toBytes() { | |||||
byte[] bytes = new byte[byteContainer.size()]; | |||||
for (int i = 0; i < byteContainer.size(); i++) { | |||||
bytes[i] = byteContainer.get(i); | |||||
} | |||||
return bytes; | |||||
} | |||||
ByteGroup addBytes(byte[] bytes) { | |||||
for (byte b : bytes) { | |||||
byteContainer.add(b); | |||||
} | |||||
return this; | |||||
} | |||||
int size() { | |||||
return byteContainer.size(); | |||||
} | |||||
} |
@@ -0,0 +1,68 @@ | |||||
/** | |||||
* 对公众平台发送给公众账号的消息加解密示例代码. | |||||
* | |||||
* @copyright Copyright (c) 1998-2014 Tencent Inc. | |||||
*/ | |||||
// ------------------------------------------------------------------------ | |||||
package me.chanjar.weixin.enterprise.util.crypto; | |||||
import java.nio.charset.Charset; | |||||
import java.util.Arrays; | |||||
/** | |||||
* 提供基于PKCS7算法的加解 | |||||
*/ | |||||
class PKCS7Encoder { | |||||
private static final Charset CHARSET = Charset.forName("utf-8"); | |||||
private static final int BLOCK_SIZE = 32; | |||||
/** | |||||
* 获得对明文进行补位填充的字节. | |||||
* | |||||
* @param count 需要进行填充补位操作的明文字节个数 | |||||
* @return 补齐用的字节数组 | |||||
*/ | |||||
static byte[] encode(int count) { | |||||
// 计算需要填充的位数 | |||||
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE); | |||||
if (amountToPad == 0) { | |||||
amountToPad = BLOCK_SIZE; | |||||
} | |||||
// 获得补位所用的字符 | |||||
char padChr = chr(amountToPad); | |||||
String tmp = new String(); | |||||
for (int index = 0; index < amountToPad; index++) { | |||||
tmp += padChr; | |||||
} | |||||
return tmp.getBytes(CHARSET); | |||||
} | |||||
/** | |||||
* 删除解密后明文的补位字符 | |||||
* | |||||
* @param decrypted 解密后的明文 | |||||
* @return 删除补位字符后的明文 | |||||
*/ | |||||
static byte[] decode(byte[] decrypted) { | |||||
int pad = (int) decrypted[decrypted.length - 1]; | |||||
if (pad < 1 || pad > 32) { | |||||
pad = 0; | |||||
} | |||||
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad); | |||||
} | |||||
/** | |||||
* 将数字转化成ASCII码对应的字符,用于对明文进行补码 | |||||
* | |||||
* @param a 需要转化的数字 | |||||
* @return 转化得到的字符 | |||||
*/ | |||||
static char chr(int a) { | |||||
byte target = (byte) (a & 0xFF); | |||||
return (char) target; | |||||
} | |||||
} |
@@ -0,0 +1,42 @@ | |||||
package me.chanjar.weixin.enterprise.util.crypto; | |||||
import java.security.MessageDigest; | |||||
import java.security.NoSuchAlgorithmException; | |||||
import java.util.Arrays; | |||||
/** | |||||
* Created by Daniel Qian on 14/10/19. | |||||
*/ | |||||
public class SHA1 { | |||||
/** | |||||
* 生成SHA1签名 | |||||
* @param arr | |||||
* @return | |||||
*/ | |||||
public static String gen(String... arr) throws NoSuchAlgorithmException { | |||||
Arrays.sort(arr); | |||||
StringBuilder sb = new StringBuilder(); | |||||
for(String a : arr) { | |||||
sb.append(a); | |||||
} | |||||
MessageDigest sha1 = MessageDigest.getInstance("SHA1"); | |||||
sha1.update(sb.toString().getBytes()); | |||||
byte[] output = sha1.digest(); | |||||
return bytesToHex(output); | |||||
} | |||||
protected static String bytesToHex(byte[] b) { | |||||
char hexDigit[] = {'0', '1', '2', '3', '4', '5', '6', '7', | |||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; | |||||
StringBuffer buf = new StringBuffer(); | |||||
for (int j = 0; j < b.length; j++) { | |||||
buf.append(hexDigit[(b[j] >> 4) & 0x0f]); | |||||
buf.append(hexDigit[b[j] & 0x0f]); | |||||
} | |||||
return buf.toString(); | |||||
} | |||||
} |
@@ -0,0 +1,322 @@ | |||||
/** | |||||
* 对公众平台发送给公众账号的消息加解密示例代码. | |||||
* | |||||
* @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.enterprise.util.crypto; | |||||
import java.io.StringReader; | |||||
import java.nio.charset.Charset; | |||||
import java.security.NoSuchAlgorithmException; | |||||
import java.util.Arrays; | |||||
import java.util.Random; | |||||
import javax.crypto.Cipher; | |||||
import javax.crypto.spec.IvParameterSpec; | |||||
import javax.crypto.spec.SecretKeySpec; | |||||
import javax.xml.parsers.DocumentBuilder; | |||||
import javax.xml.parsers.DocumentBuilderFactory; | |||||
import javax.xml.parsers.ParserConfigurationException; | |||||
import me.chanjar.weixin.enterprise.api.WxCpConfigStorage; | |||||
import org.apache.commons.codec.binary.Base64; | |||||
import org.w3c.dom.Document; | |||||
import org.w3c.dom.Element; | |||||
import org.xml.sax.InputSource; | |||||
public class WxCryptUtil { | |||||
private static final Base64 base64 = new Base64(); | |||||
private static final Charset CHARSET = Charset.forName("utf-8"); | |||||
private static final ThreadLocal<DocumentBuilder> builderLocal = | |||||
new ThreadLocal<DocumentBuilder>() { | |||||
@Override protected DocumentBuilder initialValue() { | |||||
try { | |||||
return DocumentBuilderFactory.newInstance().newDocumentBuilder(); | |||||
} catch (ParserConfigurationException exc) { | |||||
throw new IllegalArgumentException(exc); | |||||
} | |||||
} | |||||
}; | |||||
private byte[] aesKey; | |||||
private String token; | |||||
private String corpId; | |||||
/** | |||||
* 构造函数 | |||||
* | |||||
* @param wxCpConfigStorage | |||||
*/ | |||||
public WxCryptUtil(WxCpConfigStorage wxCpConfigStorage) { | |||||
/* | |||||
* @param token 公众平台上,开发者设置的token | |||||
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey | |||||
* @param corpId 公众平台appid | |||||
*/ | |||||
String encodingAesKey = wxCpConfigStorage.getAesKey(); | |||||
String token = wxCpConfigStorage.getToken(); | |||||
String corpId = wxCpConfigStorage.getCorpId(); | |||||
this.token = token; | |||||
this.corpId = corpId; | |||||
this.aesKey = Base64.decodeBase64(encodingAesKey + "="); | |||||
} | |||||
/** | |||||
* 构造函数 | |||||
* | |||||
* @param token 公众平台上,开发者设置的token | |||||
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey | |||||
* @param corpId 公众平台appid | |||||
*/ | |||||
public WxCryptUtil(String token, String encodingAesKey, String corpId) { | |||||
this.token = token; | |||||
this.corpId = corpId; | |||||
this.aesKey = Base64.decodeBase64(encodingAesKey + "="); | |||||
} | |||||
/** | |||||
* 将公众平台回复用户的消息加密打包. | |||||
* <ol> | |||||
* <li>对要发送的消息进行AES-CBC加密</li> | |||||
* <li>生成安全签名</li> | |||||
* <li>将消息密文和安全签名打包成xml格式</li> | |||||
* </ol> | |||||
* | |||||
* @param plainText 公众平台待回复用户的消息,xml格式的字符串 | |||||
* @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串 | |||||
*/ | |||||
public String encrypt(String plainText) { | |||||
// 加密 | |||||
String encryptedXml = encrypt(genRandomStr(), plainText); | |||||
// 生成安全签名 | |||||
String timeStamp = timeStamp = Long.toString(System.currentTimeMillis() / 1000l); | |||||
String nonce = genRandomStr(); | |||||
try { | |||||
String signature = SHA1.gen(token, timeStamp, nonce, encryptedXml); | |||||
String result = generateXml(encryptedXml, signature, timeStamp, nonce); | |||||
return result; | |||||
} catch (NoSuchAlgorithmException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
/** | |||||
* 对明文进行加密. | |||||
* | |||||
* @param plainText 需要加密的明文 | |||||
* @return 加密后base64编码的字符串 | |||||
*/ | |||||
protected String encrypt(String randomStr, String plainText) { | |||||
ByteGroup byteCollector = new ByteGroup(); | |||||
byte[] randomStringBytes = randomStr.getBytes(CHARSET); | |||||
byte[] plainTextBytes = plainText.getBytes(CHARSET); | |||||
byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder(plainTextBytes.length); | |||||
byte[] appIdBytes = corpId.getBytes(CHARSET); | |||||
// randomStr + networkBytesOrder + text + appid | |||||
byteCollector.addBytes(randomStringBytes); | |||||
byteCollector.addBytes(bytesOfSizeInNetworkOrder); | |||||
byteCollector.addBytes(plainTextBytes); | |||||
byteCollector.addBytes(appIdBytes); | |||||
// ... + pad: 使用自定义的填充方式对明文进行补位填充 | |||||
byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); | |||||
byteCollector.addBytes(padBytes); | |||||
// 获得最终的字节流, 未加密 | |||||
byte[] unencrypted = byteCollector.toBytes(); | |||||
try { | |||||
// 设置加密模式为AES的CBC模式 | |||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | |||||
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES"); | |||||
IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16); | |||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); | |||||
// 加密 | |||||
byte[] encrypted = cipher.doFinal(unencrypted); | |||||
// 使用BASE64对加密后的字符串进行编码 | |||||
String base64Encrypted = base64.encodeToString(encrypted); | |||||
return base64Encrypted; | |||||
} catch (Exception e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
/** | |||||
* 检验消息的真实性,并且获取解密后的明文. | |||||
* <ol> | |||||
* <li>利用收到的密文生成安全签名,进行签名验证</li> | |||||
* <li>若验证通过,则提取xml中的加密消息</li> | |||||
* <li>对消息进行解密</li> | |||||
* </ol> | |||||
* | |||||
* @param msgSignature 签名串,对应URL参数的msg_signature | |||||
* @param timeStamp 时间戳,对应URL参数的timestamp | |||||
* @param nonce 随机串,对应URL参数的nonce | |||||
* @param encryptedXml 密文,对应POST请求的数据 | |||||
* @return 解密后的原文 | |||||
*/ | |||||
public String decrypt(String msgSignature, String timeStamp, String nonce, String encryptedXml) { | |||||
// 密钥,公众账号的app corpSecret | |||||
// 提取密文 | |||||
String cipherText = extractEncryptPart(encryptedXml); | |||||
try { | |||||
// 验证安全签名 | |||||
String signature = SHA1.gen(token, timeStamp, nonce, cipherText); | |||||
if (!signature.equals(msgSignature)) { | |||||
throw new RuntimeException("加密消息签名校验失败"); | |||||
} | |||||
} catch (NoSuchAlgorithmException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
// 解密 | |||||
String result = decrypt(cipherText); | |||||
return result; | |||||
} | |||||
/** | |||||
* 对密文进行解密. | |||||
* | |||||
* @param cipherText 需要解密的密文 | |||||
* @return 解密得到的明文 | |||||
*/ | |||||
public String decrypt(String cipherText) { | |||||
byte[] original; | |||||
try { | |||||
// 设置解密模式为AES的CBC模式 | |||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | |||||
SecretKeySpec key_spec = new SecretKeySpec(aesKey, "AES"); | |||||
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16)); | |||||
cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); | |||||
// 使用BASE64对密文进行解码 | |||||
byte[] encrypted = Base64.decodeBase64(cipherText); | |||||
// 解密 | |||||
original = cipher.doFinal(encrypted); | |||||
} catch (Exception e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
String xmlContent, from_appid; | |||||
try { | |||||
// 去除补位字符 | |||||
byte[] bytes = PKCS7Encoder.decode(original); | |||||
// 分离16位随机字符串,网络字节序和AppId | |||||
byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); | |||||
int xmlLength = bytesNetworkOrder2Number(networkOrder); | |||||
xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); | |||||
from_appid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), | |||||
CHARSET); | |||||
} catch (Exception e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
// appid不相同的情况 | |||||
if (!from_appid.equals(corpId)) { | |||||
throw new RuntimeException("AppID不正确"); | |||||
} | |||||
return xmlContent; | |||||
} | |||||
/** | |||||
* 将一个数字转换成生成4个字节的网络字节序bytes数组 | |||||
* | |||||
* @param number | |||||
*/ | |||||
private byte[] number2BytesInNetworkOrder(int number) { | |||||
byte[] orderBytes = new byte[4]; | |||||
orderBytes[3] = (byte) (number & 0xFF); | |||||
orderBytes[2] = (byte) (number >> 8 & 0xFF); | |||||
orderBytes[1] = (byte) (number >> 16 & 0xFF); | |||||
orderBytes[0] = (byte) (number >> 24 & 0xFF); | |||||
return orderBytes; | |||||
} | |||||
/** | |||||
* 4个字节的网络字节序bytes数组还原成一个数字 | |||||
* | |||||
* @param bytesInNetworkOrder | |||||
*/ | |||||
private int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) { | |||||
int sourceNumber = 0; | |||||
for (int i = 0; i < 4; i++) { | |||||
sourceNumber <<= 8; | |||||
sourceNumber |= bytesInNetworkOrder[i] & 0xff; | |||||
} | |||||
return sourceNumber; | |||||
} | |||||
/** | |||||
* 随机生成16位字符串 | |||||
* | |||||
* @return | |||||
*/ | |||||
private String genRandomStr() { | |||||
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | |||||
Random random = new Random(); | |||||
StringBuffer sb = new StringBuffer(); | |||||
for (int i = 0; i < 16; i++) { | |||||
int number = random.nextInt(base.length()); | |||||
sb.append(base.charAt(number)); | |||||
} | |||||
return sb.toString(); | |||||
} | |||||
/** | |||||
* 生成xml消息 | |||||
* | |||||
* @param encrypt 加密后的消息密文 | |||||
* @param signature 安全签名 | |||||
* @param timestamp 时间戳 | |||||
* @param nonce 随机字符串 | |||||
* @return 生成的xml字符串 | |||||
*/ | |||||
private String generateXml(String encrypt, String signature, String timestamp, String nonce) { | |||||
String format = | |||||
"<xml>\n" | |||||
+ "<Encrypt><![CDATA[%1$s]]></Encrypt>\n" | |||||
+ "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" | |||||
+ "<TimeStamp>%3$s</TimeStamp>\n" | |||||
+ "<Nonce><![CDATA[%4$s]]></Nonce>\n" | |||||
+ "</xml>"; | |||||
return String.format(format, encrypt, signature, timestamp, nonce); | |||||
} | |||||
static String extractEncryptPart(String xml) { | |||||
try { | |||||
DocumentBuilder db = builderLocal.get(); | |||||
Document document = db.parse(new InputSource(new StringReader(xml))); | |||||
Element root = document.getDocumentElement(); | |||||
return root.getElementsByTagName("Encrypt").item(0).getTextContent(); | |||||
} catch (Exception e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,47 @@ | |||||
package me.chanjar.weixin.enterprise.util.fs; | |||||
import java.io.File; | |||||
import java.io.FileOutputStream; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
public class FileUtils { | |||||
/** | |||||
* 创建临时文件 | |||||
* @param inputStream | |||||
* @param name 文件名 | |||||
* @param ext 扩展名 | |||||
* @return | |||||
* @throws IOException | |||||
*/ | |||||
public static File createTmpFile(InputStream inputStream, String name, String ext) throws IOException { | |||||
FileOutputStream fos = null; | |||||
try { | |||||
File tmpFile = File.createTempFile(name, '.' + ext); | |||||
tmpFile.deleteOnExit(); | |||||
fos = new FileOutputStream(tmpFile); | |||||
int read = 0; | |||||
byte[] bytes = new byte[1024 * 100]; | |||||
while ((read = inputStream.read(bytes)) != -1) { | |||||
fos.write(bytes, 0, read); | |||||
} | |||||
fos.flush(); | |||||
return tmpFile; | |||||
} finally { | |||||
if (inputStream != null) { | |||||
try { | |||||
inputStream.close(); | |||||
} catch (IOException e) { | |||||
} | |||||
} | |||||
if (fos != null) { | |||||
try { | |||||
fos.close(); | |||||
} catch (IOException e) { | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,27 @@ | |||||
package me.chanjar.weixin.enterprise.util.http; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import org.apache.http.HttpEntity; | |||||
import org.apache.http.HttpResponse; | |||||
import org.apache.http.StatusLine; | |||||
import org.apache.http.client.HttpResponseException; | |||||
import org.apache.http.client.ResponseHandler; | |||||
import org.apache.http.util.EntityUtils; | |||||
public class InputStreamResponseHandler implements ResponseHandler<InputStream> { | |||||
public static final ResponseHandler<InputStream> INSTANCE = new InputStreamResponseHandler(); | |||||
public InputStream handleResponse(final HttpResponse response) throws HttpResponseException, IOException { | |||||
final StatusLine statusLine = response.getStatusLine(); | |||||
final HttpEntity entity = response.getEntity(); | |||||
if (statusLine.getStatusCode() >= 300) { | |||||
EntityUtils.consume(entity); | |||||
throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); | |||||
} | |||||
return entity == null ? null : entity.getContent(); | |||||
} | |||||
} |
@@ -0,0 +1,68 @@ | |||||
package me.chanjar.weixin.enterprise.util.http; | |||||
import java.io.File; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.util.regex.Matcher; | |||||
import java.util.regex.Pattern; | |||||
import org.apache.commons.lang3.StringUtils; | |||||
import org.apache.http.Header; | |||||
import org.apache.http.client.ClientProtocolException; | |||||
import org.apache.http.client.methods.CloseableHttpResponse; | |||||
import org.apache.http.client.methods.HttpGet; | |||||
import org.apache.http.entity.ContentType; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxError; | |||||
import me.chanjar.weixin.enterprise.exception.WxErrorException; | |||||
import me.chanjar.weixin.enterprise.util.fs.FileUtils; | |||||
/** | |||||
* 下载媒体文件请求执行器,请求的参数是String, 返回的结果是File | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class MediaDownloadRequestExecutor implements RequestExecutor<File, String> { | |||||
@Override | |||||
public File execute(String uri, String queryParam) throws WxErrorException, ClientProtocolException, IOException { | |||||
if (queryParam != null) { | |||||
if (uri.indexOf('?') == -1) { | |||||
uri += '?'; | |||||
} | |||||
uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||||
} | |||||
HttpGet httpGet = new HttpGet(uri); | |||||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||||
Header[] contentTypeHeader = response.getHeaders("Content-Type"); | |||||
if (contentTypeHeader != null && contentTypeHeader.length > 0) { | |||||
// 下载媒体文件出错 | |||||
if (ContentType.TEXT_PLAIN.getMimeType().equals(contentTypeHeader[0].getValue())) { | |||||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
throw new WxErrorException(WxError.fromJson(responseContent)); | |||||
} | |||||
} | |||||
InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response); | |||||
// 视频文件不支持下载 | |||||
String fileName = getFileName(response); | |||||
if (StringUtils.isBlank(fileName)) { | |||||
return null; | |||||
} | |||||
String[] name_ext = fileName.split("\\."); | |||||
File localFile = FileUtils.createTmpFile(inputStream, name_ext[0], name_ext[1]); | |||||
return localFile; | |||||
} | |||||
protected String getFileName(CloseableHttpResponse response) { | |||||
Header[] contentDispositionHeader = response.getHeaders("Content-disposition"); | |||||
Pattern p = Pattern.compile(".*filename=\"(.*)\""); | |||||
Matcher m = p.matcher(contentDispositionHeader[0].getValue()); | |||||
m.matches(); | |||||
String fileName = m.group(1); | |||||
return fileName; | |||||
} | |||||
} |
@@ -0,0 +1,44 @@ | |||||
package me.chanjar.weixin.enterprise.util.http; | |||||
import java.io.File; | |||||
import java.io.IOException; | |||||
import org.apache.http.HttpEntity; | |||||
import org.apache.http.client.ClientProtocolException; | |||||
import org.apache.http.client.methods.CloseableHttpResponse; | |||||
import org.apache.http.client.methods.HttpPost; | |||||
import org.apache.http.entity.ContentType; | |||||
import org.apache.http.entity.mime.MultipartEntityBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxError; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxMediaUploadResult; | |||||
import me.chanjar.weixin.enterprise.exception.WxErrorException; | |||||
/** | |||||
* 上传媒体文件请求执行器,请求的参数是File, 返回的结果是String | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class MediaUploadRequestExecutor implements RequestExecutor<WxMediaUploadResult, File> { | |||||
@Override | |||||
public WxMediaUploadResult execute(String uri, File file) throws WxErrorException, ClientProtocolException, IOException { | |||||
HttpPost httpPost = new HttpPost(uri); | |||||
if (file != null) { | |||||
HttpEntity entity = MultipartEntityBuilder | |||||
.create() | |||||
.addBinaryBody("media", file) | |||||
.build(); | |||||
httpPost.setEntity(entity); | |||||
httpPost.setHeader("Content-Type", ContentType.MULTIPART_FORM_DATA.toString()); | |||||
} | |||||
CloseableHttpResponse response = httpclient.execute(httpPost); | |||||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
WxError error = WxError.fromJson(responseContent); | |||||
if (error.getErrorCode() != 0) { | |||||
throw new WxErrorException(error); | |||||
} | |||||
return WxMediaUploadResult.fromJson(responseContent); | |||||
} | |||||
} |
@@ -0,0 +1,56 @@ | |||||
package me.chanjar.weixin.enterprise.util.http; | |||||
import java.io.File; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.net.URLEncoder; | |||||
import java.util.UUID; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxQrCodeTicket; | |||||
import me.chanjar.weixin.enterprise.exception.WxErrorException; | |||||
import me.chanjar.weixin.enterprise.util.fs.FileUtils; | |||||
import org.apache.http.Header; | |||||
import org.apache.http.client.ClientProtocolException; | |||||
import org.apache.http.client.methods.CloseableHttpResponse; | |||||
import org.apache.http.client.methods.HttpGet; | |||||
import org.apache.http.entity.ContentType; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxError; | |||||
/** | |||||
* 获得QrCode图片 请求执行器 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class QrCodeRequestExecutor implements RequestExecutor<File, WxQrCodeTicket> { | |||||
@Override | |||||
public File execute(String uri, WxQrCodeTicket ticket) throws WxErrorException, ClientProtocolException, IOException { | |||||
if (ticket != null) { | |||||
if (uri.indexOf('?') == -1) { | |||||
uri += '?'; | |||||
} | |||||
uri += uri.endsWith("?") ? | |||||
"ticket=" + URLEncoder.encode(ticket.getTicket(), "UTF-8") | |||||
: | |||||
"&ticket=" + URLEncoder.encode(ticket.getTicket(), "UTF-8"); | |||||
} | |||||
HttpGet httpGet = new HttpGet(uri); | |||||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||||
Header[] contentTypeHeader = response.getHeaders("Content-Type"); | |||||
if (contentTypeHeader != null && contentTypeHeader.length > 0) { | |||||
// 出错 | |||||
if (ContentType.TEXT_PLAIN.getMimeType().equals(contentTypeHeader[0].getValue())) { | |||||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
throw new WxErrorException(WxError.fromJson(responseContent)); | |||||
} | |||||
} | |||||
InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response); | |||||
File localFile = FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg"); | |||||
return localFile; | |||||
} | |||||
} |
@@ -0,0 +1,24 @@ | |||||
package me.chanjar.weixin.enterprise.util.http; | |||||
import java.io.IOException; | |||||
import org.apache.http.client.ClientProtocolException; | |||||
import org.apache.http.impl.client.CloseableHttpClient; | |||||
import org.apache.http.impl.client.HttpClients; | |||||
import me.chanjar.weixin.enterprise.exception.WxErrorException; | |||||
/** | |||||
* http请求执行器 | |||||
* @author Daniel Qian | |||||
* | |||||
* @param <T> 返回值类型 | |||||
* @param <E> 请求参数类型 | |||||
*/ | |||||
public interface RequestExecutor<T, E> { | |||||
public static final CloseableHttpClient httpclient = HttpClients.createDefault(); | |||||
public T execute(String uri, E data) throws WxErrorException, ClientProtocolException, IOException; | |||||
} |
@@ -0,0 +1,37 @@ | |||||
package me.chanjar.weixin.enterprise.util.http; | |||||
import java.io.IOException; | |||||
import org.apache.http.client.ClientProtocolException; | |||||
import org.apache.http.client.methods.CloseableHttpResponse; | |||||
import org.apache.http.client.methods.HttpGet; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxError; | |||||
import me.chanjar.weixin.enterprise.exception.WxErrorException; | |||||
/** | |||||
* 简单的GET请求执行器,请求的参数是String, 返回的结果也是String | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class SimpleGetRequestExecutor implements RequestExecutor<String, String> { | |||||
@Override | |||||
public String execute(String uri, String queryParam) throws WxErrorException, ClientProtocolException, IOException { | |||||
if (queryParam != null) { | |||||
if (uri.indexOf('?') == -1) { | |||||
uri += '?'; | |||||
} | |||||
uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||||
} | |||||
HttpGet httpGet = new HttpGet(uri); | |||||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
WxError error = WxError.fromJson(responseContent); | |||||
if (error.getErrorCode() != 0) { | |||||
throw new WxErrorException(error); | |||||
} | |||||
return responseContent; | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
package me.chanjar.weixin.enterprise.util.http; | |||||
import java.io.IOException; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxError; | |||||
import me.chanjar.weixin.enterprise.exception.WxErrorException; | |||||
import org.apache.http.Consts; | |||||
import org.apache.http.client.ClientProtocolException; | |||||
import org.apache.http.client.methods.CloseableHttpResponse; | |||||
import org.apache.http.client.methods.HttpPost; | |||||
import org.apache.http.entity.StringEntity; | |||||
/** | |||||
* 简单的POST请求执行器,请求的参数是String, 返回的结果也是String | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class SimplePostRequestExecutor implements RequestExecutor<String, String> { | |||||
@Override | |||||
public String execute(String uri, String postEntity) throws WxErrorException, ClientProtocolException, IOException { | |||||
HttpPost httpPost = new HttpPost(uri); | |||||
if (postEntity != null) { | |||||
StringEntity entity = new StringEntity(postEntity, Consts.UTF_8); | |||||
httpPost.setEntity(entity); | |||||
} | |||||
CloseableHttpResponse response = httpclient.execute(httpPost); | |||||
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); | |||||
WxError error = WxError.fromJson(responseContent); | |||||
if (error.getErrorCode() != 0) { | |||||
throw new WxErrorException(error); | |||||
} | |||||
return responseContent; | |||||
} | |||||
} |
@@ -0,0 +1,32 @@ | |||||
package me.chanjar.weixin.enterprise.util.http; | |||||
import java.io.IOException; | |||||
import org.apache.http.Consts; | |||||
import org.apache.http.HttpEntity; | |||||
import org.apache.http.HttpResponse; | |||||
import org.apache.http.StatusLine; | |||||
import org.apache.http.client.HttpResponseException; | |||||
import org.apache.http.client.ResponseHandler; | |||||
import org.apache.http.util.EntityUtils; | |||||
/** | |||||
* copy from {@link org.apache.http.impl.client.BasicResponseHandler} | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class Utf8ResponseHandler implements ResponseHandler<String> { | |||||
public static final ResponseHandler<String> INSTANCE = new Utf8ResponseHandler(); | |||||
public String handleResponse(final HttpResponse response) throws HttpResponseException, IOException { | |||||
final StatusLine statusLine = response.getStatusLine(); | |||||
final HttpEntity entity = response.getEntity(); | |||||
if (statusLine.getStatusCode() >= 300) { | |||||
EntityUtils.consume(entity); | |||||
throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); | |||||
} | |||||
return entity == null ? null : EntityUtils.toString(entity, Consts.UTF_8); | |||||
} | |||||
} |
@@ -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.enterprise.util.json; | |||||
import com.google.gson.JsonElement; | |||||
import com.google.gson.JsonObject; | |||||
public class GsonHelper { | |||||
public static boolean isNull(JsonElement element) { | |||||
return element == null || element.isJsonNull(); | |||||
} | |||||
public static boolean isNotNull(JsonElement element) { | |||||
return !isNull(element); | |||||
} | |||||
public static Long getLong(JsonObject json, String property) { | |||||
return getAsLong(json.get(property)); | |||||
} | |||||
public static long getPrimitiveLong(JsonObject json, String property) { | |||||
return getAsPrimitiveLong(json.get(property)); | |||||
} | |||||
public static Integer getInteger(JsonObject json, String property) { | |||||
return getAsInteger(json.get(property)); | |||||
} | |||||
public static int getPrimitiveInteger(JsonObject json, String property) { | |||||
return getAsPrimitiveInt(json.get(property)); | |||||
} | |||||
public static Double getDouble(JsonObject json, String property) { | |||||
return getAsDouble(json.get(property)); | |||||
} | |||||
public static double getPrimitiveDouble(JsonObject json, String property) { | |||||
return getAsPrimitiveDouble(json.get(property)); | |||||
} | |||||
public static Float getFloat(JsonObject json, String property) { | |||||
return getAsFloat(json.get(property)); | |||||
} | |||||
public static float getPrimitiveFloat(JsonObject json, String property) { | |||||
return getAsPrimitiveFloat(json.get(property)); | |||||
} | |||||
public static Boolean getBoolean(JsonObject json, String property) { | |||||
return getAsBoolean(json.get(property)); | |||||
} | |||||
public static String getString(JsonObject json, String property) { | |||||
return getAsString(json.get(property)); | |||||
} | |||||
public static String getAsString(JsonElement element) { | |||||
return isNull(element) ? null : element.getAsString(); | |||||
} | |||||
public static Long getAsLong(JsonElement element) { | |||||
return isNull(element) ? null : element.getAsLong(); | |||||
} | |||||
public static long getAsPrimitiveLong(JsonElement element) { | |||||
Long r = getAsLong(element); | |||||
return r == null ? 0l : r; | |||||
} | |||||
public static Integer getAsInteger(JsonElement element) { | |||||
return isNull(element) ? null : element.getAsInt(); | |||||
} | |||||
public static int getAsPrimitiveInt(JsonElement element) { | |||||
Integer r = getAsInteger(element); | |||||
return r == null ? 0 : r; | |||||
} | |||||
public static Boolean getAsBoolean(JsonElement element) { | |||||
return isNull(element) ? null : element.getAsBoolean(); | |||||
} | |||||
public static boolean getAsPrimitiveBool(JsonElement element) { | |||||
Boolean r = getAsBoolean(element); | |||||
return r == null ? false : r.booleanValue(); | |||||
} | |||||
public static Double getAsDouble(JsonElement element) { | |||||
return isNull(element) ? null : element.getAsDouble(); | |||||
} | |||||
public static double getAsPrimitiveDouble(JsonElement element) { | |||||
Double r = getAsDouble(element); | |||||
return r == null ? 0d : r; | |||||
} | |||||
public static Float getAsFloat(JsonElement element) { | |||||
return isNull(element) ? null : element.getAsFloat(); | |||||
} | |||||
public static float getAsPrimitiveFloat(JsonElement element) { | |||||
Float r = getAsFloat(element); | |||||
return r == null ? 0f : r; | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import com.google.gson.*; | |||||
import me.chanjar.weixin.enterprise.bean.WxAccessToken; | |||||
import java.lang.reflect.Type; | |||||
/** | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxAccessTokenAdapter implements JsonDeserializer<WxAccessToken> { | |||||
public WxAccessToken deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||||
WxAccessToken accessToken = new WxAccessToken(); | |||||
JsonObject accessTokenJsonObject = json.getAsJsonObject(); | |||||
if (accessTokenJsonObject.get("access_token") != null && !accessTokenJsonObject.get("access_token").isJsonNull()) { | |||||
accessToken.setAccessToken(GsonHelper.getAsString(accessTokenJsonObject.get("access_token"))); | |||||
} | |||||
if (accessTokenJsonObject.get("expires_in") != null && !accessTokenJsonObject.get("expires_in").isJsonNull()) { | |||||
accessToken.setExpiresIn(GsonHelper.getAsPrimitiveInt(accessTokenJsonObject.get("expires_in"))); | |||||
} | |||||
return accessToken; | |||||
} | |||||
} |
@@ -0,0 +1,87 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import java.lang.reflect.Type; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage; | |||||
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; | |||||
/** | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxCustomMessageGsonAdapter implements JsonSerializer<WxCustomMessage> { | |||||
public JsonElement serialize(WxCustomMessage message, Type typeOfSrc, JsonSerializationContext context) { | |||||
JsonObject messageJson = new JsonObject(); | |||||
messageJson.addProperty("touser", message.getToUser()); | |||||
messageJson.addProperty("msgtype", message.getMsgType()); | |||||
if (WxConsts.CUSTOM_MSG_TEXT.equals(message.getMsgType())) { | |||||
JsonObject text = new JsonObject(); | |||||
text.addProperty("content", message.getContent()); | |||||
messageJson.add("text", text); | |||||
} | |||||
if (WxConsts.CUSTOM_MSG_IMAGE.equals(message.getMsgType())) { | |||||
JsonObject image = new JsonObject(); | |||||
image.addProperty("media_id", message.getMediaId()); | |||||
messageJson.add("image", image); | |||||
} | |||||
if (WxConsts.CUSTOM_MSG_VOICE.equals(message.getMsgType())) { | |||||
JsonObject voice = new JsonObject(); | |||||
voice.addProperty("media_id", message.getMediaId()); | |||||
messageJson.add("voice", voice); | |||||
} | |||||
if (WxConsts.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 (WxConsts.CUSTOM_MSG_MUSIC.equals(message.getMsgType())) { | |||||
JsonObject music = new JsonObject(); | |||||
music.addProperty("title", message.getTitle()); | |||||
music.addProperty("description", message.getDescription()); | |||||
music.addProperty("thumb_media_id", message.getThumbMediaId()); | |||||
music.addProperty("musicurl", message.getMusicUrl()); | |||||
music.addProperty("hqmusicurl", message.getHqMusicUrl()); | |||||
messageJson.add("music", music); | |||||
} | |||||
if (WxConsts.CUSTOM_MSG_NEWS.equals(message.getMsgType())) { | |||||
JsonArray articleJsonArray = new JsonArray(); | |||||
for (WxCustomMessage.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; | |||||
} | |||||
} |
@@ -6,11 +6,10 @@ | |||||
* arose from modification of the original source, or other redistribution of this source | * 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. | * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | ||||
*/ | */ | ||||
package me.chanjar.weixin.util.json; | |||||
package me.chanjar.weixin.enterprise.util.json; | |||||
import com.google.gson.*; | import com.google.gson.*; | ||||
import me.chanjar.weixin.bean.WxAccessToken; | |||||
import me.chanjar.weixin.bean.result.WxError; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxError; | |||||
import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||
@@ -0,0 +1,58 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import java.lang.reflect.Type; | |||||
import me.chanjar.weixin.enterprise.bean.WxDepartment; | |||||
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 WxGroupGsonAdapter implements JsonSerializer<WxDepartment>, JsonDeserializer<WxDepartment> { | |||||
public JsonElement serialize(WxDepartment group, Type typeOfSrc, JsonSerializationContext context) { | |||||
JsonObject json = new JsonObject(); | |||||
JsonObject groupJson = new JsonObject(); | |||||
groupJson.addProperty("name", group.getName()); | |||||
groupJson.addProperty("id", group.getId()); | |||||
groupJson.addProperty("count", group.getCount()); | |||||
json.add("group", groupJson); | |||||
return json; | |||||
} | |||||
public WxDepartment deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||||
WxDepartment group = new WxDepartment(); | |||||
JsonObject groupJson = json.getAsJsonObject(); | |||||
if (json.getAsJsonObject().get("group") != null) { | |||||
groupJson = json.getAsJsonObject().get("group").getAsJsonObject(); | |||||
} | |||||
if (groupJson.get("name") != null && !groupJson.get("name").isJsonNull()) { | |||||
group.setName(GsonHelper.getAsString(groupJson.get("name"))); | |||||
} | |||||
if (groupJson.get("id") != null && !groupJson.get("id").isJsonNull()) { | |||||
group.setId(GsonHelper.getAsPrimitiveLong(groupJson.get("id"))); | |||||
} | |||||
if (groupJson.get("count") != null && !groupJson.get("count").isJsonNull()) { | |||||
group.setCount(GsonHelper.getAsPrimitiveLong(groupJson.get("count"))); | |||||
} | |||||
return group; | |||||
} | |||||
} |
@@ -0,0 +1,36 @@ | |||||
package me.chanjar.weixin.enterprise.util.json; | |||||
import com.google.gson.Gson; | |||||
import com.google.gson.GsonBuilder; | |||||
import me.chanjar.weixin.enterprise.bean.*; | |||||
import me.chanjar.weixin.enterprise.bean.result.*; | |||||
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(WxDepartment.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(); | |||||
} | |||||
} |
@@ -0,0 +1,64 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import java.lang.reflect.Type; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.WxMassGroupMessage; | |||||
import com.google.gson.JsonElement; | |||||
import com.google.gson.JsonObject; | |||||
import com.google.gson.JsonSerializationContext; | |||||
import com.google.gson.JsonSerializer; | |||||
/** | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMassMessageGsonAdapter implements JsonSerializer<WxMassGroupMessage> { | |||||
public JsonElement serialize(WxMassGroupMessage message, Type typeOfSrc, JsonSerializationContext context) { | |||||
JsonObject messageJson = new JsonObject(); | |||||
JsonObject filter = new JsonObject(); | |||||
filter.addProperty("group_id", message.getGroupId()); | |||||
messageJson.add("filter", filter); | |||||
if (WxConsts.MASS_MSG_NEWS.equals(message.getMsgtype())) { | |||||
JsonObject sub = new JsonObject(); | |||||
sub.addProperty("media_id", message.getMediaId()); | |||||
messageJson.add(WxConsts.MASS_MSG_NEWS, sub); | |||||
} | |||||
if (WxConsts.MASS_MSG_TEXT.equals(message.getMsgtype())) { | |||||
JsonObject sub = new JsonObject(); | |||||
sub.addProperty("content", message.getContent()); | |||||
messageJson.add(WxConsts.MASS_MSG_TEXT, sub); | |||||
} | |||||
if (WxConsts.MASS_MSG_VOICE.equals(message.getMsgtype())) { | |||||
JsonObject sub = new JsonObject(); | |||||
sub.addProperty("media_id", message.getMediaId()); | |||||
messageJson.add(WxConsts.MASS_MSG_VOICE, sub); | |||||
} | |||||
if (WxConsts.MASS_MSG_IMAGE.equals(message.getMsgtype())) { | |||||
JsonObject sub = new JsonObject(); | |||||
sub.addProperty("media_id", message.getMediaId()); | |||||
messageJson.add(WxConsts.MASS_MSG_IMAGE, sub); | |||||
} | |||||
if (WxConsts.MASS_MSG_VIDEO.equals(message.getMsgtype())) { | |||||
JsonObject sub = new JsonObject(); | |||||
sub.addProperty("media_id", message.getMediaId()); | |||||
messageJson.add(WxConsts.MASS_MSG_VIDEO, sub); | |||||
} | |||||
messageJson.addProperty("msgtype", message.getMsgtype()); | |||||
return messageJson; | |||||
} | |||||
} |
@@ -0,0 +1,55 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import java.lang.reflect.Type; | |||||
import me.chanjar.weixin.enterprise.bean.WxMassNews; | |||||
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; | |||||
/** | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMassNewsGsonAdapter implements JsonSerializer<WxMassNews> { | |||||
public JsonElement serialize(WxMassNews message, Type typeOfSrc, JsonSerializationContext context) { | |||||
JsonObject newsJson = new JsonObject(); | |||||
JsonArray articleJsonArray = new JsonArray(); | |||||
for (WxMassNews.WxMassNewsArticle article : message.getArticles()) { | |||||
JsonObject articleJson = new JsonObject(); | |||||
articleJson.addProperty("thumb_media_id", article.getThumbMediaId()); | |||||
articleJson.addProperty("title", article.getTitle()); | |||||
articleJson.addProperty("content", article.getContent()); | |||||
if (null != article.getAuthor()) { | |||||
articleJson.addProperty("author", article.getAuthor()); | |||||
} | |||||
if (null != article.getContentSourceUrl()) { | |||||
articleJson.addProperty("content_source_url", article.getContentSourceUrl()); | |||||
} | |||||
if (null != article.getDigest()) { | |||||
articleJson.addProperty("digest", article.getDigest()); | |||||
} | |||||
articleJson.addProperty("show_cover_pic", article.isShowCoverPic() ? "1" : "0"); | |||||
articleJsonArray.add(articleJson); | |||||
} | |||||
newsJson.add("articles", articleJsonArray); | |||||
return newsJson; | |||||
} | |||||
} |
@@ -0,0 +1,68 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import java.lang.reflect.Type; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.WxMassOpenIdsMessage; | |||||
import com.google.gson.JsonArray; | |||||
import com.google.gson.JsonElement; | |||||
import com.google.gson.JsonObject; | |||||
import com.google.gson.JsonPrimitive; | |||||
import com.google.gson.JsonSerializationContext; | |||||
import com.google.gson.JsonSerializer; | |||||
/** | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMassOpenIdsMessageGsonAdapter implements JsonSerializer<WxMassOpenIdsMessage> { | |||||
public JsonElement serialize(WxMassOpenIdsMessage message, Type typeOfSrc, JsonSerializationContext context) { | |||||
JsonObject messageJson = new JsonObject(); | |||||
JsonArray toUsers = new JsonArray(); | |||||
for (String openId : message.getToUsers()) { | |||||
toUsers.add(new JsonPrimitive(openId)); | |||||
} | |||||
messageJson.add("touser", toUsers); | |||||
if (WxConsts.MASS_MSG_NEWS.equals(message.getMsgType())) { | |||||
JsonObject sub = new JsonObject(); | |||||
sub.addProperty("media_id", message.getMediaId()); | |||||
messageJson.add(WxConsts.MASS_MSG_NEWS, sub); | |||||
} | |||||
if (WxConsts.MASS_MSG_TEXT.equals(message.getMsgType())) { | |||||
JsonObject sub = new JsonObject(); | |||||
sub.addProperty("content", message.getContent()); | |||||
messageJson.add(WxConsts.MASS_MSG_TEXT, sub); | |||||
} | |||||
if (WxConsts.MASS_MSG_VOICE.equals(message.getMsgType())) { | |||||
JsonObject sub = new JsonObject(); | |||||
sub.addProperty("media_id", message.getMediaId()); | |||||
messageJson.add(WxConsts.MASS_MSG_VOICE, sub); | |||||
} | |||||
if (WxConsts.MASS_MSG_IMAGE.equals(message.getMsgType())) { | |||||
JsonObject sub = new JsonObject(); | |||||
sub.addProperty("media_id", message.getMediaId()); | |||||
messageJson.add(WxConsts.MASS_MSG_IMAGE, sub); | |||||
} | |||||
if (WxConsts.MASS_MSG_VIDEO.equals(message.getMsgType())) { | |||||
JsonObject sub = new JsonObject(); | |||||
sub.addProperty("media_id", message.getMediaId()); | |||||
messageJson.add(WxConsts.MASS_MSG_VIDEO, sub); | |||||
} | |||||
messageJson.addProperty("msgtype", message.getMsgType()); | |||||
return messageJson; | |||||
} | |||||
} |
@@ -0,0 +1,39 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import com.google.gson.*; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxMassSendResult; | |||||
import java.lang.reflect.Type; | |||||
/** | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMassSendResultAdapter implements JsonDeserializer<WxMassSendResult> { | |||||
public WxMassSendResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||||
WxMassSendResult sendResult = new WxMassSendResult(); | |||||
JsonObject sendResultJsonObject = json.getAsJsonObject(); | |||||
if (sendResultJsonObject.get("errcode") != null && !sendResultJsonObject.get("errcode").isJsonNull()) { | |||||
sendResult.setErrorCode(GsonHelper.getAsString(sendResultJsonObject.get("errcode"))); | |||||
} | |||||
if (sendResultJsonObject.get("errmsg") != null && !sendResultJsonObject.get("errmsg").isJsonNull()) { | |||||
sendResult.setErrorMsg(GsonHelper.getAsString(sendResultJsonObject.get("errmsg"))); | |||||
} | |||||
if (sendResultJsonObject.get("msg_id") != null && !sendResultJsonObject.get("msg_id").isJsonNull()) { | |||||
sendResult.setMsgId(GsonHelper.getAsString(sendResultJsonObject.get("msg_id"))); | |||||
} | |||||
return sendResult; | |||||
} | |||||
} |
@@ -0,0 +1,39 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import com.google.gson.*; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxMassUploadResult; | |||||
import java.lang.reflect.Type; | |||||
/** | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMassUploadResultAdapter implements JsonDeserializer<WxMassUploadResult> { | |||||
public WxMassUploadResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||||
WxMassUploadResult uploadResult = new WxMassUploadResult(); | |||||
JsonObject uploadResultJsonObject = json.getAsJsonObject(); | |||||
if (uploadResultJsonObject.get("type") != null && !uploadResultJsonObject.get("type").isJsonNull()) { | |||||
uploadResult.setType(GsonHelper.getAsString(uploadResultJsonObject.get("type"))); | |||||
} | |||||
if (uploadResultJsonObject.get("media_id") != null && !uploadResultJsonObject.get("media_id").isJsonNull()) { | |||||
uploadResult.setMediaId(GsonHelper.getAsString(uploadResultJsonObject.get("media_id"))); | |||||
} | |||||
if (uploadResultJsonObject.get("created_at") != null && !uploadResultJsonObject.get("created_at").isJsonNull()) { | |||||
uploadResult.setCreatedAt(GsonHelper.getAsPrimitiveLong(uploadResultJsonObject.get("created_at"))); | |||||
} | |||||
return uploadResult; | |||||
} | |||||
} |
@@ -0,0 +1,31 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import com.google.gson.*; | |||||
import me.chanjar.weixin.enterprise.bean.WxMassVideo; | |||||
import java.lang.reflect.Type; | |||||
/** | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMassVideoAdapter implements JsonSerializer<WxMassVideo> { | |||||
public JsonElement serialize(WxMassVideo message, Type typeOfSrc, JsonSerializationContext context) { | |||||
JsonObject messageJson = new JsonObject(); | |||||
messageJson.addProperty("media_id", message.getMediaId()); | |||||
messageJson.addProperty("description", message.getDescription()); | |||||
messageJson.addProperty("title", message.getTitle()); | |||||
return messageJson; | |||||
} | |||||
} |
@@ -0,0 +1,42 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import com.google.gson.*; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxMediaUploadResult; | |||||
import java.lang.reflect.Type; | |||||
/** | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMediaUploadResultAdapter implements JsonDeserializer<WxMediaUploadResult> { | |||||
public WxMediaUploadResult deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||||
WxMediaUploadResult uploadResult = new WxMediaUploadResult(); | |||||
JsonObject uploadResultJsonObject = json.getAsJsonObject(); | |||||
if (uploadResultJsonObject.get("type") != null && !uploadResultJsonObject.get("type").isJsonNull()) { | |||||
uploadResult.setType(GsonHelper.getAsString(uploadResultJsonObject.get("type"))); | |||||
} | |||||
if (uploadResultJsonObject.get("media_id") != null && !uploadResultJsonObject.get("media_id").isJsonNull()) { | |||||
uploadResult.setMediaId(GsonHelper.getAsString(uploadResultJsonObject.get("media_id"))); | |||||
} | |||||
if (uploadResultJsonObject.get("thumb_media_id") != null && !uploadResultJsonObject.get("thumb_media_id").isJsonNull()) { | |||||
uploadResult.setThumbMediaId(GsonHelper.getAsString(uploadResultJsonObject.get("thumb_media_id"))); | |||||
} | |||||
if (uploadResultJsonObject.get("created_at") != null && !uploadResultJsonObject.get("created_at").isJsonNull()) { | |||||
uploadResult.setCreatedAt(GsonHelper.getAsPrimitiveLong(uploadResultJsonObject.get("created_at"))); | |||||
} | |||||
return uploadResult; | |||||
} | |||||
} |
@@ -0,0 +1,94 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import java.lang.reflect.Type; | |||||
import me.chanjar.weixin.enterprise.bean.WxMenu; | |||||
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; | |||||
import com.google.gson.JsonSerializationContext; | |||||
import com.google.gson.JsonSerializer; | |||||
/** | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class WxMenuGsonAdapter implements JsonSerializer<WxMenu>, JsonDeserializer<WxMenu> { | |||||
public JsonElement serialize(WxMenu menu, Type typeOfSrc, JsonSerializationContext context) { | |||||
JsonObject json = new JsonObject(); | |||||
JsonArray buttonArray = new JsonArray(); | |||||
for (WxMenu.WxMenuButton button : menu.getButtons()) { | |||||
JsonObject buttonJson = convertToJson(button); | |||||
buttonArray.add(buttonJson); | |||||
} | |||||
json.add("button", buttonArray); | |||||
return json; | |||||
} | |||||
protected JsonObject convertToJson(WxMenu.WxMenuButton button) { | |||||
JsonObject buttonJson = new JsonObject(); | |||||
buttonJson.addProperty("type", button.getType()); | |||||
buttonJson.addProperty("name", button.getName()); | |||||
buttonJson.addProperty("key", button.getKey()); | |||||
buttonJson.addProperty("url", button.getUrl()); | |||||
if (button.getSubButtons() != null && button.getSubButtons().size() > 0) { | |||||
JsonArray buttonArray = new JsonArray(); | |||||
for (WxMenu.WxMenuButton sub_button : button.getSubButtons()) { | |||||
buttonArray.add(convertToJson(sub_button)); | |||||
} | |||||
buttonJson.add("sub_button", buttonArray); | |||||
} | |||||
return buttonJson; | |||||
} | |||||
public WxMenu deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | |||||
/* | |||||
* 操蛋的微信 | |||||
* 创建菜单时是 { button : ... } | |||||
* 查询菜单时是 { menu : { button : ... } } | |||||
*/ | |||||
WxMenu menu = new WxMenu(); | |||||
JsonObject menuJson = json.getAsJsonObject().get("menu").getAsJsonObject(); | |||||
JsonArray buttonsJson = menuJson.get("button").getAsJsonArray(); | |||||
for (int i = 0; i < buttonsJson.size(); i++) { | |||||
JsonObject buttonJson = buttonsJson.get(i).getAsJsonObject(); | |||||
WxMenu.WxMenuButton button = convertFromJson(buttonJson); | |||||
menu.getButtons().add(button); | |||||
if (buttonJson.get("sub_button") == null || buttonJson.get("sub_button").isJsonNull()) { | |||||
continue; | |||||
} | |||||
JsonArray sub_buttonsJson = buttonJson.get("sub_button").getAsJsonArray(); | |||||
for (int j = 0; j < sub_buttonsJson.size(); j++) { | |||||
JsonObject sub_buttonJson = sub_buttonsJson.get(j).getAsJsonObject(); | |||||
button.getSubButtons().add(convertFromJson(sub_buttonJson)); | |||||
} | |||||
} | |||||
return menu; | |||||
} | |||||
protected WxMenu.WxMenuButton convertFromJson(JsonObject json) { | |||||
WxMenu.WxMenuButton button = new WxMenu.WxMenuButton(); | |||||
button.setName(GsonHelper.getString(json, "name")); | |||||
button.setKey(GsonHelper.getString(json, "key")); | |||||
button.setUrl(GsonHelper.getString(json, "url")); | |||||
button.setType(GsonHelper.getString(json, "type")); | |||||
return button; | |||||
} | |||||
} |
@@ -6,11 +6,10 @@ | |||||
* arose from modification of the original source, or other redistribution of this source | * 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. | * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | ||||
*/ | */ | ||||
package me.chanjar.weixin.util.json; | |||||
package me.chanjar.weixin.enterprise.util.json; | |||||
import com.google.gson.*; | import com.google.gson.*; | ||||
import me.chanjar.weixin.bean.result.WxMassUploadResult; | |||||
import me.chanjar.weixin.bean.result.WxQrCodeTicket; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxQrCodeTicket; | |||||
import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||
@@ -0,0 +1,52 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import java.lang.reflect.Type; | |||||
import me.chanjar.weixin.enterprise.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 Daniel Qian | |||||
* | |||||
*/ | |||||
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; | |||||
} | |||||
} |
@@ -0,0 +1,42 @@ | |||||
/* | |||||
* 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.enterprise.util.json; | |||||
import java.lang.reflect.Type; | |||||
import me.chanjar.weixin.enterprise.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 Daniel Qian | |||||
* | |||||
*/ | |||||
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; | |||||
} | |||||
} |
@@ -0,0 +1,24 @@ | |||||
package me.chanjar.weixin.enterprise.util.xml; | |||||
import javax.xml.bind.annotation.adapters.XmlAdapter; | |||||
/** | |||||
* | |||||
* http://stackoverflow.com/questions/14193944/jaxb-marshalling-unmarshalling-with-cdata | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class AdapterCDATA extends XmlAdapter<String, String> { | |||||
@Override | |||||
public String marshal(String arg0) throws Exception { | |||||
return "<![CDATA[" + arg0 + "]]>"; | |||||
} | |||||
@Override | |||||
public String unmarshal(String arg0) throws Exception { | |||||
return arg0; | |||||
} | |||||
} |
@@ -0,0 +1,21 @@ | |||||
package me.chanjar.weixin.enterprise.util.xml; | |||||
import javax.xml.bind.annotation.adapters.XmlAdapter; | |||||
/** | |||||
* @author Daniel Qian | |||||
*/ | |||||
public class MediaIdMarshaller extends XmlAdapter<String, String> { | |||||
@Override | |||||
public String marshal(String arg0) throws Exception { | |||||
return "<MediaId><![CDATA[" + arg0 + "]]></MediaId>"; | |||||
} | |||||
@Override | |||||
public String unmarshal(String arg0) throws Exception { | |||||
// do nothing | |||||
return arg0; | |||||
} | |||||
} |
@@ -0,0 +1,94 @@ | |||||
package me.chanjar.weixin.enterprise.util.xml; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.io.StringReader; | |||||
import java.io.StringWriter; | |||||
import java.io.Writer; | |||||
import javax.xml.bind.*; | |||||
import me.chanjar.weixin.enterprise.bean.*; | |||||
import me.chanjar.weixin.enterprise.bean.*; | |||||
import org.xml.sax.InputSource; | |||||
import com.sun.xml.bind.marshaller.CharacterEscapeHandler; | |||||
public class XmlTransformer { | |||||
protected static final JAXBContext JAXB_CONTEXT = initJAXBContext(); | |||||
/** | |||||
* xml -> pojo | |||||
* | |||||
* @param clazz | |||||
* @param xml | |||||
* @return | |||||
* @throws JAXBException | |||||
*/ | |||||
@SuppressWarnings("unchecked") | |||||
public static <T> T fromXml(Class<T> clazz, String xml) throws JAXBException { | |||||
Unmarshaller um = JAXB_CONTEXT.createUnmarshaller(); | |||||
T object = (T) um.unmarshal(new StringReader(xml)); | |||||
return object; | |||||
} | |||||
@SuppressWarnings("unchecked") | |||||
public static <T> T fromXml(Class<T> clazz, InputStream is) throws JAXBException { | |||||
Unmarshaller um = JAXB_CONTEXT.createUnmarshaller(); | |||||
InputSource inputSource = new InputSource(is); | |||||
inputSource.setEncoding("utf-8"); | |||||
T object = (T) um.unmarshal(inputSource); | |||||
return object; | |||||
} | |||||
/** | |||||
* pojo -> xml | |||||
* | |||||
* @param clazz | |||||
* @param object | |||||
* @return | |||||
* @throws JAXBException | |||||
*/ | |||||
public static <T> String toXml(Class<T> clazz, T object) throws JAXBException { | |||||
StringWriter stringWriter = new StringWriter(); | |||||
toXml(clazz, object, stringWriter); | |||||
return stringWriter.getBuffer().toString(); | |||||
} | |||||
public static <T> void toXml(Class<T> clazz, T object, Writer writer) throws JAXBException { | |||||
Marshaller m = JAXB_CONTEXT.createMarshaller(); | |||||
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); | |||||
m.setProperty(CharacterEscapeHandler.class.getName(), CHAR_ESCAPE_HANDLER); | |||||
m.marshal(object, writer); | |||||
} | |||||
protected static final CharacterEscapeHandler CHAR_ESCAPE_HANDLER = new CharacterUnescapeHandler(); | |||||
protected static class CharacterUnescapeHandler implements CharacterEscapeHandler { | |||||
public void escape(char[] ac, int i, int j, boolean flag, Writer writer) throws IOException { | |||||
writer.write(ac, i, j); | |||||
} | |||||
} | |||||
private static JAXBContext initJAXBContext() { | |||||
/* | |||||
* JAXBContext对象是线程安全的,根据官方文档的建议将对象作为全局实例 | |||||
* https://jaxb.java.net/guide/Performance_and_thread_safety.html | |||||
*/ | |||||
try { | |||||
return JAXBContext.newInstance( | |||||
WxXmlOutMessage.class, | |||||
WxXmlOutImageMessage.class, | |||||
WxXmlOutMewsMessage.class, | |||||
WxXmlOutMusicMessage.class, | |||||
WxXmlOutTextMessage.class, | |||||
WxXmlOutVideoMessage.class, | |||||
WxXmlOutVoiceMessage.class, | |||||
WxCpXmlMessage.class); | |||||
} catch (JAXBException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,61 @@ | |||||
package me.chanjar.weixin.enterprise.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"); | |||||
WxXmlCpConfigStorage config = fromXml(WxXmlCpConfigStorage.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 WxXmlCpConfigStorage extends WxInMemoryCpConfigStorage { | |||||
protected String openId; | |||||
public String getOpenId() { | |||||
return openId; | |||||
} | |||||
public void setOpenId(String openId) { | |||||
this.openId = openId; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "SimpleWxConfigProvider [corpId=" + corpId + ", corpSecret=" + corpSecret + ", accessToken=" + accessToken | |||||
+ ", expiresIn=" + expiresIn + ", token=" + token + ", openId=" + openId + "]"; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,35 @@ | |||||
package me.chanjar.weixin.enterprise.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.enterprise.exception.WxErrorException; | |||||
import com.google.inject.Inject; | |||||
/** | |||||
* 基础API测试 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
@Test(groups = "baseAPI") | |||||
@Guice(modules = ApiTestModule.class) | |||||
public class WxBaseAPITest { | |||||
@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,159 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import java.util.Map; | |||||
import me.chanjar.weixin.enterprise.bean.WxCpXmlMessage; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.DataProvider; | |||||
import org.testng.annotations.Test; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMessage; | |||||
/** | |||||
* 测试消息路由器 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
@Test | |||||
public class WxCpMessageRouterTest { | |||||
@Test(enabled = false) | |||||
public void prepare(boolean async, StringBuffer sb, WxCpMessageRouter router) { | |||||
router | |||||
.rule() | |||||
.async(async) | |||||
.msgType(WxConsts.XML_MSG_TEXT).event(WxConsts.EVT_CLICK).eventKey("KEY_1").content("CONTENT_1") | |||||
.handler(new WxEchoCpMessageHandler(sb, "COMBINE_4")) | |||||
.end() | |||||
.rule() | |||||
.async(async) | |||||
.msgType(WxConsts.XML_MSG_TEXT).event(WxConsts.EVT_CLICK).eventKey("KEY_1") | |||||
.handler(new WxEchoCpMessageHandler(sb, "COMBINE_3")) | |||||
.end() | |||||
.rule() | |||||
.async(async) | |||||
.msgType(WxConsts.XML_MSG_TEXT).event(WxConsts.EVT_CLICK) | |||||
.handler(new WxEchoCpMessageHandler(sb, "COMBINE_2")) | |||||
.end() | |||||
.rule().async(async).msgType(WxConsts.XML_MSG_TEXT).handler(new WxEchoCpMessageHandler(sb, WxConsts.XML_MSG_TEXT)).end() | |||||
.rule().async(async).event(WxConsts.EVT_CLICK).handler(new WxEchoCpMessageHandler(sb, WxConsts.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 WxXmlOutMessage 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(WxConsts.XML_MSG_TEXT); | |||||
WxCpXmlMessage message2 = new WxCpXmlMessage(); | |||||
message2.setEvent(WxConsts.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(WxConsts.XML_MSG_TEXT); | |||||
c2.setEvent(WxConsts.EVT_CLICK); | |||||
WxCpXmlMessage c3 = new WxCpXmlMessage(); | |||||
c3.setMsgType(WxConsts.XML_MSG_TEXT); | |||||
c3.setEvent(WxConsts.EVT_CLICK); | |||||
c3.setEventKey("KEY_1"); | |||||
WxCpXmlMessage c4 = new WxCpXmlMessage(); | |||||
c4.setMsgType(WxConsts.XML_MSG_TEXT); | |||||
c4.setEvent(WxConsts.EVT_CLICK); | |||||
c4.setEventKey("KEY_1"); | |||||
c4.setContent("CONTENT_1"); | |||||
return new Object[][] { | |||||
new Object[] { message1, WxConsts.XML_MSG_TEXT + "," }, | |||||
new Object[] { message2, WxConsts.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 WxXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context) { | |||||
sb.append(this.echoStr).append(','); | |||||
return null; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,33 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import org.testng.annotations.Guice; | |||||
import org.testng.annotations.Test; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage; | |||||
import me.chanjar.weixin.enterprise.exception.WxErrorException; | |||||
import com.google.inject.Inject; | |||||
/*** | |||||
* 测试发送客服消息 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
@Test(groups="customMessageAPI", dependsOnGroups = "baseAPI") | |||||
@Guice(modules = ApiTestModule.class) | |||||
public class WxCustomMessageAPITest { | |||||
@Inject | |||||
protected WxCpServiceImpl wxService; | |||||
public void testSendCustomMessage() throws WxErrorException { | |||||
ApiTestModule.WxXmlCpConfigStorage configStorage = (ApiTestModule.WxXmlCpConfigStorage) wxService.wxCpConfigStorage; | |||||
WxCustomMessage message = new WxCustomMessage(); | |||||
message.setMsgType(WxConsts.CUSTOM_MSG_TEXT); | |||||
message.setToUser(configStorage.getOpenId()); | |||||
message.setContent("欢迎欢迎,热烈欢迎\n换行测试\n超链接:<a href=\"http://www.baidu.com\">Hello World</a>"); | |||||
wxService.messageSend(message); | |||||
} | |||||
} |
@@ -0,0 +1,50 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.enterprise.bean.WxDepartment; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.Guice; | |||||
import org.testng.annotations.Test; | |||||
import me.chanjar.weixin.enterprise.exception.WxErrorException; | |||||
import com.google.inject.Inject; | |||||
/** | |||||
* 测试分组接口 | |||||
* | |||||
* @author Daniel Qian | |||||
*/ | |||||
@Test(groups = "groupAPI", dependsOnGroups = "baseAPI") | |||||
@Guice(modules = ApiTestModule.class) | |||||
public class WxGroupAPITest { | |||||
@Inject | |||||
protected WxCpServiceImpl wxService; | |||||
protected WxDepartment group; | |||||
public void testGroupCreate() throws WxErrorException { | |||||
WxDepartment res = wxService.departmentCreate("测试分组1"); | |||||
Assert.assertEquals(res.getName(), "测试分组1"); | |||||
} | |||||
@Test(dependsOnMethods="testGroupCreate") | |||||
public void testGroupGet() throws WxErrorException { | |||||
List<WxDepartment> groupList = wxService.departmentGet(); | |||||
Assert.assertNotNull(groupList); | |||||
Assert.assertTrue(groupList.size() > 0); | |||||
for (WxDepartment g : groupList) { | |||||
group = g; | |||||
Assert.assertNotNull(g.getName()); | |||||
} | |||||
} | |||||
@Test(dependsOnMethods={"testGroupGet", "testGroupCreate"}) | |||||
public void getGroupUpdate() throws WxErrorException { | |||||
group.setName("分组改名"); | |||||
wxService.departmentUpdate(group); | |||||
} | |||||
} |
@@ -0,0 +1,72 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.DataProvider; | |||||
import org.testng.annotations.Guice; | |||||
import org.testng.annotations.Test; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxMediaUploadResult; | |||||
import me.chanjar.weixin.enterprise.exception.WxErrorException; | |||||
import com.google.inject.Inject; | |||||
/** | |||||
* 测试多媒体文件上传下载 | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
@Test(groups="mediaAPI", dependsOnGroups="baseAPI") | |||||
@Guice(modules = ApiTestModule.class) | |||||
public class WxMediaAPITest { | |||||
@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[] { WxConsts.MEDIA_IMAGE, WxConsts.FILE_JPG, "mm.jpeg" }, | |||||
new Object[] { WxConsts.MEDIA_VOICE, WxConsts.FILE_MP3, "mm.mp3" }, | |||||
new Object[] { WxConsts.MEDIA_VIDEO, WxConsts.FILE_MP4, "mm.mp4" }, | |||||
new Object[] { WxConsts.MEDIA_THUMB, WxConsts.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,91 @@ | |||||
package me.chanjar.weixin.enterprise.api; | |||||
import javax.xml.bind.JAXBException; | |||||
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.enterprise.bean.WxMenu; | |||||
import me.chanjar.weixin.enterprise.bean.WxMenu.WxMenuButton; | |||||
import me.chanjar.weixin.enterprise.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(WxConsts.BUTTON_CLICK); | |||||
button1.setName("今日歌曲"); | |||||
button1.setKey("V1001_TODAY_MUSIC"); | |||||
WxMenuButton button2 = new WxMenuButton(); | |||||
button2.setType(WxConsts.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(WxConsts.BUTTON_VIEW); | |||||
button31.setName("搜索"); | |||||
button31.setUrl("http://www.soso.com/"); | |||||
WxMenuButton button32 = new WxMenuButton(); | |||||
button32.setType(WxConsts.BUTTON_VIEW); | |||||
button32.setName("视频"); | |||||
button32.setUrl("http://v.qq.com/"); | |||||
WxMenuButton button33 = new WxMenuButton(); | |||||
button33.setType(WxConsts.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,19 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.bean.WxAccessToken; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.Test; | |||||
@Test | |||||
public class WxAccessTokenTest { | |||||
public void testFromJson() { | |||||
String json = "{\"access_token\":\"ACCESS_TOKEN\",\"expires_in\":7200}"; | |||||
WxAccessToken wxError = WxAccessToken.fromJson(json); | |||||
Assert.assertEquals(wxError.getAccessToken(), "ACCESS_TOKEN"); | |||||
Assert.assertTrue(wxError.getExpiresIn() == 7200); | |||||
} | |||||
} |
@@ -0,0 +1,91 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
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(), WxConsts.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"); | |||||
} | |||||
} |
@@ -0,0 +1,133 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.Test; | |||||
import me.chanjar.weixin.enterprise.api.WxConsts; | |||||
import me.chanjar.weixin.enterprise.bean.WxCustomMessage.WxArticle; | |||||
@Test | |||||
public class WxCustomMessageTest { | |||||
public void testTextReply() { | |||||
WxCustomMessage reply = new WxCustomMessage(); | |||||
reply.setToUser("OPENID"); | |||||
reply.setMsgType(WxConsts.CUSTOM_MSG_TEXT); | |||||
reply.setContent("sfsfdsdf"); | |||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"sfsfdsdf\"}}"); | |||||
} | |||||
public void testTextBuild() { | |||||
WxCustomMessage reply = WxCustomMessage.TEXT().toUser("OPENID").content("sfsfdsdf").build(); | |||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"text\",\"text\":{\"content\":\"sfsfdsdf\"}}"); | |||||
} | |||||
public void testImageReply() { | |||||
WxCustomMessage reply = new WxCustomMessage(); | |||||
reply.setToUser("OPENID"); | |||||
reply.setMsgType(WxConsts.CUSTOM_MSG_IMAGE); | |||||
reply.setMediaId("MEDIA_ID"); | |||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"image\",\"image\":{\"media_id\":\"MEDIA_ID\"}}"); | |||||
} | |||||
public void testImageBuild() { | |||||
WxCustomMessage reply = WxCustomMessage.IMAGE().toUser("OPENID").mediaId("MEDIA_ID").build(); | |||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"image\",\"image\":{\"media_id\":\"MEDIA_ID\"}}"); | |||||
} | |||||
public void testVoiceReply() { | |||||
WxCustomMessage reply = new WxCustomMessage(); | |||||
reply.setToUser("OPENID"); | |||||
reply.setMsgType(WxConsts.CUSTOM_MSG_VOICE); | |||||
reply.setMediaId("MEDIA_ID"); | |||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"voice\",\"voice\":{\"media_id\":\"MEDIA_ID\"}}"); | |||||
} | |||||
public void testVoiceBuild() { | |||||
WxCustomMessage reply = WxCustomMessage.VOICE().toUser("OPENID").mediaId("MEDIA_ID").build(); | |||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"voice\",\"voice\":{\"media_id\":\"MEDIA_ID\"}}"); | |||||
} | |||||
public void testVideoReply() { | |||||
WxCustomMessage reply = new WxCustomMessage(); | |||||
reply.setToUser("OPENID"); | |||||
reply.setMsgType(WxConsts.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() { | |||||
WxCustomMessage reply = WxCustomMessage.VIDEO().toUser("OPENID").title("TITLE").mediaId("MEDIA_ID").thumbMediaId("MEDIA_ID").description("DESCRIPTION").build(); | |||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"video\",\"video\":{\"media_id\":\"MEDIA_ID\",\"thumb_media_id\":\"MEDIA_ID\",\"title\":\"TITLE\",\"description\":\"DESCRIPTION\"}}"); | |||||
} | |||||
public void testMusicReply() { | |||||
WxCustomMessage reply = new WxCustomMessage(); | |||||
reply.setToUser("OPENID"); | |||||
reply.setMsgType(WxConsts.CUSTOM_MSG_MUSIC); | |||||
reply.setThumbMediaId("MEDIA_ID"); | |||||
reply.setDescription("DESCRIPTION"); | |||||
reply.setTitle("TITLE"); | |||||
reply.setMusicUrl("MUSIC_URL"); | |||||
reply.setHqMusicUrl("HQ_MUSIC_URL"); | |||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"music\",\"music\":{\"title\":\"TITLE\",\"description\":\"DESCRIPTION\",\"thumb_media_id\":\"MEDIA_ID\",\"musicurl\":\"MUSIC_URL\",\"hqmusicurl\":\"HQ_MUSIC_URL\"}}"); | |||||
} | |||||
public void testMusicBuild() { | |||||
WxCustomMessage reply = WxCustomMessage.MUSIC() | |||||
.toUser("OPENID") | |||||
.title("TITLE") | |||||
.thumbMediaId("MEDIA_ID") | |||||
.description("DESCRIPTION") | |||||
.musicUrl("MUSIC_URL") | |||||
.hqMusicUrl("HQ_MUSIC_URL") | |||||
.build(); | |||||
Assert.assertEquals(reply.toJson(), "{\"touser\":\"OPENID\",\"msgtype\":\"music\",\"music\":{\"title\":\"TITLE\",\"description\":\"DESCRIPTION\",\"thumb_media_id\":\"MEDIA_ID\",\"musicurl\":\"MUSIC_URL\",\"hqmusicurl\":\"HQ_MUSIC_URL\"}}"); | |||||
} | |||||
public void testNewsReply() { | |||||
WxCustomMessage reply = new WxCustomMessage(); | |||||
reply.setToUser("OPENID"); | |||||
reply.setMsgType(WxConsts.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"); | |||||
WxCustomMessage reply = WxCustomMessage.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,38 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.Test; | |||||
import me.chanjar.weixin.enterprise.bean.result.WxError; | |||||
@Test | |||||
public class WxErrorTest { | |||||
public void testFromJson() { | |||||
String json = "{ \"errcode\": 40003, \"errmsg\": \"invalid openid\" }"; | |||||
WxError wxError = WxError.fromJson(json); | |||||
Assert.assertTrue(wxError.getErrorCode() == 40003); | |||||
Assert.assertEquals(wxError.getErrorMsg(), "invalid openid"); | |||||
} | |||||
public void testFromBadJson1() { | |||||
String json = "{ \"errcode\": 40003, \"errmsg\": \"invalid openid\", \"media_id\": \"12323423dsfafsf232f\" }"; | |||||
WxError wxError = WxError.fromJson(json); | |||||
Assert.assertTrue(wxError.getErrorCode() == 40003); | |||||
Assert.assertEquals(wxError.getErrorMsg(), "invalid openid"); | |||||
} | |||||
public void testFromBadJson2() { | |||||
String json = "{\"access_token\":\"ACCESS_TOKEN\",\"expires_in\":7200}"; | |||||
WxError wxError = WxError.fromJson(json); | |||||
Assert.assertTrue(wxError.getErrorCode() == 0); | |||||
Assert.assertEquals(wxError.getErrorMsg(), null); | |||||
} | |||||
} |
@@ -0,0 +1,112 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.bean.WxMenu; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.DataProvider; | |||||
import org.testng.annotations.Test; | |||||
import me.chanjar.weixin.enterprise.bean.WxMenu.WxMenuButton; | |||||
@Test | |||||
public class WxMenuTest { | |||||
@Test(dataProvider="wxReturnMenu") | |||||
public void testFromJson(String json) { | |||||
WxMenu menu = WxMenu.fromJson(json); | |||||
Assert.assertEquals(menu.getButtons().size(), 3); | |||||
} | |||||
@Test(dataProvider="wxPushMenu") | |||||
public void testToJson(String json) { | |||||
WxMenu menu = new WxMenu(); | |||||
WxMenuButton button1 = new WxMenuButton(); | |||||
button1.setType("click"); | |||||
button1.setName("今日歌曲"); | |||||
button1.setKey("V1001_TODAY_MUSIC"); | |||||
WxMenuButton button2 = new WxMenuButton(); | |||||
button2.setType("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("view"); | |||||
button31.setName("搜索"); | |||||
button31.setUrl("http://www.soso.com/"); | |||||
WxMenuButton button32 = new WxMenuButton(); | |||||
button32.setType("view"); | |||||
button32.setName("视频"); | |||||
button32.setUrl("http://v.qq.com/"); | |||||
WxMenuButton button33 = new WxMenuButton(); | |||||
button33.setType("click"); | |||||
button33.setName("赞一下我们"); | |||||
button33.setKey("V1001_GOOD"); | |||||
button3.getSubButtons().add(button31); | |||||
button3.getSubButtons().add(button32); | |||||
button3.getSubButtons().add(button33); | |||||
Assert.assertEquals(menu.toJson(), json); | |||||
} | |||||
@DataProvider | |||||
public Object[][] wxReturnMenu() { | |||||
Object[][] res = menuJson(); | |||||
String json = "{ \"menu\" : " + res[0][0] + " }"; | |||||
return new Object[][] { | |||||
new Object[] { json } | |||||
}; | |||||
} | |||||
@DataProvider(name="wxPushMenu") | |||||
public Object[][] menuJson() { | |||||
String json = | |||||
"{" | |||||
+"\"button\":[" | |||||
+"{" | |||||
+"\"type\":\"click\"," | |||||
+"\"name\":\"今日歌曲\"," | |||||
+"\"key\":\"V1001_TODAY_MUSIC\"" | |||||
+"}," | |||||
+"{" | |||||
+"\"type\":\"click\"," | |||||
+"\"name\":\"歌手简介\"," | |||||
+"\"key\":\"V1001_TODAY_SINGER\"" | |||||
+"}," | |||||
+"{" | |||||
+"\"name\":\"菜单\"," | |||||
+"\"sub_button\":[" | |||||
+"{" | |||||
+"\"type\":\"view\"," | |||||
+"\"name\":\"搜索\"," | |||||
+"\"url\":\"http://www.soso.com/\"" | |||||
+"}," | |||||
+"{" | |||||
+"\"type\":\"view\"," | |||||
+"\"name\":\"视频\"," | |||||
+"\"url\":\"http://v.qq.com/\"" | |||||
+"}," | |||||
+"{" | |||||
+"\"type\":\"click\"," | |||||
+"\"name\":\"赞一下我们\"," | |||||
+"\"key\":\"V1001_GOOD\"" | |||||
+"}" | |||||
+"]" | |||||
+"}" | |||||
+"]" | |||||
+"}"; | |||||
return new Object[][] { | |||||
new Object[] { json } | |||||
}; | |||||
} | |||||
} |
@@ -0,0 +1,52 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutImageMessage; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMessage; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.Test; | |||||
@Test | |||||
public class WxXmlOutImageMessageTest { | |||||
public void test() { | |||||
WxXmlOutImageMessage m = new WxXmlOutImageMessage(); | |||||
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[image]]></MsgType>" | |||||
+ "<Image><MediaId><![CDATA[ddfefesfsdfef]]></MediaId></Image>" | |||||
+ "</xml>"; | |||||
System.out.println(m.toXml()); | |||||
Assert.assertEquals(m.toXml().replaceAll("\\s", ""), expected.replaceAll("\\s", "")); | |||||
} | |||||
public void testBuild() { | |||||
WxXmlOutImageMessage m = WxXmlOutMessage.IMAGE().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[image]]></MsgType>" | |||||
+ "<Image><MediaId><![CDATA[ddfefesfsdfef]]></MediaId></Image>" | |||||
+ "</xml>"; | |||||
System.out.println(m.toXml()); | |||||
Assert.assertEquals( | |||||
m | |||||
.toXml() | |||||
.replaceAll("\\s", "") | |||||
.replaceAll("<CreateTime>.*?</CreateTime>", ""), | |||||
expected | |||||
.replaceAll("\\s", "") | |||||
.replaceAll("<CreateTime>.*?</CreateTime>", "") | |||||
); | |||||
} | |||||
} |
@@ -0,0 +1,76 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMessage; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMusicMessage; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.Test; | |||||
@Test | |||||
public class WxXmlOutMusicMessageTest { | |||||
public void test() { | |||||
WxXmlOutMusicMessage m = new WxXmlOutMusicMessage(); | |||||
m.setTitle("title"); | |||||
m.setDescription("ddfff"); | |||||
m.setHqMusicUrl("hQMusicUrl"); | |||||
m.setMusicUrl("musicUrl"); | |||||
m.setThumbMediaId("thumbMediaId"); | |||||
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[music]]></MsgType>" | |||||
+ "<Music>" | |||||
+ " <Title><![CDATA[title]]></Title>" | |||||
+ " <Description><![CDATA[ddfff]]></Description>" | |||||
+ " <ThumbMediaId><![CDATA[thumbMediaId]]></ThumbMediaId>" | |||||
+ " <MusicUrl><![CDATA[musicUrl]]></MusicUrl>" | |||||
+ " <HQMusicUrl><![CDATA[hQMusicUrl]]></HQMusicUrl>" | |||||
+ " </Music>" | |||||
+ "</xml>"; | |||||
System.out.println(m.toXml()); | |||||
Assert.assertEquals(m.toXml().replaceAll("\\s", ""), expected.replaceAll("\\s", "")); | |||||
} | |||||
public void testBuild() { | |||||
WxXmlOutMusicMessage m = WxXmlOutMessage.MUSIC() | |||||
.fromUser("fromUser") | |||||
.toUser("toUser") | |||||
.title("title") | |||||
.description("ddfff") | |||||
.hqMusicUrl("hQMusicUrl") | |||||
.musicUrl("musicUrl") | |||||
.thumbMediaId("thumbMediaId") | |||||
.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[music]]></MsgType>" | |||||
+ "<Music>" | |||||
+ " <Title><![CDATA[title]]></Title>" | |||||
+ " <Description><![CDATA[ddfff]]></Description>" | |||||
+ " <ThumbMediaId><![CDATA[thumbMediaId]]></ThumbMediaId>" | |||||
+ " <MusicUrl><![CDATA[musicUrl]]></MusicUrl>" | |||||
+ " <HQMusicUrl><![CDATA[hQMusicUrl]]></HQMusicUrl>" | |||||
+ " </Music>" | |||||
+ "</xml>"; | |||||
System.out.println(m.toXml()); | |||||
Assert.assertEquals( | |||||
m | |||||
.toXml() | |||||
.replaceAll("\\s", "") | |||||
.replaceAll("<CreateTime>.*?</CreateTime>", ""), | |||||
expected | |||||
.replaceAll("\\s", "") | |||||
.replaceAll("<CreateTime>.*?</CreateTime>", "") | |||||
); | |||||
} | |||||
} |
@@ -0,0 +1,97 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMessage; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMewsMessage; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.Test; | |||||
@Test | |||||
public class WxXmlOutNewsMessageTest { | |||||
public void test() { | |||||
WxXmlOutMewsMessage m = new WxXmlOutMewsMessage(); | |||||
m.setCreateTime(1122l); | |||||
m.setFromUserName("fromUser"); | |||||
m.setToUserName("toUser"); | |||||
WxXmlOutMewsMessage.Item item = new WxXmlOutMewsMessage.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() { | |||||
WxXmlOutMewsMessage.Item item = new WxXmlOutMewsMessage.Item(); | |||||
item.setDescription("description"); | |||||
item.setPicUrl("picUrl"); | |||||
item.setTitle("title"); | |||||
item.setUrl("url"); | |||||
WxXmlOutMewsMessage m = WxXmlOutMessage.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,54 @@ | |||||
package me.chanjar.weixin.enterprise.bean; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutMessage; | |||||
import me.chanjar.weixin.enterprise.bean.WxXmlOutTextMessage; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.Test; | |||||
@Test | |||||
public class WxXmlOutTextMessageTest { | |||||
public void test() { | |||||
WxXmlOutTextMessage m = new WxXmlOutTextMessage(); | |||||
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() { | |||||
WxXmlOutTextMessage m = WxXmlOutMessage.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>", "") | |||||
); | |||||
} | |||||
} |