diff --git a/.gitignore b/.gitignore index ff01410..ef6ce9b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ target/ +.settings/ *.iml *.class *.jar +*.classpath +*.project diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2d64c61 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,16 @@ +sudo: false +language: java +script: mvn clean package -Dmaven.test.skip=true +jdk: + - openjdk6 + - openjdk7 + - oraclejdk7 +# whitelist +branches: + only: + - master + - develop +notifications: + email: false +before_install: + - export TZ=Asia/Shanghai \ No newline at end of file diff --git a/README.md b/README.md index e10b9e9..d964b7e 100644 --- a/README.md +++ b/README.md @@ -1,195 +1,247 @@ -!["MPSDK4J"](http://j2ee.u.qiniudn.com/mpsdk4j-logo.png-aliassmall "MPSDK4J") -## MPSDK4J -### 目录 -* [1.引言](#引言) -* [2.介绍](#介绍) - * [2.1.结构设计](#结构设计) - * [2.2.交互时序](#交互时序) -* [3.项目](#项目) - * [3.1.最新源码](#最新源码) - * [3.2.Maven库引用](#Maven库引用) -* [4.示例代码](#示例代码) - * [4.1.HttpServlet环境](#HttpServlet环境) - * [4.2.Struts2环境](#Struts2环境) - * [4.3SpringMVC环境](#SpringMVC环境) -* [5.Issue](#Issue) -* [6.联系](#联系) -* [7.其它](#其它) - - -#### 1.引言 -双11是一个令不少人狂欢的日子,今天你买了么?或许在那XXX亿的曲线中能找到你的影子哟,呵~,不过这与俺无关了,只是借用这个双11来纪念一下而已。从事微信公众平台开发也有一段时间了,算是积累了不少经验吧,趁这些天空闲的时间,把这些经验重构一遍拿出来分享一下。特此声明本人并非技术大牛(纯粹的技术渣),只是用最简单的流程与编码实现微信公众平台交互SDK,有什么不满意的话,可以尽情的吐槽。目前已经实现微信API所有的功能且同步至官方最新发布(如最新的AES消息加密),后续还会不断的扩展(会做一个多微信号管理平台),欢迎关注加入,谢谢。 - - -#### 2.介绍 -**MPSDK4J**,非常直观的阐述了此项目的意义所在。没错,它就是JAVA语言环境下的微信公众平台开发SDK。其中MP代表的是微信公众平台的域名前缀,SDK表示开发工具包,4同音英文“for”,J代表了JAVA。虽然现网络上已经有不少JAVA版本的SDK现身,但是***[MPSDK4J]*** 的出现也并非只是造轮子的重复工作。它遵循单一设计模式规则,所有的设计与功能都是源于微信公众平台API,一切都是为了追求简单与速度。 - ->**a.设计简单**:整体设计非常的简单,仅有7个包39个类对象(其中VO对象占据一半之多,详见结构设计图),核心功能部分就4类(WxBase,WxApi,WxOpenApi,WxHandler); - ->**b.解析速度**:基本SAX驱动式XML处理,结合JDK7的新特性,能够快速的解析收到用户发送的微信消息,放弃JAVA反射功能直接编码生成VO对象更加快速; - ->**c.敏捷开发**:微信交互信息全都统一封装VO对象,所有VO的属性都是微信公众平台API原生状态。开发者无须再关心它来源是XML还JSON格式,其中消息的收发只需掌握2个VO(ReceiveMsg,OutPutMsg)即可; - ->**d.支持力度**:API功能分为三个部分(后续会不断更新升级),微信基本消息的交互,高级接口(Token,自定义菜单,模板消息,群发消息等等)及开放平台功能接口的调用。 - - -##### 2.1结构设计 -!["MPSDK4J设计图"](http://j2ee.u.qiniudn.com/mpsdk4j-class-design.png-alias "MPSDK4J设计图") - - -##### 2.2交互时序 -!["MPSDK4J交互时序图"](http://j2ee.u.qiniudn.com/mpsdk4j-sequence.png-alias "MPSDK4J交互时序图") - - -#### 3.项目 - - -##### 3.1最新源码 -* OSChina项目主页: -* 开源协议:[Apache Licenses 2.0](http://www.apache.org/licenses/LICENSE-2.0) - - -##### 3.2Maven库引用 -另外你也可以通过OSChina的Maven库获取依赖 - -* 1.加入OSC仓库,也可参考官方说明[OSChina Maven Help](http://maven.oschina.net/help.html) -```xml - - - nexus - http://maven.oschina.net/content/groups/public/ - - true - - - false - - - - nexus-third - http://maven.oschina.net/content/repositories/thirdparty/ - - true - - - true - - - -``` - -* 2.添加依赖坐标 -```xml - - org.elkan1788.osc - mpsdk4j - 1.a.19 - -``` - -或者自己编译jar包。 -``` -mvn clean package -``` - - -#### 4.示例代码 -MPSDK4J在Web环境中暂时提供了以下三种支持,欢迎提交其它环境扩展。在实际的使用过程中只需要继承相应环境的Wx***Support父类,重写init初始化方法修改其中的公众号信息及微信消息处理器,添加环境的入口(Servlet环境无需此步骤),调用wxInteract方法,最后发布上线即可。 - -##### 4.1.HttpServlet环境: -```java -@WebServlet(name = "weixinServlet", urlPatterns = "/weixin/mp/core.ser") -public class WeiXinServlet extends WxServletSupport { - - @Override - public void init() throws ServletException { - super.init(); - MPAct mpAct = new MPAct(); - // 修改为实际的公众号信息,可以在开发者栏目中查看 - mpAct.setAppId("wx****"); - mpAct.setAppSecert("***"); - mpAct.setToken("***"); - mpAct.setAESKey("******"); - this.setMpAct(mpAct); - // 可实现自己的WxHandler - this.setWxHandler(new WxDefaultHandler()); - } -} -``` - -##### 4.2.SpringMVC环境: - -```java -@Controller -@RequestMapping("/weixin/mp") -public class WeiXinController extends WxSpringSupport { - - @Override - protected void init() { - MPAct mpAct = new MPAct(); - // 修改为实际的公众号信息,可以在开发者栏目中查看 - mpAct.setAppId("wx****"); - mpAct.setAppSecert("***"); - mpAct.setToken("***"); - mpAct.setAESKey("******"); - this.setMpAct(mpAct); - // 可实现自己的WxHandler - this.setWxHandler(new WxDefaultHandler()); - } - - @RequestMapping(value = "/core",produces = {"text/plain;charset=UTF-8"}) - @ResponseBody - public String wxCore(HttpServletRequest req) { - String reply = ""; - try { - reply = wxInteract(req); - } catch (IOException e) { - log.error(e.getLocalizedMessage(), e); - } - return reply; - } -} -``` - - -##### 4.3.Struts2环境: -```java -public class WeiXinAction extends WxStruts2Support { - - @Override - protected void init() { - super.init(); - MPAct mpAct = new MPAct(); - // 修改为实际的公众号信息,可以在开发者栏目中查看 - mpAct.setAppId("wx****"); - mpAct.setAppSecert("***"); - mpAct.setToken("***"); - mpAct.setAESKey("******"); - this.setMpAct(mpAct); - // 可实现自己的WxHandler - this.setWxHandler(new WxDefaultHandler()); - } - - public void wxCore() throws IOException { - wxInteract(); - } -} -``` - - -##### 5.Issue -BUG提交: - - -##### 6.联系 -特别希望看到该项目对您哪怕一点点的帮助。你有任何的想法和建议,除以上Issue提交外,也随时欢迎与我沟通,联系方式: - -* Email: elkan1788@gmail.com -* QQ: 2292706174 -* 微信: - - -##### 7.其它 -目前正在尝试微信开放平台探究,已初步实现授权管理功能,后续会不断完善成一个管理平台,期待你的加入。 - -!["MPSDK4J公众号开发服务"](http://j2ee.u.qiniudn.com/weixn-open-demo.png-alias "MPSDK4J公众号开发服务") \ No newline at end of file +# MPSDK4J v2 +[![Build Status](https://travis-ci.org/elkan1788/mpsdk4j.svg?branch=master)](https://travis-ci.org/elkan1788/mpsdk4j) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.elkan1788/mpsdk4j/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.elkan1788/mpsdk4j) +[![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) + +!["MPSDK4J"](http://j2ee.u.qiniudn.com/mpsdk4j-logo.png-aliassmall "MPSDK4J") + +## 目录 +* [1.引言](#引言) +* [2.介绍](#介绍) + * [2.1.结构设计](#结构设计) + * [2.2.交互时序](#时序图) + * [2.3.测试覆盖](#测试覆盖) +* [3.项目](#项目) + * [3.1.最新源码](#最新源码) + * [3.2.Maven库引用](#Maven库引用) +* [4.示例代码](#示例代码) + * [4.1.HttpServlet环境](#HttpServlet环境) + * [4.2.SpringMVC环境](#SpringMVC环境) + * [4.3.Nutz环境](#Nutz环境) + * [4.4.示例代码](#示例) +* [5.Issue](#Issue) +* [6.联系](#联系) +* [7.其它](#其它) + + +## 1.引言 + +"十一"黄金周假期已悄然逝去,_MPSDK4J_在不知不觉中也经历过一个年头啦,非常感谢这段时间里网友们的支持.有了小伙伴们的支持_MPSDK4J_成长更加的迅速,当前版本也确实存在问题诸多的问题.如: 交互中单,多例问题,多公众号混淆问题,微信公众号API更新等等问题.所以趁最近空闲时间把_MPSDK4J_重构,需要注意**此次升级是全新的改造不往下兼容**.新版本的_MPSDK4J_结构上更加清晰,使用同样的简单,依然不忘追求速度与简单的初心. + + +## 2.介绍 + +_MPSDK4J_,非常直观的阐述了此项目的意义所在.没错,它就是JAVA语言环境下的微信公众平台开发SDK.其中MP代表的是微信公众平台的域名前缀,SDK表示开发工具包,4同音英文"for",J代表了JAVA.虽然现网络上已经有不少JAVA版本的SDK现身,但是[_MPSDK4J_]的出现也并非只是造轮子的重复工作.它遵循单一设计模式规则,所有的设计与功能都是源于微信公众平台API,一切都是为了追求简单与速度. + +>**a.设计简单**:整体设计非常的简单,仅有9个大包11个接口,3个枚举,55类,1个配置文件(其中API交互实体对象占据一半之多),核心功能部分就4类(WechatKeneral,MessageHandler,WechatHandler,WechatAPI); + +>**b.解析速度**:基本SAX驱动式XML处理,能够快速的解析收到用户发送的微信消息,放弃JAVA反射功能,采用直接编码生成VO对象,加快解释速度; + +>**c.敏捷开发**:微信交互信息全都统一封装VO对象,所有VO的属性都是微信公众平台API原生状态.但开发者无须再关心它来源是XML还JSON格式,只需掌握其中消息的收发对应VO对象即可; + +>**d.支持力度**:API功能分类与微信公众平台保持一致,分类信息详见下表(后续会不断更新升级),微信基本消息的交互,高级接口(Token,自定义菜单,模板消息,群发消息等等). + +| API名称 | 描述 | +| ------ | ------ | +| CredentialAPI | 微信服务器IP列表,access_token,jssdk_ticket,短链接生成 | +| GroupsAPI | 用户分组接口: 创建,查询,移动用户等 | +| MediaAPI | 多媒体文件接口: 上传多媒体素材(临时/永久),下载,删除 | +| MenuAPI | 自定义菜单接口: 查询,创建,删除 | +| MessageAPI | 高级消息接口: 发送模板消息,客服消息,群发消息 | +| QRCodeAPI | 二维码接口: 创建,获取 | +| UserAPI | 用户管理接口: 用户信息,订阅列表等 | +| WechatAPIImpl | 上述接口统一实现 | + + +### 2.1.整体结构设计 +!["MPSDK4J-V2"](http://j2ee.u.qiniudn.com/mpsdk4j-2.png-alias "MPSDK4J-V2") + + +### 2.2.交互时序图 +!["MPSDK4J-seq"](http://j2ee.u.qiniudn.com/mpsdk4j-v2-seq.png-alias "MPSDK4J-seq") + + +### 2.3.测试覆盖 +!["MPSDK4J-test"](http://j2ee.u.qiniudn.com/mpsdk4j-v2-test-coverage.png-alias "MPSDK4J-test") + + +## 3.项目 + + +### 3.1最新源码 +* OSChina项目主页: +* Github项目主页: +* 开源协议:[Apache Licenses 2.0](http://www.apache.org/licenses/LICENSE-2.0) + + +### 3.2Maven库引用 + +```xml + + + io.github.elkan1788 + mpsdk4j + 2.b.1 + + +``` + +或者自己编译jar包. + +``` + +mvn clean package + +``` + + +## 4.示例代码 + +_MPSDK4J_在`Web`环境中暂时提供了以下环境支持,欢迎提交其它环境扩展.在实际的使用过程中只需要继承相应环境的`WechatWebSupport`父类,重写`init`初始化方法修改其中的公众号信息及微信消息处理器(实现`WechatHandler`接口或是继承`WechatDefHandler`基类),添加环境的入口(`Servlet`环境无需此步骤),调用`interact`方法,最后发布上线即可. + + +### 4.1.HttpServlet环境: + +```java + +@WebServlet(name = "wechatCoreServlet", urlPatterns = "/servlet/wechatcore.ser") +public class WechatCoreServlet extends HttpServletSupport { + + private static final long serialVersionUID = 4370883777170946295L; + + private static final Logger log = LoggerFactory.getLogger(WechatCoreServlet.class); + + private static final ConfigReader _cr = new ConfigReader("/mp.properties"); + + @Override + public void init() throws ServletException { + log.info("====== Servlet环境 ======="); + MPAccount mpact = new MPAccount(); + // 修改为实际的公众号信息,可以在开发者栏目中查看 + mpAct.setAppId("wx****"); + mpAct.setAppSecert("***"); + mpAct.setToken("***"); + mpAct.setAESKey("******"); + _wk.setMpAct(mpact); + _wk.setWechatHandler(new WechatDefHandler()); + } + +} + +``` + + +### 4.2.SpringMVC环境: + +```java + +/** + * SpringMVC环境接入 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@RequestMapping("/springmvc") +@Controller +public class WechatCoreController extends WechatWebSupport { + + private static final Logger log = LoggerFactory.getLogger(WechatCoreController.class); + + private static final ConfigReader _cr = new ConfigReader("/mp.properties"); + + @Override + public void init() { + log.info("====== SpringMVC环境 ======="); + MPAccount mpact = new MPAccount(); + // 修改为实际的公众号信息,可以在开发者栏目中查看 + mpAct.setAppId("wx****"); + mpAct.setAppSecert("***"); + mpAct.setToken("***"); + mpAct.setAESKey("******"); + _wk.setMpAct(mpact); + _wk.setWechatHandler(new WechatDefHandler()); + } + + @RequestMapping("/wechatcore") + public void wechatCore(HttpServletRequest req, HttpServletResponse resp) throws Exception { + this.interact(req, resp); + } + +} + +``` + + +### 4.3.Nutz环境: + +```java + +/** + * Nutz环境接入 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@At("/nutz") +@IocBean +public class WechatCoreModule extends WechatWebSupport { + + private static final Log log = Logs.get(); + + private static final ConfigReader _cr = new ConfigReader("/mp.properties"); + + @Override + public void init() { + log.info("====== Nutz环境 ======="); + // 修改为实际的公众号信息,可以在开发者栏目中查看 + mpAct.setAppId("wx****"); + mpAct.setAppSecert("***"); + mpAct.setToken("***"); + mpAct.setAESKey("******"); + _wk.setMpAct(mpact); + _wk.setWechatHandler(new WechatDefHandler()); + } + + @At("/wechatcore") + public void wechatCore(HttpServletRequest req, HttpServletResponse resp) { + try { + this.interact(req, resp); + } + catch (IOException e) { + throw Lang.wrapThrow(e); + } + } +} + +``` + + +### 4.4.示例项目源码 + +* OSChina仓库: + + +## 5.Issue +BUG提交: + + +## 6.联系 +特别希望看到该项目对您哪怕一点点的帮助。你有任何的想法和建议,除以上Issue提交外,也随时欢迎与我沟通,联系方式: + +* Email: elkan1788@gmail.com +* QQ群:486192816 + +!["MPSDK4J-qq"](http://j2ee.u.qiniudn.com/MPSDK4J-qq.png-noalias "MPSDK4J-qq") + + +## 7.其它: JCE 使用说明 + +| 文件名称 | JDK版本 | +| ------ | ------ | +| jce_policy-6.zip | 1.6 | +| UnlimitedJCEPolicyJDK7.zip | 1.7 | +| jce_policy-8.zip | 1.8 | + +选择相应的`J2EE`版本下载后解压,可以看到`local_policy.jar`和`US_export_policy.jar`以及`readme.txt` +* 如果安装了`JRE`,将两个`jar`文件放到`%JRE_HOME%\lib\security`目录下覆盖原来的文件; +* 如果安装了`JDK`,将两个`jar`文件放到`%JDK_HOME%\jre\lib\security`目录下覆盖原来文件. \ No newline at end of file diff --git a/jce-patch/UnlimitedJCEPolicyJDK7.zip b/jce-patch/UnlimitedJCEPolicyJDK7.zip new file mode 100644 index 0000000..0a6890c Binary files /dev/null and b/jce-patch/UnlimitedJCEPolicyJDK7.zip differ diff --git a/jce-patch/jce_policy-6.zip b/jce-patch/jce_policy-6.zip new file mode 100644 index 0000000..1e59079 Binary files /dev/null and b/jce-patch/jce_policy-6.zip differ diff --git a/jce-patch/jce_policy-8.zip b/jce-patch/jce_policy-8.zip new file mode 100644 index 0000000..63cd0cd Binary files /dev/null and b/jce-patch/jce_policy-8.zip differ diff --git a/pom.xml b/pom.xml index e3e62b9..9bb25a9 100644 --- a/pom.xml +++ b/pom.xml @@ -1,183 +1,216 @@ - - 4.0.0 + + 4.0.0 - org.elkan1788.osc - mpsdk4j - 1.a.24-SNAPSHOTS - jar - 微信公众平台JAVA开发SDK - - 腾讯微信公众平台开发者服务,Java平台开发下的SDK包 - + io.github.elkan1788 + mpsdk4j + 2.b.1 + jar - - - 0 - 凡梦星尘 - elkan1788@gmail.com - China - - - Author - - - - + mpsdk4j + + A simple SDK for wechat api develop in Java program. + + + http://elkan1788.github.io/mpskd4j + + Github Issue + https://github.com/elkan1788/mpsdk4j/issues + + + + The Apache Software License, Version 2.0 + http://apache.org/licenses/LICENSE-2.0.txt + + - - UTF-8 - + + + 1 + 凡梦星尘 + elkan1788@gmail.com + CHINA + + + Author + + + Committer + + + + + 2 + 黑色幽默 + 1315055798@qq.com + CHINA + + + Committer + + + + + 3 + 高轩雾褪 + 977903096@qq.com + CHINA + + + Committer + + + + - - - - junit - junit - 4.11 - test - + + UTF-8 + - - - log4j - log4j - 1.2.17 - - - org.slf4j - slf4j-api - 1.7.7 - - - org.slf4j - log4j-over-slf4j - 1.7.7 - - - org.slf4j - slf4j-log4j12 - 1.7.7 - + - - - commons-codec - commons-codec - 1.9 - - - commons-logging - commons-logging - 1.1.3 - - - commons-collections - commons-collections - 3.2.1 - - - commons-beanutils - commons-beanutils - 1.9.2 - + + + junit + junit + 4.12 + test + - - - org.apache.httpcomponents - httpcore - 4.3.3 - - - org.apache.httpcomponents - httpclient - 4.3.5 - - - org.apache.httpcomponents - fluent-hc - 4.3.6 - - - org.apache.httpcomponents - httpmime - 4.3.5 - + + + org.jmockit + jmockit + 1.19 + test + - - - com.alibaba - fastjson - 1.2.0 - + + + log4j + log4j + 1.2.17 + test + + + org.slf4j + slf4j-api + 1.7.7 + test + + + org.slf4j + log4j-over-slf4j + 1.7.7 + test + + + org.slf4j + slf4j-log4j12 + 1.7.7 + test + - - - javax.servlet - servlet-api - 2.5 - provided - + + + org.nutz + nutz + 1.b.52 + - - org.apache.struts - struts2-core - 2.3.20 - compile - + + + javax.servlet + servlet-api + 2.5 + provided + true + - + - - mpsdk4j-${project.version} - - - org.apache.maven.plugins - maven-compiler-plugin - 3.1 - - 1.7 - 1.7 - ${project.build.sourceEncoding} - - - - org.apache.maven.plugins - maven-resources-plugin - 2.7 - - ${project.build.sourceEncoding} - - - - org.apache.maven.plugins - maven-jar-plugin - 2.5 - - - *.properties - - - - - org.apache.maven.plugins - maven-source-plugin - 2.4 - - true - - - - compile - - jar - - - - - - + + mpsdk4j-${project.version} + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + 1.6 + 1.6 + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.10.3 + + ${project.build.sourceEncoding} + ${project.build.sourceEncoding} + ${project.build.sourceEncoding} + + + + package + + jar + + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + ${project.build.sourceEncoding} + + + + org.apache.maven.plugins + maven-jar-plugin + 2.4 + + + org.apache.maven.plugins + maven-source-plugin + 2.4 + + true + + + + compile + + jar + + + + + + org.jacoco + jacoco-maven-plugin + 0.7.5.201505241946 + + + prepare-unit-tests + + prepare-agent + + + + report + test + + report + + + + + + \ No newline at end of file diff --git a/src/main/java/com/qq/weixin/mp/aes/SHA1.java b/src/main/java/com/qq/weixin/mp/aes/SHA1.java deleted file mode 100644 index b051969..0000000 --- a/src/main/java/com/qq/weixin/mp/aes/SHA1.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.qq.weixin.mp.aes; - -import java.security.MessageDigest; -import java.util.Arrays; -import java.util.Formatter; - -/** - * SHA1算法计算公众平台的消息签名接口 - * - * @author Tencent - * @since 2014/11/4 - */ -public class SHA1 { - - /** - * 用SHA1算法生成安全签名 - * - * @param params [token, timestamp, nonce, encrypt] - * @return 安全签名 - * @throws com.qq.weixin.mp.aes.AesException - */ - public static String calculate(String... params) throws AesException { - try { - String[] array = params; - StringBuffer sb = new StringBuffer(); - // 字符串排序 - Arrays.sort(array); - int len = params.length; - for (int i = 0; i < len; i++) { - sb.append(array[i]); - } - - // SHA1签名生成 - MessageDigest md = MessageDigest.getInstance("SHA-1"); - md.reset(); - md.update(new String(sb).getBytes("UTF-8")); - - // HEX输出 - byte[] hash = md.digest(); - Formatter formatter = new Formatter(); - for (byte b : hash) { - formatter.format("%02x", b); - } - String hex = formatter.toString(); - formatter.close(); - return hex; - } catch (Exception e) { - throw new AesException(AesException.ComputeSignatureError); - } - } -} diff --git a/src/main/java/com/qq/weixin/mp/aes/WXBizMsgCrypt.java b/src/main/java/com/qq/weixin/mp/aes/WXBizMsgCrypt.java deleted file mode 100644 index 4f9b28e..0000000 --- a/src/main/java/com/qq/weixin/mp/aes/WXBizMsgCrypt.java +++ /dev/null @@ -1,284 +0,0 @@ -package com.qq.weixin.mp.aes; - -import org.apache.commons.codec.binary.Base64; -import org.elkan1788.osc.weixin.mp.util.XmlMsgBuilder; - -import javax.crypto.Cipher; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; -import java.nio.charset.Charset; -import java.util.Arrays; -import java.util.Random; - -/** - * 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串). - *
    - *
  1. 第三方回复加密消息给公众平台
  2. - *
  3. 第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。
  4. - *
- * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 - *
    - *
  1. 在官方网站下载JCE无限制权限策略文件(JDK7的下载地址: - * http://www.oracle.com/technetwork/java/javase/downloads/jce-7-download-432124.html
  2. - *
  3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
  4. - *
  5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
  6. - *
  7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
  8. - *
- */ -public class WXBizMsgCrypt { - - private static Charset CHARSET = Charset.forName("utf-8"); - private Base64 base64 = new Base64(); - private byte[] aesKey; - private String token; - private String appId; - private String fromAppId; - - /** - * 构造函数 - * @param token 公众平台上,开发者设置的token - * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey - * @param appId 公众平台appid - * - * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 - */ - public WXBizMsgCrypt(String token, String encodingAesKey, String appId) throws AesException { - if (encodingAesKey.length() != 43) { - throw new AesException(AesException.IllegalAesKey); - } - - this.token = token; - this.appId = appId; - aesKey = Base64.decodeBase64(encodingAesKey + "="); - } - - // 生成4个字节的网络字节序 - private byte[] getNetworkBytesOrder(int sourceNumber) { - byte[] orderBytes = new byte[4]; - orderBytes[3] = (byte) (sourceNumber & 0xFF); - orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF); - orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF); - orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF); - return orderBytes; - } - - // 还原4个字节的网络字节序 - private int recoverNetworkBytesOrder(byte[] orderBytes) { - int sourceNumber = 0; - for (int i = 0; i < 4; i++) { - sourceNumber <<= 8; - sourceNumber |= orderBytes[i] & 0xff; - } - return sourceNumber; - } - - // 随机生成16位字符串 - private String getRandomStr() { - StringBuffer sb = new StringBuffer(); - Random ran = new Random(); - for(int i=0; i<16; i++) { - boolean flag = ran.nextInt(2) % 2 == 0; - if(flag) { - char c = (char) (int) (Math.random() * 26 + 97); - sb.append(c); - } else { - sb.append(ran.nextInt(10)); - } - } - return sb.toString(); - } - - /** - * 对明文进行加密. - * - * @param text 需要加密的明文 - * @return 加密后base64编码的字符串 - * @throws AesException aes加密失败 - */ - private String encrypt(String randomStr, String text) throws AesException { - ByteGroup byteCollector = new ByteGroup(); - byte[] randomStrBytes = randomStr.getBytes(CHARSET); - byte[] textBytes = text.getBytes(CHARSET); - byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length); - byte[] appidBytes = appId.getBytes(CHARSET); - - // randomStr + networkBytesOrder + text + appid - byteCollector.addBytes(randomStrBytes); - byteCollector.addBytes(networkBytesOrder); - byteCollector.addBytes(textBytes); - 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 AesException(AesException.EncryptAESError); - } - } - - /** - * 对密文进行解密. - * - * @param text 需要解密的密文 - * @return 解密得到的明文 - * @throws AesException aes解密失败 - */ - private String decrypt(String text) throws AesException { - 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(text); - - // 解密 - original = cipher.doFinal(encrypted); - } catch (Exception e) { - throw new AesException(AesException.DecryptAESError); - } - - String xmlContent; - try { - // 去除补位字符 - byte[] bytes = PKCS7Encoder.decode(original); - - // 分离16位随机字符串,网络字节序和AppId - byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); - - int xmlLength = recoverNetworkBytesOrder(networkOrder); - - xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); - fromAppId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), - CHARSET); - } catch (Exception e) { - throw new AesException(AesException.IllegalBuffer); - } - - // appid不相同的情况 - if (!fromAppId.equals(appId)) { - throw new AesException(AesException.ValidateAppidError); - } - return xmlContent; - - } - - /** - * 将公众平台回复用户的消息加密打包. - *
    - *
  1. 对要发送的消息进行AES-CBC加密
  2. - *
  3. 生成安全签名
  4. - *
  5. 将消息密文和安全签名打包成xml格式
  6. - *
- * - * @param replyMsg 公众平台待回复用户的消息,xml格式的字符串 - * @param timeStamp 时间戳,可以自己生成,也可以用URL参数的timestamp - * @param nonce 随机串,可以自己生成,也可以用URL参数的nonce - * - * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串 - * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 - */ - public String encryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException { - // 加密 - String encrypt = encrypt(getRandomStr(), replyMsg); - - // 生成安全签名 - if ("".equals(timeStamp)) { - timeStamp = Long.toString(System.currentTimeMillis()); - } - - String signature = SHA1.calculate(token, timeStamp, nonce, encrypt); - - // 生成发送的xml - String result = XmlMsgBuilder.create().encrypt(encrypt, signature, timeStamp, nonce); - return result; - } - - /** - * 检验消息的真实性,并且获取解密后的明文. - *
    - *
  1. 利用收到的密文生成安全签名,进行签名验证
  2. - *
  3. 若验证通过,则提取xml中的加密消息
  4. - *
  5. 对消息进行解密
  6. - *
- * - * @param msgSignature 签名串,对应URL参数的msg_signature - * @param timeStamp 时间戳,对应URL参数的timestamp - * @param nonce 随机串,对应URL参数的nonce - * @param postData 密文,对应POST请求的数据 - * - * @return 解密后的原文 - * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 - */ - public String decryptMsg(String msgSignature, String timeStamp, String nonce, String postData) - throws AesException { - - // 密钥,公众账号的app secret - // 提取密文 - Object[] encrypt = XMLParse.extract(postData); - - // 验证安全签名 - String signature = SHA1.calculate(token, timeStamp, nonce, encrypt[1].toString()); - - // 和URL中的签名比较是否相等 - if (!signature.equals(msgSignature)) { - throw new AesException(AesException.ValidateSignatureError); - } - - // 解密 - String result = decrypt(encrypt[1].toString()); - return result; - } - - /** - * 验证URL - * @param msgSignature 签名串,对应URL参数的msg_signature - * @param timeStamp 时间戳,对应URL参数的timestamp - * @param nonce 随机串,对应URL参数的nonce - * @param echoStr 随机串,对应URL参数的echostr - * - * @return 解密之后的echostr - * @throws AesException 执行失败,请查看该异常的错误码和具体的错误信息 - */ - public String verifyUrl(String msgSignature, String timeStamp, String nonce, String echoStr) - throws AesException { - String signature = SHA1.calculate(token, timeStamp, nonce, echoStr); - - if (!signature.equals(msgSignature)) { - throw new AesException(AesException.ValidateSignatureError); - } - - String result = decrypt(echoStr); - return result; - } - - /** - * 获取加密消息中的APPID[为订阅号提供] - * - * @return APPID - */ - public String getFromAppid() { - return this.fromAppId; - } -} \ No newline at end of file diff --git a/src/main/java/com/qq/weixin/mp/aes/package-info.java b/src/main/java/com/qq/weixin/mp/aes/package-info.java deleted file mode 100644 index daaec1d..0000000 --- a/src/main/java/com/qq/weixin/mp/aes/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 微信消息AES加密工具 - */ -package com.qq.weixin.mp.aes; \ No newline at end of file diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/MPSDK4J.java b/src/main/java/io/github/elkan1788/mpsdk4j/Mpsdk4j.java similarity index 51% rename from src/main/java/org/elkan1788/osc/weixin/mp/MPSDK4J.java rename to src/main/java/io/github/elkan1788/mpsdk4j/Mpsdk4j.java index e7ea7e3..d62a87f 100644 --- a/src/main/java/org/elkan1788/osc/weixin/mp/MPSDK4J.java +++ b/src/main/java/io/github/elkan1788/mpsdk4j/Mpsdk4j.java @@ -1,12 +1,15 @@ -package org.elkan1788.osc.weixin.mp; +package io.github.elkan1788.mpsdk4j; /** - * 微信公众平台JAVA SDK版本号声明 - * + * 项目版本号声明 + * + *
+ * (参考:http://nutzam.com/core/committer/version_naming.html)
+ * 
  * @author 凡梦星尘(elkan1788@gmail.com)
- * @since 2014/11/6
+ * @since 2.0
  */
-public class MPSDK4J {
+public final class Mpsdk4j {
 
     /**
      * 获取 mpsdk4j 的版本号,版本号的命名规范
@@ -21,16 +24,16 @@ public static String version() {
         return String.format("%d.%s.%d", majorVersion(), releaseLevel(), minorVersion());
     }
 
-    public static int majorVersion() {
-        return 1;
+    private static int majorVersion() {
+        return 2;
     }
 
-    public static int minorVersion() {
-        return 24;
+    private static int minorVersion() {
+        return 1;
     }
 
-    public static String releaseLevel() {
-        //a: 内部测试品质, b: 公测品质, r: 最终发布版
-        return "a";
+    private static String releaseLevel() {
+        // a: 内部测试品质, b: 公测品质, r: 最终发布版
+        return "b";
     }
 }
diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/CredentialAPI.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/CredentialAPI.java
new file mode 100644
index 0000000..fc6a9b9
--- /dev/null
+++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/CredentialAPI.java
@@ -0,0 +1,61 @@
+package io.github.elkan1788.mpsdk4j.api;
+
+import java.util.List;
+
+/**
+ * 微信凭据授权接口
+ * 
+ * @author 凡梦星尘(elkan1788@gmail.com)
+ * @since 2.0
+ */
+public interface CredentialAPI {
+
+    /**
+     * 获取access_token地址
+     */
+    static String get_at = "/token?grant_type=client_credential&appid=%s&secret=%s";
+    /**
+     * 获取微信服务器IP地址
+     */
+    static String cb_ips = "/getcallbackip?access_token=";
+
+    /**
+     * 长链接转短链接地址
+     */
+    static String short_url = "/shorturl?access_token=";
+
+    /**
+     * JSSDK临时凭证地址
+     */
+    static String js_ticket = "/ticket/getticket?type=jsapi&access_token=";
+
+    /**
+     * 获取微信服务凭证
+     * 
+     * @return 凭证
+     */
+    String getAccessToken();
+
+    /**
+     * 获取微信服务器IP地址
+     * 
+     * @return IP地址
+     */
+    List getServerIps();
+
+    /**
+     * 长链接转短链接
+     * 
+     * @param longUrl
+     *            需要转换的长链接,支持http://,https://,weixin://wxpay 格式的url
+     * @return 短链接
+     */
+    String getShortUrl(String longUrl);
+
+    /**
+     * 获取JSSDK凭证
+     * 
+     * @return 凭证
+     */
+    String getJSTicket();
+}
diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/GroupsAPI.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/GroupsAPI.java
new file mode 100644
index 0000000..43d69c1
--- /dev/null
+++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/GroupsAPI.java
@@ -0,0 +1,120 @@
+package io.github.elkan1788.mpsdk4j.api;
+
+import java.util.Collection;
+import java.util.List;
+
+import io.github.elkan1788.mpsdk4j.vo.api.Groups;
+
+/**
+ * 微信用户组管理接口
+ * 
+ * @author 凡梦星尘(elkan1788@gmail.com)
+ * @since 2.0
+ */
+public interface GroupsAPI {
+
+    /**
+     * 创建分组地址
+     */
+    static String create_groups = "/groups/create?access_token=";
+
+    /**
+     * 查询所有分组地址
+     */
+    static String get_groups = "/groups/get?access_token=";
+
+    /**
+     * 查询用户所在分组地址
+     */
+    static String get_member_group = "/groups/getid?access_token=";
+
+    /**
+     * 修改分组名称地址
+     */
+    static String update_group = "/groups/update?access_token=";
+
+    /**
+     * 移动用户分组地址
+     */
+    static String update_member_group = "/groups/members/update?access_token=";
+
+    /**
+     * 批量移动用户分组地址
+     */
+    static String update_members_group = "/groups/members/batchupdate?access_token=";
+
+    /**
+     * 删除分组地址
+     */
+    static String delete_groups = "/groups/delete?access_token=";
+
+    /**
+     * 创建用户分组
+     * 
+     * 
+     * 一个公众账号,最多支持创建100个分组
+     * 
+     * @param name
+     *            分组名字(30个字符以内)
+     * @return 分组Id
+     */
+    int createGroup(String name);
+
+    /**
+     * 查询所有分组
+     * 
+     * @return 分组集合{@link Groups}
+     */
+    List getGroups();
+
+    /**
+     * 查询用户所在分组
+     * 
+     * @param openId
+     *            用户openId
+     * @return 分组Id
+     */
+    int getGroup(String openId);
+
+    /**
+     * 修改分组名称
+     * 
+     * @param id
+     *            分组Id
+     * @param name
+     *            新的分组名称
+     * @return true 或 false
+     */
+    boolean renGroups(int id, String name);
+
+    /**
+     * 移动用户所在分组
+     * 
+     * @param openId
+     *            用户openId
+     * @param groupId
+     *            分组Id
+     * @return true 或 false
+     */
+    boolean move2Group(String openId, int groupId);
+
+    /**
+     * 批量移动用户分组
+     * 
+     * @param openIds
+     *            用户openId集合
+     * @param groupId
+     *            分组Id
+     * @return true 或 false
+     */
+    boolean batchMove2Group(Collection openIds, int groupId);
+
+    /**
+     * 删除分组
+     * 
+     * @param id
+     *            分组Id
+     * @return true 或 false
+     */
+    boolean delGroup(int id);
+}
diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/MediaAPI.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/MediaAPI.java
new file mode 100644
index 0000000..0bd305b
--- /dev/null
+++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/MediaAPI.java
@@ -0,0 +1,54 @@
+package io.github.elkan1788.mpsdk4j.api;
+
+import java.io.File;
+
+import io.github.elkan1788.mpsdk4j.vo.api.Media;
+
+/**
+ * 微信多媒体数据接口
+ * 
+ * @author 凡梦星尘(elkan1788@gmail.com)
+ * @since 2.0
+ */
+public interface MediaAPI {
+
+    // 上传多媒体
+    static String upload_media = "/media/upload?access_token=%s&type=%s";
+
+    // 下载多媒体
+    static String get_media = "/media/get?access_token=%s&media_id=%s";
+
+    /**
+     * 上传多媒体文件
+     * 
+     * 
+     * 上传的临时多媒体文件有格式和大小限制,如下:
+     * 
  • + * 图片(image): 1M,支持JPG格式 + *
  • + * 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 + *
  • + * 视频(video):10MB,支持MP4格式 + *
  • + * 缩略图(thumb):64KB,支持JPG格式 + * + *
    +     * 媒体文件在后台保存时间为3天,即3天后media_id失效。
    +     * 
    +     * @param type
    +     *            多媒体类型 {@link io.github.elkan1788.mpsdk4j.common.MediaType}
    +     * @param media
    +     *            多媒体文件
    +     * @return 实体{@link Media}
    +     */
    +    Media upMedia(String type, File media);
    +
    +    /**
    +     * 下载多媒体文件
    +     * 
    +     * @param mediaId
    +     *            媒体文件ID
    +     * @return {@link File}
    +     */
    +    File dlMedia(String mediaId);
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/MenuAPI.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/MenuAPI.java
    new file mode 100644
    index 0000000..4a27c17
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/MenuAPI.java
    @@ -0,0 +1,44 @@
    +package io.github.elkan1788.mpsdk4j.api;
    +
    +import java.util.List;
    +
    +import io.github.elkan1788.mpsdk4j.vo.api.Menu;
    +
    +/**
    + * 微信自定义菜单接口
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public interface MenuAPI {
    +
    +    // 菜单查询地址
    +    static String query_menu = "/menu/get?access_token=";
    +    // 菜单创建地址
    +    static String create_menu = "/menu/create?access_token=";
    +    // 菜单删除地址
    +    static String del_menu = "/menu/delete?access_token=";
    +
    +    /**
    +     * 查询当前自定菜单
    +     * 
    +     * @return 菜单项{@link io.github.elkan1788.mpsdk4j.vo.api.Menu}
    +     */
    +    List getMenu();
    +
    +    /**
    +     * 创建自定义菜单
    +     * 
    +     * @param menu
    +     *            菜单项
    +     * @return true 或 false
    +     */
    +    boolean createMenu(Menu... menu);
    +
    +    /**
    +     * 删除自定义菜单
    +     * 
    +     * @return true 或 false
    +     */
    +    boolean delMenu();
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/MessageAPI.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/MessageAPI.java
    new file mode 100644
    index 0000000..188b5f4
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/MessageAPI.java
    @@ -0,0 +1,36 @@
    +/**
    + * @author senhui.li
    + */
    +package io.github.elkan1788.mpsdk4j.api;
    +
    +import io.github.elkan1788.mpsdk4j.vo.api.Template;
    +
    +/**
    + * 微信高级消息接口
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public interface MessageAPI {
    +    /**
    +     * 发模板消息地址
    +     */
    +    static String send_template = "/message/template/send?access_token=";
    +
    +    /**
    +     * 发送模板消息
    +     * 
    +     * @param openId
    +     *            接收用户Id
    +     * @param tmlId
    +     *            模板Id
    +     * @param topColor
    +     *            顶部颜色
    +     * @param url
    +     *            跳转链接
    +     * @param tmls
    +     *            模板数据
    +     * @return 消息Id
    +     */
    +    long sendTemplateMsg(String openId, String tmlId, String topColor, String url, Template... tmls);
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/QRCodeAPI.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/QRCodeAPI.java
    new file mode 100644
    index 0000000..a70d044
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/QRCodeAPI.java
    @@ -0,0 +1,49 @@
    +package io.github.elkan1788.mpsdk4j.api;
    +
    +import io.github.elkan1788.mpsdk4j.vo.api.QRTicket;
    +
    +import java.io.File;
    +
    +/**
    + * 微信二维码接口
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public interface QRCodeAPI {
    +
    +    /**
    +     * 创建二维码ticket地址
    +     */
    +    static String create_qrcode = "/qrcode/create?access_token=";
    +
    +    /**
    +     * 下载二维码地址
    +     */
    +    static String show_qrcode = "/showqrcode?ticket=";
    +
    +    /**
    +     * 创建二维码ticket
    +     * 
    +     * 
    +     * 参考[https://github.com/nutzam/nutzwx/blob/master/src/main/java/org/nutz/weixin/impl/
    +     * WxApi2Impl.
    +     * java]
    +     * 
    +     * @param sceneId
    +     *            场景Id(大于0 表示 临时码,Number 表示永远二维码, String 表示永远字符串)
    +     * @param expireSeconds
    +     *            二维码有效时间,以秒为单位。 最大不超过604800(即7天)
    +     * @return
    +     */
    +    QRTicket createQR(Object sceneId, int expireSeconds);
    +
    +    /**
    +     * 通过ticket换取二维码
    +     * 
    +     * @param ticket
    +     *            二维码生成时的Ticket
    +     * @return 二维码图片
    +     */
    +    File getQR(String ticket);
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/TemplateAPI.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/TemplateAPI.java
    new file mode 100644
    index 0000000..c522cec
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/TemplateAPI.java
    @@ -0,0 +1,40 @@
    +package io.github.elkan1788.mpsdk4j.api;
    +
    +/**
    + * 模板消息接口
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public interface TemplateAPI {
    +
    +    /**
    +     * 设置所属行业地址
    +     */
    +    static String set_industry = "/template/api_set_industry?access_token=";
    +
    +    /**
    +     * 获得模板ID
    +     */
    +    static String add_template = "/template/api_add_template?access_token=";
    +
    +    /**
    +     * 设置所属行业
    +     * 
    +     * @param id1
    +     *            模板消息所属行业编号
    +     * @param id2
    +     *            模板消息所属行业编号
    +     * @return true 或 false
    +     */
    +    boolean setIndustry(int id1, int id2);
    +
    +    /**
    +     * 获得模板ID
    +     * 
    +     * @param tmlShortId
    +     *            模板库中模板的编号,有"TM**"和"OPENTMTM**"等形式
    +     * @return 模板Id
    +     */
    +    String getTemplateId(String tmlShortId);
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/UserAPI.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/UserAPI.java
    new file mode 100644
    index 0000000..17b0b72
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/UserAPI.java
    @@ -0,0 +1,77 @@
    +package io.github.elkan1788.mpsdk4j.api;
    +
    +import io.github.elkan1788.mpsdk4j.vo.api.FollowList;
    +import io.github.elkan1788.mpsdk4j.vo.api.Follower;
    +import io.github.elkan1788.mpsdk4j.vo.api.Follower2;
    +
    +import java.util.Collection;
    +import java.util.List;
    +
    +/**
    + * 微信用户信息接口
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public interface UserAPI {
    +
    +    /**
    +     * 设置备注名地址
    +     */
    +    static String user_remark = "/user/info/updateremark?access_token=";
    +
    +    /**
    +     * 用户列表地址
    +     */
    +    static String user_list = "/user/get?access_token=%s&next_openid=%s";
    +
    +    /**
    +     * 用户基本信息地址
    +     */
    +    static String user_info = "/user/info?access_token=%s&openid=%s&lang=%s";
    +
    +    /**
    +     * 批量用户基本信息地址
    +     */
    +    static String batch_user_info = "/user/info/batchget?access_token=";
    +
    +    /**
    +     * 设置用户备注名
    +     * 
    +     * @param openId
    +     *            用户标识
    +     * @param remark
    +     *            新的备注名,长度必须小于30字符
    +     * @return true 或 false
    +     */
    +    boolean updateRemark(String openId, String remark);
    +
    +    /**
    +     * 获取关注用户列表
    +     * 
    +     * @param nextOpenId
    +     *            第一个拉取的OPENID,不填默认从头开始拉取
    +     * @return 关注列表{@link FollowList}
    +     */
    +    FollowList getFollowerList(String nextOpenId);
    +
    +    /**
    +     * 获取用户基本信息(包括UnionID机制)
    +     * 
    +     * @param openId
    +     *            用户的标识
    +     * @param lang
    +     *            国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语
    +     * @return 关注用户{@link Follower}
    +     */
    +    Follower getFollower(String openId, String lang);
    +
    +    /**
    +     * 批量获取用户基本信息[最多拉取100条]
    +     * 
    +     * @param users
    +     *            批量用户集合{@link Follower2}
    +     * @return 关注用户{@link Follower}
    +     */
    +    List getFollowers(Collection users);
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/WechatAPI.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/WechatAPI.java
    new file mode 100644
    index 0000000..3fea2da
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/WechatAPI.java
    @@ -0,0 +1,14 @@
    +package io.github.elkan1788.mpsdk4j.api;
    +
    +/**
    + * 微信公众平台所有API汇集
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public interface WechatAPI extends CredentialAPI, MenuAPI, MediaAPI, GroupsAPI, QRCodeAPI, UserAPI,
    +        TemplateAPI, MessageAPI {
    +
    +    // 微信公众平台API入口
    +    static final String wechatapi = "https://api.weixin.qq.com/cgi-bin";
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/WechatAPIImpl.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/WechatAPIImpl.java
    new file mode 100644
    index 0000000..a2c96fe
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/WechatAPIImpl.java
    @@ -0,0 +1,621 @@
    +package io.github.elkan1788.mpsdk4j.api;
    +
    +import io.github.elkan1788.mpsdk4j.core.JsonMsgBuilder;
    +import io.github.elkan1788.mpsdk4j.exception.WechatApiException;
    +import io.github.elkan1788.mpsdk4j.session.AccessTokenMemoryCache;
    +import io.github.elkan1788.mpsdk4j.session.JSTicketMemoryCache;
    +import io.github.elkan1788.mpsdk4j.session.MemoryCache;
    +import io.github.elkan1788.mpsdk4j.util.HttpTool;
    +import io.github.elkan1788.mpsdk4j.vo.ApiResult;
    +import io.github.elkan1788.mpsdk4j.vo.MPAccount;
    +import io.github.elkan1788.mpsdk4j.vo.api.AccessToken;
    +import io.github.elkan1788.mpsdk4j.vo.api.FollowList;
    +import io.github.elkan1788.mpsdk4j.vo.api.Follower;
    +import io.github.elkan1788.mpsdk4j.vo.api.Follower2;
    +import io.github.elkan1788.mpsdk4j.vo.api.Groups;
    +import io.github.elkan1788.mpsdk4j.vo.api.JSTicket;
    +import io.github.elkan1788.mpsdk4j.vo.api.Media;
    +import io.github.elkan1788.mpsdk4j.vo.api.Menu;
    +import io.github.elkan1788.mpsdk4j.vo.api.QRTicket;
    +import io.github.elkan1788.mpsdk4j.vo.api.Template;
    +
    +import java.io.File;
    +import java.util.Collection;
    +import java.util.HashMap;
    +import java.util.List;
    +import java.util.Map;
    +
    +import org.nutz.castor.Castors;
    +import org.nutz.json.Json;
    +import org.nutz.json.JsonFormat;
    +import org.nutz.lang.Lang;
    +import org.nutz.lang.Strings;
    +import org.nutz.lang.util.NutMap;
    +import org.nutz.log.Log;
    +import org.nutz.log.Logs;
    +
    +/**
    + * 微信公众平台所有接口实现
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +@SuppressWarnings("unchecked")
    +public class WechatAPIImpl implements WechatAPI {
    +
    +    private static final Log log = Logs.get();
    +
    +    static int RETRY_COUNT = 3;
    +
    +    static MemoryCache _atmc;
    +
    +    static MemoryCache _jstmc;
    +
    +    private MPAccount mpAct;
    +
    +    public WechatAPIImpl(MPAccount mpAct) {
    +        this.mpAct = mpAct;
    +        synchronized (this) {
    +            if (_atmc == null) {
    +                _atmc = new AccessTokenMemoryCache();
    +            }
    +            if (_jstmc == null) {
    +                _jstmc = new JSTicketMemoryCache();
    +            }
    +        }
    +    }
    +
    +    /**
    +     * WechatAPI 实现方法
    +     * 
    +     * @param mpAct
    +     *            微信公众号信息{@link MPAccount}
    +     * @return 对应的API
    +     */
    +    public static WechatAPI create(MPAccount mpAct) {
    +        return new WechatAPIImpl(mpAct);
    +    }
    +
    +    private String mergeUrl(String url, Object... values) {
    +        if (!Lang.isEmpty(values)) {
    +            return wechatapi + String.format(url, values);
    +        }
    +        return wechatapi + url;
    +    }
    +
    +    /**
    +     * 强制刷新微信服务凭证
    +     */
    +    private synchronized void refreshAccessToken() {
    +        String url = mergeUrl(get_at, mpAct.getAppId(), mpAct.getAppSecret());
    +        AccessToken at = null;
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.get(url));
    +            if (ar.isSuccess()) {
    +                at = Json.fromJson(AccessToken.class, ar.getJson());
    +                _atmc.set(mpAct.getMpId(), at);
    +            }
    +
    +            if (at != null && at.isAvailable()) {
    +                return;
    +            }
    +
    +            log.errorf("Get mp[%s]access_token failed. There try %d items.", mpAct.getMpId(), i + 1);
    +
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    private synchronized void refreshJSTicket() {
    +        String url = mergeUrl(js_ticket + getAccessToken());
    +        JSTicket jst = null;
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.get(url));
    +            if (ar.isSuccess()) {
    +                jst = Json.fromJson(JSTicket.class, ar.getJson());
    +                _jstmc.set(mpAct.getMpId(), jst);
    +            }
    +
    +            if (jst != null && jst.isAvailable()) {
    +                return;
    +            }
    +
    +            log.errorf("Get mp[%s] JSSDK ticket failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       i + 1);
    +
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public String getAccessToken() {
    +        AccessToken at = _atmc.get(mpAct.getMpId());
    +        if (at == null || !at.isAvailable()) {
    +            refreshAccessToken();
    +            at = _atmc.get(mpAct.getMpId());
    +        }
    +        return at.getAccessToken();
    +    }
    +
    +    @Override
    +    public List getServerIps() {
    +        String url = mergeUrl(cb_ips + getAccessToken());
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.get(url));
    +            if (ar.isSuccess()) {
    +                return Json.fromJsonAsList(String.class, Json.toJson(ar.get("ip_list")));
    +            }
    +
    +            log.errorf("Get mp[%s] server ips failed. There try %d items.", mpAct.getMpId(), i + 1);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public String getShortUrl(String longUrl) {
    +        String url = mergeUrl(short_url + getAccessToken());
    +        String data = "{\"action\":\"long2short\",\"long_url\":\"" + longUrl + "\"}";
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                return String.valueOf(ar.get("short_url"));
    +            }
    +
    +            log.errorf("Create mp[%p] short url failed. There try %d items.", mpAct.getMpId(), i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public String getJSTicket() {
    +        JSTicket jst = _jstmc.get(mpAct.getMpId());
    +        if (jst == null || !jst.isAvailable()) {
    +            refreshJSTicket();
    +            jst = _jstmc.get(mpAct.getMpId());
    +        }
    +        return jst.getTicket();
    +    }
    +
    +    @Override
    +    public List getMenu() {
    +        String url = mergeUrl(query_menu + getAccessToken());
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.get(url));
    +            if (ar.isSuccess()) {
    +                Map button = Json.fromJson(Map.class, Json.toJson(ar.get("menu")));
    +                return Json.fromJsonAsList(Menu.class, Json.toJson(button.get("button")));
    +            }
    +
    +            // 菜单为空
    +            if (ar.getErrCode().intValue() == 46003) {
    +                return null;
    +            }
    +
    +            log.errorf("Get mp[%s] custom menu failed. There try %d items.",
    +                       mpAct.getAppId(),
    +                       i + 1);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public boolean createMenu(Menu... menu) {
    +        String url = mergeUrl(create_menu + getAccessToken());
    +        Map body = new HashMap();
    +        body.put("button", menu);
    +        String data = Json.toJson(body, JsonFormat.compact());
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                return true;
    +            }
    +
    +            log.errorf("Create mp[%s] custom menu failed. There try %d items.",
    +                       mpAct.getAppId(),
    +                       i + 1);
    +
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public boolean delMenu() {
    +        String url = mergeUrl(del_menu + getAccessToken());
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.get(url));
    +            if (ar.isSuccess()) {
    +                return true;
    +            }
    +
    +            log.errorf("Delete mp[%s] custom menu failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       i + 1);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public Media upMedia(String type, File media) {
    +        String url = mergeUrl(upload_media, getAccessToken(), type);
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.upload(url, media));
    +            if (ar.isSuccess()) {
    +                return Json.fromJson(Media.class, ar.getJson());
    +            }
    +
    +            log.errorf("Upload mp[%s] media failed. There try %d items.", mpAct.getMpId(), i + 1);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public File dlMedia(String mediaId) {
    +        String url = mergeUrl(get_media, getAccessToken(), mediaId);
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            Object tmp = HttpTool.download(url);
    +            if (tmp instanceof File) {
    +                return (File) tmp;
    +            }
    +
    +            ar = ApiResult.create((String) tmp);
    +            log.errorf("Download mp[%s] media failed. There try %d items.", mpAct.getMpId(), i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public int createGroup(String name) {
    +        String url = mergeUrl(create_groups + getAccessToken());
    +        String data = "{\"group\":{\"name\":\"" + name + "\"}}";
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                Groups g = Json.fromJson(Groups.class, Json.toJson(ar.get("group")));
    +                return g.getId();
    +            }
    +
    +            log.errorf("Create mp[%s] group name[%s] failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       name,
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public List getGroups() {
    +        String url = mergeUrl(get_groups + getAccessToken());
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.get(url));
    +            if (ar.isSuccess()) {
    +                return Json.fromJsonAsList(Groups.class, Json.toJson(ar.get("groups")));
    +            }
    +
    +            log.errorf("Get mp[%s] groups failed. There try %d items.", mpAct.getMpId(), i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public int getGroup(String openId) {
    +        String url = mergeUrl(get_member_group + getAccessToken());
    +        String data = "{\"openid\":\"" + openId + "\"}";
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                return Integer.parseInt(String.valueOf(ar.get("groupid")));
    +            }
    +
    +            log.errorf("Get mp[%s] openId[%s] groups failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       openId,
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public boolean renGroups(int id, String name) {
    +        String url = mergeUrl(update_group + getAccessToken());
    +        String data = "{\"group\":{\"id\":" + id + ",\"name\":\"" + name + "\"}}";
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                return true;
    +            }
    +
    +            log.errorf("Rename mp[%s] groups[%d-%s] failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       id,
    +                       name,
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public boolean move2Group(String openId, int groupId) {
    +        String url = mergeUrl(update_member_group + getAccessToken());
    +        String data = "{\"openid\":\"" + openId + "\",\"to_groupid\":" + groupId + "}";
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                return true;
    +            }
    +
    +            log.errorf("Move mp[%s] openId[%s] to groups[%d] failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       openId,
    +                       groupId,
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public boolean batchMove2Group(Collection openIds, int groupId) {
    +        String url = mergeUrl(update_members_group + getAccessToken());
    +        Map data = new HashMap();
    +        data.put("openid_list", Json.toJson(openIds));
    +        data.put("to_groupid", groupId);
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, Json.toJson(data, JsonFormat.compact())));
    +            if (ar.isSuccess()) {
    +                return true;
    +            }
    +
    +            log.errorf("Move mp[%s] openIds to groups[%d] failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       groupId,
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public boolean delGroup(int id) {
    +        String url = mergeUrl(delete_groups + getAccessToken());
    +        String data = "{\"group\":{\"id\":" + id + "}}";
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                return true;
    +            }
    +
    +            log.errorf("Delete mp[%s] groups[%d] failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       id,
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public QRTicket createQR(Object sceneId, int expireSeconds) {
    +        String url = mergeUrl(create_qrcode + getAccessToken());
    +        ApiResult ar = null;
    +        NutMap data = new NutMap();
    +        NutMap scene;
    +        // 临时二维码
    +        if (expireSeconds > 0) {
    +            data.put("action_name", "QR_SCENE");
    +            data.put("expire_seconds", expireSeconds);
    +
    +            scene = Lang.map("scene_id", Castors.me().castTo(sceneId, Integer.class));
    +        }
    +        // 永久二维码
    +        else if (sceneId instanceof Number) {
    +            data.put("action_name", "QR_LIMIT_SCENE");
    +            scene = Lang.map("scene_id", Castors.me().castTo(sceneId, Integer.class));
    +        }
    +        // 永久字符串二维码
    +        else {
    +            data.put("action_name", "QR_LIMIT_STR_SCENE");
    +            scene = Lang.map("scene_str", sceneId.toString());
    +        }
    +        data.put("action_info", Lang.map("scene", scene));
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, Json.toJson(data, JsonFormat.compact())));
    +            if (ar.isSuccess()) {
    +                return Json.fromJson(QRTicket.class, Json.toJson(ar.getContent()));
    +            }
    +
    +            log.infof("Create mp[%s] scene[%s] qrcode failed. There try %d items.",
    +                      mpAct.getMpId(),
    +                      data.get("action_name"),
    +                      i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public File getQR(String ticket) {
    +        String url = mergeUrl(show_qrcode + ticket);
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            Object tmp = HttpTool.get(url);
    +            if (tmp instanceof File) {
    +                return (File) tmp;
    +            }
    +
    +            ar = ApiResult.create((String) tmp);
    +            log.errorf("Download mp[%s] qrcode image failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public boolean updateRemark(String openId, String remark) {
    +        String url = mergeUrl(user_remark + getAccessToken());
    +        ApiResult ar = null;
    +        String data = "{\"openid\":\"" + openId + "\",\"remark\":\"" + remark + "\"}";
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                return true;
    +            }
    +
    +            log.errorf("Update mp[%s] user[%s] remark[%s] failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       openId,
    +                       remark,
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public FollowList getFollowerList(String nextOpenId) {
    +        String url = mergeUrl(user_list, getAccessToken(), Strings.sNull(nextOpenId, ""));
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.get(url));
    +            if (ar.isSuccess()) {
    +                FollowList fl = Json.fromJson(FollowList.class, ar.getJson());
    +                Map openid = (Map) ar.get("data");
    +                fl.setOpenIds(Json.fromJson(List.class, Json.toJson(openid.get("openid"))));
    +                return fl;
    +            }
    +
    +            log.infof("Get mp[%s] follow list failed. There try %d items.", mpAct.getMpId(), i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public Follower getFollower(String openId, String lang) {
    +        String url = mergeUrl(user_info, getAccessToken(), openId, Strings.sNull(lang, "zh_CN"));
    +        ApiResult ar = null;
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.get(url));
    +            if (ar.isSuccess()) {
    +                return Json.fromJson(Follower.class, ar.getJson());
    +            }
    +
    +            log.errorf("Get mp[%s] follower[%s] information failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       openId,
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public List getFollowers(Collection users) {
    +        String url = mergeUrl(batch_user_info + getAccessToken());
    +        ApiResult ar = null;
    +        String data = Json.toJson(Lang.map("user_list", users), JsonFormat.compact());
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                return Json.fromJsonAsList(Follower.class, Json.toJson(ar.get("user_info_list")));
    +            }
    +
    +            log.errorf("Get mp[%s] followers information failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public boolean setIndustry(int id1, int id2) {
    +        String url = mergeUrl(set_industry + getAccessToken());
    +        ApiResult ar = null;
    +        String data = "{\"industry_id1\":\"" + id1 + "\",\"industry_id2\":\"" + id2 + "\"}";
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                return true;
    +            }
    +
    +            log.errorf("Set mp[%s] template industry failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public String getTemplateId(String tmlShortId) {
    +        String url = mergeUrl(add_template + getAccessToken());
    +        ApiResult ar = null;
    +        String data = "{\"template_id_short\":\"" + tmlShortId + "\"}";
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(url, data));
    +            if (ar.isSuccess()) {
    +                return String.valueOf(ar.get("template_id"));
    +            }
    +
    +            log.errorf("Get mp[%s] template id failed. There try %d items.", mpAct.getMpId(), i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +    @Override
    +    public long sendTemplateMsg(String openId,
    +                                String tmlId,
    +                                String topColor,
    +                                String url,
    +                                Template... tmls) {
    +        String apiurl = mergeUrl(send_template + getAccessToken());
    +        ApiResult ar = null;
    +        String data = JsonMsgBuilder.create().template(openId, tmlId, topColor, url, tmls).build();
    +        for (int i = 0; i < RETRY_COUNT; i++) {
    +            ar = ApiResult.create(HttpTool.post(apiurl, data));
    +            if (ar.isSuccess()) {
    +                return Long.valueOf(ar.get("msgid").toString());
    +            }
    +
    +            log.errorf("Send mp[%s] template message failed. There try %d items.",
    +                       mpAct.getMpId(),
    +                       i);
    +        }
    +
    +        throw Lang.wrapThrow(new WechatApiException(ar.getJson()));
    +    }
    +
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/api/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/api/package-info.java
    new file mode 100644
    index 0000000..f2123eb
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/api/package-info.java
    @@ -0,0 +1,7 @@
    +/**
    + * 微信公众平台所有的API
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +package io.github.elkan1788.mpsdk4j.api;
    \ No newline at end of file
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/commons/WxEventType.java b/src/main/java/io/github/elkan1788/mpsdk4j/common/EventType.java
    similarity index 76%
    rename from src/main/java/org/elkan1788/osc/weixin/mp/commons/WxEventType.java
    rename to src/main/java/io/github/elkan1788/mpsdk4j/common/EventType.java
    index dbe362e..f2cc6e5 100644
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/commons/WxEventType.java
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/common/EventType.java
    @@ -1,13 +1,17 @@
    -package org.elkan1788.osc.weixin.mp.commons;
    +package io.github.elkan1788.mpsdk4j.common;
     
     /**
    - * 微信事件的类型
    - *
    + * 事件消息类型
    + * 
      * @author 凡梦星尘(elkan1788@gmail.com)
    - * @version 1.0
    - * @since 2015/1/8
    + * @since 2.0
      */
    -public enum WxEventType {
    +public enum EventType {
    +
    +    /**
    +     * 新事件
    +     */
    +    def,
         /**
          * 用户订阅事件
          */
    @@ -65,5 +69,14 @@ public enum WxEventType {
         /**
          * 弹出地理位置选择器
          */
    -    location_select
    +    location_select,
    +    /**
    +     * 下发消息(除文本消息)
    +     */
    +    media_id,
    +    /**
    +     * 跳转图文消息URL
    +     */
    +    view_limited
    +
     }
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/common/MediaType.java b/src/main/java/io/github/elkan1788/mpsdk4j/common/MediaType.java
    new file mode 100644
    index 0000000..2c2d788
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/common/MediaType.java
    @@ -0,0 +1,27 @@
    +package io.github.elkan1788.mpsdk4j.common;
    +
    +/**
    + * 多媒体文件类型
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public enum MediaType {
    +
    +    /**
    +     * 图片: 1M,支持JPG格式
    +     */
    +    image, 
    +    /**
    +     * 语音:2M,播放长度不超过60s,支持AMR\MP3格式
    +     */
    +    voice, 
    +    /**
    +     * 视频:10MB,支持MP4格式
    +     */
    +    video, 
    +    /**
    +     * 缩略图:64KB,支持JPG格式
    +     */
    +    thumb
    +}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/commons/WxMsgType.java b/src/main/java/io/github/elkan1788/mpsdk4j/common/MessageType.java
    similarity index 59%
    rename from src/main/java/org/elkan1788/osc/weixin/mp/commons/WxMsgType.java
    rename to src/main/java/io/github/elkan1788/mpsdk4j/common/MessageType.java
    index 45aff87..c1ec6a7 100644
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/commons/WxMsgType.java
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/common/MessageType.java
    @@ -1,49 +1,56 @@
    -package org.elkan1788.osc.weixin.mp.commons;
    +package io.github.elkan1788.mpsdk4j.common;
     
     /**
    - * 微信消息的类型
    - *
    + * 被/主动消息类型
    + * 
      * @author 凡梦星尘(elkan1788@gmail.com)
    - * @version 1.0
    - * @since 2014/11/6
    + * @since 2.0
      */
    -public enum WxMsgType {
    +public enum MessageType {
    +    /**
    +     * 新功能
    +     */
    +    def, 
         /**
          * 文本消息
          */
    -    text,
    +    text, 
         /**
          * 图像消息
          */
    -    image,
    +    image, 
         /**
          * 语音消息
          */
    -    voice,
    +    voice, 
         /**
          * 视频消息
          */
    -    video,
    +    video, 
    +    /**
    +     * 小视频消息
    +    */
    +    shortvideo, 
         /**
          * 地理位置消息
          */
    -    location,
    +    location, 
         /**
          * 链接消息
          */
    -    link,
    +    link, 
         /**
          * 音乐消息
          */
    -    music,
    +    music, 
         /**
          * 多图文消息
          */
    -    news,
    +    news, 
         /**
          * 群发消息中的图文消息
          */
    -    mpnews,
    +    mpnews, 
         /**
          * 群发消息中的视频消息
          */
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/common/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/common/package-info.java
    new file mode 100644
    index 0000000..7a78ceb
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/common/package-info.java
    @@ -0,0 +1,7 @@
    +/**
    + * 微信公众平台的常量或功能组件
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +package io.github.elkan1788.mpsdk4j.common;
    \ No newline at end of file
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/core/JsonMsgBuilder.java b/src/main/java/io/github/elkan1788/mpsdk4j/core/JsonMsgBuilder.java
    new file mode 100644
    index 0000000..e069115
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/core/JsonMsgBuilder.java
    @@ -0,0 +1,239 @@
    +package io.github.elkan1788.mpsdk4j.core;
    +
    +import io.github.elkan1788.mpsdk4j.vo.api.Template;
    +import io.github.elkan1788.mpsdk4j.vo.message.Article;
    +import io.github.elkan1788.mpsdk4j.vo.message.BasicMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.ImageMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.MusicMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.NewsMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.TextMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.VideoMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.VoiceMsg;
    +
    +import org.nutz.log.Log;
    +import org.nutz.log.Logs;
    +
    +/**
    + * 创建微信公众平台高级接口消息
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public class JsonMsgBuilder {
    +
    +    private static final Log log = Logs.get();
    +
    +    private final StringBuffer msgBuf = new StringBuffer("{");
    +
    +    /**
    +     * 创建
    +     */
    +    public static JsonMsgBuilder create() {
    +        return new JsonMsgBuilder();
    +    }
    +
    +    /**
    +     * 创建消息体前缀
    +     *
    +     * @param msg
    +     *            客服消息
    +     */
    +    void msgPrefix(BasicMsg msg) {
    +        msgBuf.append("\"touser\":\"")
    +                .append(msg.getToUserName())
    +                .append("\",");
    +        msgBuf.append("\"msgtype\":\"")
    +                .append(msg.getMsgType())
    +                .append("\",");
    +    }
    +
    +    /**
    +     * 文本客服消息
    +     *
    +     * @param msg
    +     *            文消息
    +     */
    +    public JsonMsgBuilder text(TextMsg msg) {
    +        msgPrefix(msg);
    +        msgBuf.append("\"text\": {");
    +        msgBuf.append(" \"content\":\"")
    +                .append(msg.getContent())
    +                .append("\"");
    +        msgBuf.append("}");
    +        return this;
    +    }
    +
    +    /**
    +     * 图像客服消息
    +     *
    +     * @param msg
    +     *            图像消息
    +     */
    +    public JsonMsgBuilder image(ImageMsg msg) {
    +        msgPrefix(msg);
    +        msgBuf.append("\"image\": {");
    +        msgBuf.append(" \"media_id\":\"")
    +                .append(msg.getMediaId())
    +                .append("\"");
    +        msgBuf.append("}");
    +        return this;
    +    }
    +    
    +    /**
    +     * 语音客服消息
    +     *
    +     * @param msg
    +     *            语音
    +     */
    +    public JsonMsgBuilder voice(VoiceMsg msg) {
    +        msgPrefix(msg);
    +        msgBuf.append("\"voice\": {");
    +        msgBuf.append(" \"media_id\":\"")
    +                .append(msg.getMediaId())
    +                .append("\"");
    +        msgBuf.append("}");
    +        return this;
    +    }
    +
    +    /**
    +     * 视频客服消息
    +     *
    +     * @param msg
    +     *            视频消息
    +     */
    +    public JsonMsgBuilder video(VideoMsg msg) {
    +        msgPrefix(msg);
    +        msgBuf.append("\"video\": {");
    +        msgBuf.append(" \"media_id\":\"")
    +                .append(msg.getMediaId())
    +                .append("\",");
    +        msgBuf.append(" \"thumb_media_id\":\"")
    +                .append(msg.getThumbMediaId())
    +                .append("\",");
    +        msgBuf.append(" \"title\":\"")
    +                .append(msg.getTitle())
    +                .append("\",");
    +        msgBuf.append(" \"description\":\"")
    +                .append(msg.getDescription())
    +                .append("\"");
    +        msgBuf.append("}");
    +        return this;
    +    }
    +
    +    /**
    +     * 音乐客服消息
    +     *
    +     * @param msg   音乐消息
    +     */
    +    public JsonMsgBuilder music(MusicMsg msg) {
    +        msgPrefix(msg);
    +        msgBuf.append("\"music\": {");
    +        msgBuf.append(" \"title\":\"")
    +                .append(msg.getTitle())
    +                .append("\",");
    +        msgBuf.append(" \"description\":\"")
    +                .append(msg.getDescription())
    +                .append("\",");
    +        msgBuf.append(" \"musicurl\":\"")
    +                .append(msg.getMusicUrl())
    +                .append("\",");
    +        msgBuf.append(" \"hqmusicurl\":\"")
    +                .append(msg.getHQMusicUrl())
    +                .append("\",");
    +        msgBuf.append(" \"thumb_media_id\":\"")
    +                .append(msg.getThumbMediaId())
    +                .append("\"");
    +        msgBuf.append("}");
    +        return this;
    +    }
    +
    +    /**
    +     * 多图文客服消息
    +     *
    +     * @param msg   图文消息
    +     */
    +    public JsonMsgBuilder news(NewsMsg msg) {
    +        msgPrefix(msg);
    +        StringBuffer arts_buf = new StringBuffer("\"articles\": [");
    +        StringBuffer art_buf = new StringBuffer();
    +        for (Article art : msg.getArticles()) {
    +            art_buf.setLength(0);
    +            art_buf.append("{");
    +            art_buf.append(" \"title\":\"")
    +                    .append(art.getTitle())
    +                    .append("\",");
    +            art_buf.append(" \"description\":\"")
    +                    .append(art.getDescription())
    +                    .append("\",");
    +            art_buf.append(" \"picurl\":\"")
    +                    .append(art.getPicUrl())
    +                    .append("\",");
    +            art_buf.append(" \"url\":\"")
    +                    .append(art.getUrl());
    +            art_buf.append("\"},");
    +        }
    +        art_buf.deleteCharAt(art_buf.lastIndexOf(","));
    +        arts_buf.append(art_buf);
    +        arts_buf.append("]");
    +        msgBuf.append("\"news\": {");
    +        msgBuf.append(arts_buf);
    +        msgBuf.append("}");
    +        return this;
    +    }
    +
    +    /**
    +     * 模板消息
    +     *
    +     * @param openId
    +     *            接收者
    +     * @param tmlId
    +     *            模板Id
    +     * @param topColor
    +     *            顶部颜色
    +     * @param url
    +     *            链接地址
    +     * @param tmls
    +     *            模板数据
    +     */
    +    public JsonMsgBuilder template(String openId,
    +                                   String tmlId,
    +                                   String topColor,
    +                                   String url,
    +                                   Template... tmls) {
    +        msgBuf.append("\"touser\":\"")
    +                .append(openId)
    +                .append("\",");
    +        msgBuf.append("\"template_id\":\"")
    +                .append(tmlId)
    +                .append("\",");
    +        msgBuf.append("\"url\":\"")
    +                .append(url)
    +                .append("\",");
    +        msgBuf.append("\"topcolor\":\"")
    +                .append(topColor)
    +                .append("\",");
    +        msgBuf.append("\"data\":{");
    +        
    +        StringBuffer data = new StringBuffer("");
    +        for (Template t : tmls) {
    +            data.append(t.templateData()).append(",");
    +        }
    +        data.deleteCharAt(data.lastIndexOf(","));
    +        msgBuf.append(data);
    +        msgBuf.append("}");
    +        return this;
    +    }
    +
    +    /**
    +     * 输出消息
    +     * 
    +     * @return json格式消息
    +     */
    +    public String build() {
    +        msgBuf.append("}");
    +        if (log.isDebugEnabled()) {
    +            log.debugf("Json message content: %s", msgBuf);
    +        }
    +        return new String(msgBuf);
    +    }
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/core/MessageHandler.java b/src/main/java/io/github/elkan1788/mpsdk4j/core/MessageHandler.java
    new file mode 100644
    index 0000000..ade5366
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/core/MessageHandler.java
    @@ -0,0 +1,196 @@
    +package io.github.elkan1788.mpsdk4j.core;
    +
    +import java.util.Map;
    +import java.util.concurrent.ConcurrentHashMap;
    +
    +import org.nutz.lang.Strings;
    +import org.nutz.log.Log;
    +import org.nutz.log.Logs;
    +import org.xml.sax.Attributes;
    +import org.xml.sax.SAXException;
    +import org.xml.sax.ext.DefaultHandler2;
    +
    +/**
    + * 采用驱动式处理微信消息
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public class MessageHandler extends DefaultHandler2 {
    +
    +    private static final Log log = Logs.get();
    +
    +    // 节点属性值
    +    private String attrVal;
    +
    +    static Map _vals = new ConcurrentHashMap();
    +
    +    static StringBuffer _sb = new StringBuffer();
    +
    +    public Map getValues() {
    +        return _vals;
    +    }
    +
    +    @Override
    +    public void startDocument() throws SAXException {
    +        _vals.clear();
    +        _sb.setLength(0);
    +    }
    +
    +    @Override
    +    public void startElement(String uri, String localName, String qName, Attributes attributes)
    +            throws SAXException {
    +        if ("PicList".equals(qName)) {
    +            _sb.append("[");
    +            return;
    +        }
    +    }
    +
    +    @Override
    +    public void endElement(String uri, String localName, String qName) throws SAXException {
    +        if (log.isDebugEnabled()) {
    +            if (!Strings.equals("xml", qName)
    +                && !Strings.equals("ScanCodeInfo", qName)
    +                && !Strings.equals("SendLocationInfo", qName)
    +                && !Strings.equals("SendPicsInfo", qName)
    +                && !Strings.equals("PicList", qName)
    +                && !Strings.equals("item", qName)) {
    +                log.debugf("Current node vaule: [%s-%s]", qName, attrVal);
    +            }
    +        }
    +
    +        // 暂存为map集合
    +        if ("ToUserName".equals(qName)) {
    +            _vals.put("toUserName", attrVal);
    +            return;
    +        }
    +        if ("FromUserName".equals(qName)) {
    +            _vals.put("fromUserName", attrVal);
    +            return;
    +        }
    +        if ("CreateTime".equals(qName)) {
    +            _vals.put("createTime", attrVal);
    +            return;
    +        }
    +        if ("MsgType".equals(qName)) {
    +            _vals.put("msgType", attrVal);
    +            return;
    +        }
    +        if ("Content".equals(qName)) {
    +            _vals.put("content", attrVal);
    +            return;
    +        }
    +        if ("PicUrl".equals(qName)) {
    +            _vals.put("picUrl", attrVal);
    +            return;
    +        }
    +        if ("MediaId".equals(qName)) {
    +            _vals.put("mediaId", attrVal);
    +            return;
    +        }
    +        if ("Format".equals(qName)) {
    +            _vals.put("format", attrVal);
    +            return;
    +        }
    +        if ("Recognition".equals(qName)) {
    +            _vals.put("recognition", attrVal);
    +            return;
    +        }
    +        if ("ThumbMediaId".equals(qName)) {
    +            _vals.put("thumbMediaId", attrVal);
    +            return;
    +        }
    +        if ("Location_X".equals(qName)) {
    +            _vals.put("locationX", attrVal);
    +            return;
    +        }
    +        if ("Location_Y".equals(qName)) {
    +            _vals.put("locationY", attrVal);
    +            return;
    +        }
    +        if ("Scale".equals(qName)) {
    +            _vals.put("scale", attrVal);
    +            return;
    +        }
    +        if ("Label".equals(qName)) {
    +            _vals.put("label", attrVal);
    +            return;
    +        }
    +        if ("Title".equals(qName)) {
    +            _vals.put("title", attrVal);
    +            return;
    +        }
    +        if ("Description".equals(qName)) {
    +            _vals.put("description", attrVal);
    +            return;
    +        }
    +        if ("Url".equals(qName)) {
    +            _vals.put("url", attrVal);
    +            return;
    +        }
    +        if ("MsgId".equals(qName) || "MsgID".equals(qName)) {
    +            _vals.put("msgId", attrVal);
    +            return;
    +        }
    +        if ("Event".equals(qName)) {
    +            _vals.put("event", attrVal);
    +            return;
    +        }
    +        if ("EventKey".equals(qName)) {
    +            _vals.put("eventKey", attrVal);
    +            return;
    +        }
    +        if ("ScanType".equals(qName)) {
    +            _vals.put("scanType", attrVal);
    +            return;
    +        }
    +        if ("ScanResult".equals(qName)) {
    +            _vals.put("scanResult", attrVal);
    +            return;
    +        }
    +        if ("Poiname".equals(qName)) {
    +            _vals.put("poiname", attrVal);
    +            return;
    +        }
    +        if ("Count".equals(qName)) {
    +            _vals.put("count", attrVal);
    +            return;
    +        }
    +        if ("PicMd5Sum".equals(qName)) {
    +            _sb.append("{\"picMd5Sum\":\"").append(attrVal).append("\"},");
    +            return;
    +        }
    +        if ("PicList".equals(qName)) {
    +            _sb.deleteCharAt(_sb.lastIndexOf(","));
    +            _sb.append("]");
    +            _vals.put("picList", _sb.toString());
    +            return;
    +        }
    +        if ("Status".equals(qName)) {
    +            _vals.put("status", attrVal);
    +            return;
    +        }
    +        if ("TotalCount".equals(qName)) {
    +            _vals.put("totalCount", attrVal);
    +            return;
    +        }
    +        if ("FilterCount".equals(qName)) {
    +            _vals.put("filterCount", attrVal);
    +            return;
    +        }
    +        if ("SentCount".equals(qName)) {
    +            _vals.put("sentCount", attrVal);
    +            return;
    +        }
    +        if ("ErrorCount".equals(qName)) {
    +            _vals.put("errorCount", attrVal);
    +            return;
    +        }
    +    }
    +
    +    @Override
    +    public void characters(char[] ch, int start, int length) throws SAXException {
    +        this.attrVal = new String(ch, start, length);
    +    }
    +
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/core/WechatDefHandler.java b/src/main/java/io/github/elkan1788/mpsdk4j/core/WechatDefHandler.java
    new file mode 100644
    index 0000000..69f4d1c
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/core/WechatDefHandler.java
    @@ -0,0 +1,186 @@
    +package io.github.elkan1788.mpsdk4j.core;
    +
    +import java.util.Arrays;
    +
    +import org.nutz.lang.Strings;
    +
    +import io.github.elkan1788.mpsdk4j.vo.event.BasicEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.LocationEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.MenuEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.ScanCodeEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.ScanEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.SendLocationInfoEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.SendPhotosEvent;
    +import io.github.elkan1788.mpsdk4j.vo.message.Article;
    +import io.github.elkan1788.mpsdk4j.vo.message.BasicMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.ImageMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.LinkMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.LocationMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.NewsMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.TextMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.VideoMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.VoiceMsg;
    +import io.github.elkan1788.mpsdk4j.vo.push.SentAllJobEvent;
    +import io.github.elkan1788.mpsdk4j.vo.push.SentTmlJobEvent;
    +
    +/**
    + * 微信消息,事件处理器(实际生产中需要重写)
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public class WechatDefHandler implements WechatHandler {
    +
    +    @Override
    +    public BasicMsg defMsg(BasicMsg bm) {
    +        TextMsg tm = new TextMsg(bm);
    +        tm.setContent(bm.getMsgType());
    +        return tm;
    +    }
    +
    +    @Override
    +    public BasicMsg defEvent(BasicEvent be) {
    +        TextMsg tm = new TextMsg(be);
    +        tm.setContent(Strings.join("\n", be.getEvent(), be.getEventKey()));
    +        return tm;
    +    }
    +
    +    @Override
    +    public BasicMsg text(TextMsg tm) {
    +        return tm;
    +    }
    +
    +    @Override
    +    public BasicMsg image(ImageMsg im) {
    +        return im;
    +    }
    +
    +    @Override
    +    public BasicMsg voice(VoiceMsg vm) {
    +        TextMsg tm = new TextMsg(vm);
    +        tm.setContent(Strings.join("\n", vm.getMediaId(), vm.getFormat(), vm.getRecognition()));
    +        return tm;
    +    }
    +
    +    @Override
    +    public BasicMsg video(VideoMsg vim) {
    +        TextMsg tm = new TextMsg(vim);
    +        tm.setContent(Strings.join("\n", vim.getMsgType(), vim.getMediaId(), vim.getThumbMediaId()));
    +        return tm;
    +    }
    +
    +    @Override
    +    public BasicMsg shortVideo(VideoMsg vim) {
    +        TextMsg tm = new TextMsg(vim);
    +        tm.setContent(Strings.join("\n", vim.getMsgType(), vim.getMediaId(), vim.getThumbMediaId()));
    +        return tm;
    +    }
    +
    +    @Override
    +    public BasicMsg location(LocationMsg lm) {
    +        TextMsg tm = new TextMsg(lm);
    +        tm.setContent(Strings.join("\n",
    +                                   lm.getX(),
    +                                   lm.getY(),
    +                                   String.valueOf(lm.getScale()),
    +                                   lm.getLabel()));
    +        return tm;
    +    }
    +
    +    @Override
    +    public BasicMsg link(LinkMsg lm) {
    +        NewsMsg news = new NewsMsg(lm);
    +        Article art = new Article();
    +        art.setTitle(lm.getTitle());
    +        art.setDescription(lm.getDescription());
    +        art.setPicUrl("http://j2ee.u.qiniudn.com/mpsdk4j-logo.png-aliassmall");
    +        art.setUrl(lm.getUrl());
    +        news.setArticles(Arrays.asList(art));
    +        return news;
    +    }
    +
    +    @Override
    +    public BasicMsg eClick(MenuEvent me) {
    +        TextMsg tm = new TextMsg(me);
    +        tm.setContent(me.getEventKey());
    +        return tm;
    +    }
    +
    +    @Override
    +    public void eView(MenuEvent me) {}
    +
    +    @Override
    +    public BasicMsg eSub(BasicEvent be) {
    +        TextMsg tm = new TextMsg(be);
    +        tm.setContent("Welcom, wechat develop with use mpsdk4j!");
    +        return tm;
    +    }
    +
    +    @Override
    +    public void eUnSub(BasicEvent be) {}
    +
    +    @Override
    +    public BasicMsg eScan(ScanEvent se) {
    +        TextMsg tm = new TextMsg(se);
    +        tm.setContent(se.getEventKey() + se.getTicket());
    +        return tm;
    +    }
    +
    +    @Override
    +    public void eLocation(LocationEvent le) {}
    +
    +    @Override
    +    public BasicMsg eScanCodePush(ScanCodeEvent sce) {
    +        TextMsg tm = new TextMsg(sce);
    +        tm.setContent(Strings.join("\n", sce.getEventKey(), sce.getScanType(), sce.getScanResult()));
    +        return tm;
    +    }
    +
    +    @Override
    +    public BasicMsg eScanCodeWait(ScanCodeEvent sce) {
    +        return this.eScanCodePush(sce);
    +    }
    +
    +    @Override
    +    public BasicMsg ePicSysPhoto(SendPhotosEvent spe) {
    +        TextMsg tm = new TextMsg(spe);
    +        tm.setContent(Strings.join("\n",
    +                                   spe.getEventKey(),
    +                                   String.valueOf(spe.getSendPicsInfo().getCount()),
    +                                   String.valueOf(spe.getSendPicsInfo().getPicList()),
    +                                   String.valueOf(spe.getSendPicsInfo()
    +                                                     .getPicList()
    +                                                     .get(0)
    +                                                     .getPicMd5Sum())));
    +        return tm;
    +    }
    +
    +    @Override
    +    public BasicMsg ePicPhotoOrAlbum(SendPhotosEvent spe) {
    +        return this.ePicSysPhoto(spe);
    +    }
    +
    +    @Override
    +    public BasicMsg ePicWeixin(SendPhotosEvent spe) {
    +        return this.ePicSysPhoto(spe);
    +    }
    +
    +    @Override
    +    public BasicMsg eLocationSelect(SendLocationInfoEvent slie) {
    +        TextMsg tm = new TextMsg(slie);
    +        tm.setContent(Strings.join("\n",
    +                                   slie.getLocationX(),
    +                                   slie.getLocationY(),
    +                                   slie.getLabel(),
    +                                   String.valueOf(slie.getScale()),
    +                                   slie.getPoiname()));
    +        return tm;
    +    }
    +
    +    @Override
    +    public void eSentTmplJobFinish(SentTmlJobEvent stje) {}
    +
    +    @Override
    +    public void eSentAllJobFinish(SentAllJobEvent saje) {}
    +
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/core/WechatHandler.java b/src/main/java/io/github/elkan1788/mpsdk4j/core/WechatHandler.java
    new file mode 100644
    index 0000000..2318ead
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/core/WechatHandler.java
    @@ -0,0 +1,230 @@
    +package io.github.elkan1788.mpsdk4j.core;
    +
    +import io.github.elkan1788.mpsdk4j.vo.event.BasicEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.LocationEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.MenuEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.ScanCodeEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.ScanEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.SendLocationInfoEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.SendPhotosEvent;
    +import io.github.elkan1788.mpsdk4j.vo.message.BasicMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.ImageMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.LinkMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.LocationMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.TextMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.VideoMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.VoiceMsg;
    +import io.github.elkan1788.mpsdk4j.vo.push.SentAllJobEvent;
    +import io.github.elkan1788.mpsdk4j.vo.push.SentTmlJobEvent;
    +
    +/**
    + * 微信消息处理集合
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public interface WechatHandler {
    +
    +    /**
    +     * 新消息类型
    +     * 
    +     * @param bm
    +     *            新消息
    +     * @return 回复消息
    +     */
    +    BasicMsg defMsg(BasicMsg bm);
    +
    +    /**
    +     * 新事件类型
    +     * 
    +     * @param be
    +     *            新类型
    +     * @return 回复消息
    +     */
    +    BasicMsg defEvent(BasicEvent be);
    +
    +    /**
    +     * 处理文本消息
    +     *
    +     * @param tm
    +     *            文本消息
    +     * @return 回复消息
    +     */
    +    BasicMsg text(TextMsg tm);
    +
    +    /**
    +     * 处理图像消息
    +     *
    +     * @param im
    +     *            图像消息
    +     * @return 回复消息
    +     */
    +    BasicMsg image(ImageMsg im);
    +
    +    /**
    +     * 处理音频消息
    +     *
    +     * @param vom
    +     *            音频消息
    +     * @return 回复消息
    +     */
    +    BasicMsg voice(VoiceMsg vom);
    +
    +    /**
    +     * 处理视频消息
    +     *
    +     * @param vim
    +     *            视频消息
    +     * @return 回复消息
    +     */
    +    BasicMsg video(VideoMsg vim);
    +
    +    /**
    +     * 处理短视频消息
    +     *
    +     * @param vim
    +     *            短视频消息
    +     * @return 回复消息
    +     */
    +    BasicMsg shortVideo(VideoMsg vim);
    +
    +    /**
    +     * 处理地理位置消息
    +     * 
    +     * @param lm
    +     *            地理位置
    +     * @return 回复消息
    +     */
    +    BasicMsg location(LocationMsg lm);
    +
    +    /**
    +     * 处理链接消息
    +     *
    +     * @param lm
    +     *            链接消息
    +     * @return 回复消息
    +     */
    +    BasicMsg link(LinkMsg lm);
    +
    +    /**
    +     * 处理菜单点击事件消息
    +     *
    +     * @param me
    +     *            菜单事件
    +     * @return 回复消息
    +     */
    +    BasicMsg eClick(MenuEvent me);
    +
    +    /**
    +     * 处理菜单视图事件消息
    +     *
    +     * @param me
    +     *            菜单事件
    +     */
    +    void eView(MenuEvent me);
    +
    +    /**
    +     * 处理订阅事件消息
    +     *
    +     * @param be
    +     *            事件消息
    +     * @return 回复消息
    +     */
    +    BasicMsg eSub(BasicEvent be);
    +
    +    /**
    +     * 处理退订事件消息
    +     *
    +     * @param be
    +     *            事件消息
    +     */
    +    void eUnSub(BasicEvent be);
    +
    +    /**
    +     * 处理扫描事件消息
    +     *
    +     * @param se
    +     *            事件消息
    +     * @return 回复消息
    +     */
    +    BasicMsg eScan(ScanEvent se);
    +
    +    /**
    +     * 处理自动上传地理事件消息
    +     *
    +     * @param le
    +     *            地理事件事件
    +     */
    +    void eLocation(LocationEvent le);
    +
    +    /**
    +     * 处理二维码扫描事件消息
    +     *
    +     * @param sce
    +     *            扫码事件
    +     * @return 回复消息
    +     */
    +    BasicMsg eScanCodePush(ScanCodeEvent sce);
    +
    +    /**
    +     * 扫码推事件且弹出“消息接收中”提示框
    +     *
    +     * @param sce
    +     *            扫码事件
    +     * @return 回复消息
    +     */
    +    BasicMsg eScanCodeWait(ScanCodeEvent sce);
    +
    +    /**
    +     * 处理弹出系统拍照发图的事件推送
    +     *
    +     * @param spe
    +     *            发图事件
    +     * @return 回复消息
    +     */
    +    BasicMsg ePicSysPhoto(SendPhotosEvent spe);
    +
    +    /**
    +     * 处理弹出拍照或者相册发图的事件推送
    +     *
    +     * @param spe
    +     *            发图事件
    +     * @return 回复消息
    +     */
    +    BasicMsg ePicPhotoOrAlbum(SendPhotosEvent spe);
    +
    +    /**
    +     * 处理弹出微信相册发图器的事件推送
    +     *
    +     * @param spe
    +     *            发图事件
    +     * @return 回复消息
    +     */
    +    BasicMsg ePicWeixin(SendPhotosEvent spe);
    +
    +    /**
    +     * 处理弹出地理位置选择器的事件推送消息
    +     *
    +     * @param slie
    +     *            地理位置选取事件
    +     * @return 回复消息
    +     */
    +    BasicMsg eLocationSelect(SendLocationInfoEvent slie);
    +
    +    /**
    +     * 处理模板发送事件消息
    +     *
    +     * @param stje
    +     *            发送模板消息结果事件
    +     */
    +    void eSentTmplJobFinish(SentTmlJobEvent stje);
    +
    +    /**
    +     * 处理群发消息事件消息
    +     *
    +     * @param saje
    +     *            群发消息结果事件
    +     */
    +    void eSentAllJobFinish(SentAllJobEvent saje);
    +
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/core/WechatKernel.java b/src/main/java/io/github/elkan1788/mpsdk4j/core/WechatKernel.java
    new file mode 100644
    index 0000000..9825bb8
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/core/WechatKernel.java
    @@ -0,0 +1,416 @@
    +package io.github.elkan1788.mpsdk4j.core;
    +
    +import java.io.InputStream;
    +import java.util.Map;
    +import java.util.Map.Entry;
    +import java.util.Set;
    +
    +import javax.xml.parsers.SAXParser;
    +import javax.xml.parsers.SAXParserFactory;
    +
    +import org.nutz.lang.Lang;
    +import org.nutz.lang.Strings;
    +import org.nutz.log.Log;
    +import org.nutz.log.Logs;
    +
    +import io.github.elkan1788.mpsdk4j.common.EventType;
    +import io.github.elkan1788.mpsdk4j.common.MessageType;
    +import io.github.elkan1788.mpsdk4j.exception.WechatRunTimeException;
    +import io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes.AesException;
    +import io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes.SHA1;
    +import io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes.WXBizMsgCrypt;
    +import io.github.elkan1788.mpsdk4j.util.StreamTool;
    +import io.github.elkan1788.mpsdk4j.vo.MPAccount;
    +import io.github.elkan1788.mpsdk4j.vo.event.BasicEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.LocationEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.MenuEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.ScanCodeEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.ScanEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.SendLocationInfoEvent;
    +import io.github.elkan1788.mpsdk4j.vo.event.SendPhotosEvent;
    +import io.github.elkan1788.mpsdk4j.vo.message.BasicMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.ImageMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.LinkMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.LocationMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.MusicMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.NewsMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.TextMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.VideoMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.VoiceMsg;
    +import io.github.elkan1788.mpsdk4j.vo.push.SentAllJobEvent;
    +import io.github.elkan1788.mpsdk4j.vo.push.SentTmlJobEvent;
    +
    +/**
    + * 微信消息内核
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public class WechatKernel {
    +
    +    private static final Log log = Logs.get();
    +
    +    // XML解析准备
    +    private SAXParserFactory factory = SAXParserFactory.newInstance();
    +    private SAXParser xmlParser;
    +    private MessageHandler msgHandler = new MessageHandler();
    +
    +    // 消息处理器
    +    private WechatHandler handler;
    +    // Request参数
    +    private Map params;
    +    // 公众号信息
    +    private MPAccount mpAct;
    +
    +    public WechatKernel() {
    +        try {
    +            xmlParser = factory.newSAXParser();
    +        }
    +        catch (Exception e) {
    +            throw Lang.wrapThrow(new WechatRunTimeException("初始化SAXParserFactory出现异常", e));
    +        }
    +    }
    +
    +    public WechatKernel(MPAccount mpAct, WechatHandler handler, Map params) {
    +        this();
    +        this.mpAct = mpAct;
    +        this.handler = handler;
    +        this.params = params;
    +    }
    +
    +    /**
    +     * 设置微信服务器请求参数
    +     * 
    +     * @param params
    +     *            请求参数
    +     */
    +    public void setParams(Map params) {
    +        this.params = params;
    +        if (log.isDebugEnabled()) {
    +            Set> es = params.entrySet();
    +            log.debug("wechat server request params.");
    +            for (Entry e : es) {
    +                log.debugf("%s-%s", e.getKey(), e.getValue()[ 0]);
    +            }
    +        }
    +    }
    +
    +    /**
    +     * 设置公众号信息
    +     * 
    +     * @param mpAct
    +     *            公众号信息
    +     */
    +    public void setMpAct(MPAccount mpAct) {
    +        this.mpAct = mpAct;
    +    }
    +
    +    /**
    +     * 设置微信消息处理器
    +     * 
    +     * @param handler
    +     *            消息处理器
    +     */
    +    public void setWechatHandler(WechatHandler handler) {
    +        this.handler = handler;
    +    }
    +
    +    /**
    +     * 获取参数值
    +     * 
    +     * @param param
    +     *            参数名
    +     * @return 参数值
    +     */
    +    protected String get(String param) {
    +        String[] vals = params.get(param);
    +        return vals == null ? null : vals[ 0];
    +    }
    +
    +    /**
    +     * 微信服务器校验
    +     * 
    +     * @return 微信服务器随机字符串
    +     */
    +    public String check() {
    +        String sign = get("signature");
    +        String ts = get("timestamp");
    +        String nonce = get("nonce");
    +
    +        if (sign == null
    +            || sign.length() > 128
    +            || ts == null
    +            || ts.length() > 128
    +            || nonce == null
    +            || nonce.length() > 128) {
    +            log.warnf("The sign params are null or too long. Please check them.");
    +            return "error";
    +        }
    +
    +        try {
    +            String validsign = SHA1.calculate(mpAct.getToken(), ts, nonce);
    +            if (log.isDebugEnabled()) {
    +                log.debugf("Valid wechat server sign %b. sign: %s",
    +                           Lang.equals(validsign, sign),
    +                           validsign);
    +            }
    +
    +            if(sign.equals(validsign)){
    +                return get("echostr");
    +            }
    +
    +            return "error";
    +        }
    +        catch (AesException e) {
    +            throw Lang.wrapThrow(new WechatRunTimeException("校验服务器认证出现异常", e));
    +        }
    +    }
    +
    +    /**
    +     * 与微信服务器交互处理过程
    +     * 
    +     * @param is
    +     *            微信服务器推送消息
    +     * @return 响应消息
    +     */
    +    // TODO 是否考虑添加重复消息过滤功能
    +    public String handle(InputStream is) {
    +        String encrypt = get("encrypt_type");
    +        WXBizMsgCrypt pc = null;
    +        BasicMsg msg = null;
    +        String respmsg = "success";
    +        // 密文模式
    +        if (encrypt != null && "aes".equals(encrypt) && mpAct.getAESKey() != null) {
    +            try {
    +                pc = new WXBizMsgCrypt(mpAct.getToken(), mpAct.getAESKey(), mpAct.getAppId());
    +
    +                String ts = get("timestamp");
    +                String nonce = get("nonce");
    +                String msgsign = get("msg_signature");
    +
    +                String decmsg = pc.decryptMsg(msgsign, ts, nonce, is);
    +                xmlParser.parse(StreamTool.toStream(decmsg), msgHandler);
    +                msg = handleMsg();
    +                respmsg = pc.encryptMsg(responseXML(msg), ts, nonce);
    +            }
    +            catch (Exception e) {
    +                throw Lang.wrapThrow(new WechatRunTimeException("使用密文模式出现异常", e));
    +            }
    +        }
    +        // 明文模式
    +        else {
    +            try {
    +                xmlParser.parse(is, msgHandler);
    +            }
    +            catch (Exception e) {
    +                throw Lang.wrapThrow(new WechatRunTimeException("明文模式下解析消息出现异常", e));
    +            }
    +            msg = handleMsg();
    +            respmsg = responseXML(msg);
    +        }
    +
    +        return respmsg;
    +    }
    +
    +    /**
    +     * 微信消息处理
    +     * 
    +     * @return 回复消息
    +     */
    +    protected BasicMsg handleMsg() {
    +        String msgtype = msgHandler.getValues().get("msgType");
    +        if ("event".equals(msgtype)) {
    +            return handleEventMsg();
    +        }
    +        else {
    +            return handleNormalMsg();
    +        }
    +    }
    +
    +    /**
    +     * 处理普通消息
    +     * 
    +     * @return 回复消息
    +     */
    +    protected BasicMsg handleNormalMsg() {
    +        BasicMsg msg = null;
    +        MessageType mt;
    +        try {
    +            mt = MessageType.valueOf(msgHandler.getValues().get("msgType"));
    +        }
    +        catch (Exception e) {
    +            log.error("There are have found new meessage type in wechat.");
    +            mt = MessageType.def;
    +        }
    +        switch (mt) {
    +            case text:
    +                TextMsg tm = new TextMsg(msgHandler.getValues());
    +                msg = handler.text(tm);
    +                break;
    +            case image:
    +                ImageMsg im = new ImageMsg(msgHandler.getValues());
    +                msg = handler.image(im);
    +                break;
    +            case voice:
    +                VoiceMsg vom = new VoiceMsg(msgHandler.getValues());
    +                msg = handler.voice(vom);
    +                break;
    +            case video:
    +                VideoMsg vim = new VideoMsg(msgHandler.getValues());
    +                msg = handler.video(vim);
    +                break;
    +            case shortvideo:
    +                VideoMsg shortvim = new VideoMsg(msgHandler.getValues());
    +                msg = handler.shortVideo(shortvim);
    +                break;
    +            case location:
    +                LocationMsg locm = new LocationMsg(msgHandler.getValues());
    +                msg = handler.location(locm);
    +                break;
    +            case link:
    +                LinkMsg lm = new LinkMsg(msgHandler.getValues());
    +                msg = handler.link(lm);
    +                break;
    +            default:
    +                BasicMsg bm = new BasicMsg(msgHandler.getValues());
    +                msg = handler.defMsg(bm);
    +                break;
    +        }
    +        return msg;
    +    }
    +
    +    /**
    +     * 处理事件消息
    +     * 
    +     * @return 回复消息
    +     */
    +    protected BasicMsg handleEventMsg() {
    +        BasicMsg msg = null;
    +        EventType et;
    +        try {
    +            et = EventType.valueOf(msgHandler.getValues().get("event"));
    +        }
    +        catch (Exception e) {
    +            log.error("There are have found new event type from wechat.");
    +            et = EventType.def;
    +        }
    +        switch (et) {
    +            case subscribe:
    +                BasicEvent sube = new BasicEvent(msgHandler.getValues());
    +                msg = handler.eSub(sube);
    +                break;
    +            case unsubscribe:
    +                BasicEvent unsube = new BasicEvent(msgHandler.getValues());
    +                handler.eUnSub(unsube);
    +                break;
    +            case SCAN:
    +                ScanEvent se = new ScanEvent(msgHandler.getValues());
    +                msg = handler.eScan(se);
    +                break;
    +            case LOCATION:
    +                LocationEvent le = new LocationEvent(msgHandler.getValues());
    +                handler.eLocation(le);
    +                break;
    +            case CLICK:
    +                MenuEvent cme = new MenuEvent(msgHandler.getValues());
    +                msg = handler.eClick(cme);
    +                break;
    +            case VIEW:
    +                MenuEvent vme = new MenuEvent(msgHandler.getValues());
    +                handler.eView(vme);
    +                break;
    +            case scancode_push:
    +                ScanCodeEvent sce = new ScanCodeEvent(msgHandler.getValues());
    +                msg = handler.eScanCodePush(sce);
    +                break;
    +            case scancode_waitmsg:
    +                ScanCodeEvent scemsg = new ScanCodeEvent(msgHandler.getValues());
    +                msg = handler.eScanCodeWait(scemsg);
    +                break;
    +            case pic_sysphoto:
    +                SendPhotosEvent spesys = new SendPhotosEvent(msgHandler.getValues());
    +                msg = handler.ePicSysPhoto(spesys);
    +                break;
    +            case pic_photo_or_album:
    +                SendPhotosEvent spealb = new SendPhotosEvent(msgHandler.getValues());
    +                msg = handler.ePicPhotoOrAlbum(spealb);
    +                break;
    +            case pic_weixin:
    +                SendPhotosEvent spewx = new SendPhotosEvent(msgHandler.getValues());
    +                msg = handler.ePicWeixin(spewx);
    +                break;
    +            case location_select:
    +                SendLocationInfoEvent lse = new SendLocationInfoEvent(msgHandler.getValues());
    +                msg = handler.eLocationSelect(lse);
    +                break;
    +            // TODO 暂不清楚微信的推送
    +            /*
    +             * case media_id:
    +             * case view_limited:
    +             * BasicEvent mvbe = new BasicEvent(msgHandler.getValues());
    +             * msg = handler.defEvent(mvbe);
    +             * break;
    +             */
    +            case TEMPLATESENDJOBFINISH:
    +                SentTmlJobEvent stje = new SentTmlJobEvent(msgHandler.getValues());
    +                handler.eSentTmplJobFinish(stje);
    +                break;
    +            case MASSSENDJOBFINISH:
    +                SentAllJobEvent saje = new SentAllJobEvent(msgHandler.getValues());
    +                handler.eSentAllJobFinish(saje);
    +                break;
    +            default:
    +                BasicEvent be = new BasicEvent(msgHandler.getValues());
    +                msg = handler.defEvent(be);
    +                break;
    +        }
    +        return msg;
    +    }
    +
    +    /**
    +     * 输出回复消息
    +     * 
    +     * @param msg
    +     *            回复消息数据
    +     * @return XML消息
    +     */
    +    protected String responseXML(BasicMsg msg) {
    +        String respmsg = "success";
    +        if (msg == null || Strings.isBlank(msg.getMsgType())) {
    +            return respmsg;
    +        }
    +
    +        // 交换 fromUser 和 toUser
    +        String fromUser = msg.getFromUserName();
    +        String toUser = msg.getToUserName();
    +        msg.setFromUserName(toUser);
    +        msg.setToUserName(fromUser);
    +
    +        MessageType mt = MessageType.valueOf(msg.getMsgType());
    +
    +        switch (mt) {
    +            case text:
    +                respmsg = XmlMsgBuilder.create().text((TextMsg) msg).build();
    +                break;
    +            case image:
    +                respmsg = XmlMsgBuilder.create().image((ImageMsg) msg).build();
    +                break;
    +            case voice:
    +                respmsg = XmlMsgBuilder.create().voice((VoiceMsg) msg).build();
    +                break;
    +            case music:
    +                respmsg = XmlMsgBuilder.create().music((MusicMsg) msg).build();
    +                break;
    +            case video:
    +                respmsg = XmlMsgBuilder.create().video((VideoMsg) msg).build();
    +                break;
    +            case news:
    +                respmsg = XmlMsgBuilder.create().news((NewsMsg) msg).build();
    +                break;
    +            default:
    +                break;
    +        }
    +        return respmsg;
    +    }
    +
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/core/XmlMsgBuilder.java b/src/main/java/io/github/elkan1788/mpsdk4j/core/XmlMsgBuilder.java
    new file mode 100644
    index 0000000..a14b3b5
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/core/XmlMsgBuilder.java
    @@ -0,0 +1,199 @@
    +package io.github.elkan1788.mpsdk4j.core;
    +
    +import org.nutz.log.Log;
    +import org.nutz.log.Logs;
    +
    +import io.github.elkan1788.mpsdk4j.vo.message.Article;
    +import io.github.elkan1788.mpsdk4j.vo.message.BasicMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.ImageMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.MusicMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.NewsMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.TextMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.VideoMsg;
    +import io.github.elkan1788.mpsdk4j.vo.message.VoiceMsg;
    +
    +/**
    + * 创建微信公众平台被动响应消息
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public class XmlMsgBuilder {
    +
    +    private static final Log log = Logs.get();
    +
    +    private final StringBuffer msgBuf = new StringBuffer("\n");;
    +
    +    /**
    +     * 创建
    +     */
    +    public static XmlMsgBuilder create() {
    +        return new XmlMsgBuilder();
    +    }
    +
    +    /**
    +     * 创建消息体前缀
    +     *
    +     * @param msg
    +     *            输出消息实体
    +     */
    +    void msgPrefix(BasicMsg msg) {
    +        msgBuf.append("\n");
    +        msgBuf.append("\n");
    +        msgBuf.append("").append(msg.getCreateTime()).append("\n");
    +        msgBuf.append("\n");
    +    }
    +
    +    /**
    +     * 被动文本消息
    +     * 
    +     * @param msg
    +     *            文本消息
    +     */
    +    public XmlMsgBuilder text(TextMsg msg) {
    +        msgPrefix(msg);
    +        msgBuf.append("\n");
    +        return this;
    +    }
    +
    +    /**
    +     * 被动图像消息
    +     *
    +     * @param msg
    +     *            图像消息
    +     */
    +    public XmlMsgBuilder image(ImageMsg msg) {
    +        msgPrefix(msg);
    +        msgBuf.append("");
    +        msgBuf.append("\n");
    +        msgBuf.append("");
    +        return this;
    +    }
    +
    +    /**
    +     * 被动语音消息
    +     *
    +     * @param msg
    +     *            音频消息
    +     */
    +    public XmlMsgBuilder voice(VoiceMsg msg) {
    +        msgPrefix(msg);
    +        msgBuf.append("");
    +        msgBuf.append("\n");
    +        msgBuf.append("\n");
    +        return this;
    +    }
    +
    +    /**
    +     * 被动视频消息
    +     *
    +     * @param msg
    +     *            视频消息
    +     */
    +    public XmlMsgBuilder video(VideoMsg msg) {
    +        msgPrefix(msg);
    +        msgBuf.append("\n");
    +        return this;
    +    }
    +
    +    /**
    +     * 被动音乐消息
    +     * 
    +     * @param msg
    +     *            音乐消息
    +     */
    +
    +    public XmlMsgBuilder music(MusicMsg msg) {
    +        msgPrefix(msg);
    +        msgBuf.append("");
    +        msgBuf.append("<![CDATA[").append(msg.getTitle()).append("]]>\n");
    +        msgBuf.append("\n");
    +        msgBuf.append("\n");
    +        msgBuf.append("\n");
    +        msgBuf.append("\n");
    +        msgBuf.append("\n");
    +        return this;
    +    }
    +
    +    /**
    +     * 被动多图文消息
    +     *
    +     * @param msg
    +     *            图文消息
    +     */
    +    public XmlMsgBuilder news(NewsMsg msg) {
    +        msgPrefix(msg);
    +        StringBuffer arts_buf = new StringBuffer("\n");
    +        StringBuffer item_buf = new StringBuffer();
    +        for (Article art : msg.getArticles()) {
    +            item_buf.setLength(0);
    +            item_buf.append("\n");
    +            item_buf.append("<![CDATA[").append(art.getTitle()).append("]]>\n");
    +            item_buf.append("\n");
    +            item_buf.append("\n");
    +            item_buf.append("\n");
    +            item_buf.append("\n");
    +            arts_buf.append(item_buf);
    +        }
    +        arts_buf.append("\n");
    +        msgBuf.append("").append(msg.getCount()).append("\n");
    +        msgBuf.append(arts_buf);
    +        return this;
    +    }
    +
    +    /**
    +     * AES加密信息
    +     *
    +     * @param xml
    +     *            密文消息
    +     * @param msgSignature
    +     *            消息签名
    +     * @param timeStamp
    +     *            时间戳
    +     * @param nonce
    +     *            随机字符
    +     */
    +    public String encrypt(String xml, String msgSignature, String timeStamp, String nonce) {
    +        msgBuf.setLength(0);
    +        msgBuf.append("\n");
    +        msgBuf.append("\n");
    +        msgBuf.append("\n");
    +        msgBuf.append("").append(timeStamp).append("\n");
    +        msgBuf.append("\n");
    +        msgBuf.append("");
    +        return msgBuf.toString();
    +    }
    +
    +    /**
    +     * 输出回复消息
    +     *
    +     * @return 回复消息
    +     */
    +    public String build() {
    +        msgBuf.append("");
    +        if (log.isDebugEnabled()) {
    +            log.debugf("Xml message content: \n%s", msgBuf);
    +        }
    +        return new String(msgBuf);
    +    }
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/core/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/core/package-info.java
    new file mode 100644
    index 0000000..226c11c
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/core/package-info.java
    @@ -0,0 +1,7 @@
    +/**
    + * 微信公众平台业务逻辑实现
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +package io.github.elkan1788.mpsdk4j.core;
    \ No newline at end of file
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/exception/WechatApiException.java b/src/main/java/io/github/elkan1788/mpsdk4j/exception/WechatApiException.java
    new file mode 100644
    index 0000000..b294953
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/exception/WechatApiException.java
    @@ -0,0 +1,29 @@
    +package io.github.elkan1788.mpsdk4j.exception;
    +
    +/**
    + * 微信高级API请求异常
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public class WechatApiException extends Exception {
    +
    +    private static final long serialVersionUID = -303278319021435258L;
    +
    +    public WechatApiException() {
    +        super();
    +    }
    +
    +    public WechatApiException(String message, Throwable cause) {
    +        super(message, cause);
    +    }
    +
    +    public WechatApiException(String message) {
    +        super(message);
    +    }
    +
    +    public WechatApiException(Throwable cause) {
    +        super(cause);
    +    }
    +
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/exception/WechatRunTimeException.java b/src/main/java/io/github/elkan1788/mpsdk4j/exception/WechatRunTimeException.java
    new file mode 100644
    index 0000000..7ddf8fb
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/exception/WechatRunTimeException.java
    @@ -0,0 +1,29 @@
    +package io.github.elkan1788.mpsdk4j.exception;
    +
    +/**
    + * 与微信服务交互时异常
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public class WechatRunTimeException extends Exception {
    +
    +    private static final long serialVersionUID = 7300175978685072703L;
    +
    +    public WechatRunTimeException() {
    +        super();
    +    }
    +
    +    public WechatRunTimeException(String msg, Throwable e) {
    +        super(msg, e);
    +    }
    +
    +    public WechatRunTimeException(String e) {
    +        super(e);
    +    }
    +
    +    public WechatRunTimeException(Throwable e) {
    +        super(e);
    +    }
    +
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/exception/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/exception/package-info.java
    new file mode 100644
    index 0000000..aa0002a
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/exception/package-info.java
    @@ -0,0 +1,7 @@
    +/**
    + * 微信公众平台自定义异常
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +package io.github.elkan1788.mpsdk4j.exception;
    \ No newline at end of file
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/mvc/HttpServletSupport.java b/src/main/java/io/github/elkan1788/mpsdk4j/mvc/HttpServletSupport.java
    new file mode 100644
    index 0000000..9e8913f
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/mvc/HttpServletSupport.java
    @@ -0,0 +1,55 @@
    +package io.github.elkan1788.mpsdk4j.mvc;
    +
    +import java.io.IOException;
    +
    +import javax.servlet.ServletException;
    +import javax.servlet.http.HttpServlet;
    +import javax.servlet.http.HttpServletRequest;
    +import javax.servlet.http.HttpServletResponse;
    +
    +import io.github.elkan1788.mpsdk4j.core.WechatKernel;
    +
    +/**
    + * Servlet环境接入
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +@SuppressWarnings("unchecked")
    +public abstract class HttpServletSupport extends HttpServlet {
    +
    +    private static final long serialVersionUID = 8041879868339754244L;
    +
    +    protected static WechatKernel _wk = new WechatKernel();
    +
    +    /**
    +     * !!!实际生产中需要重写此方法内的数据!!!
    +     * 
      + *
    1. 开发者的微信公众号信息
    2. + *
    3. 微信消息处理器
    4. + *
    + */ + @Override + public void init() throws ServletException {} + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, + IOException { + _wk.setParams(req.getParameterMap()); + String echo = _wk.check(); + resp.setCharacterEncoding("UTF-8"); + resp.setContentType("text/html"); + resp.getWriter().print(echo); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + _wk.setParams(req.getParameterMap()); + String respmsg = _wk.handle(req.getInputStream()); + resp.setCharacterEncoding("UTF-8"); + resp.setContentType("text/html"); + resp.getWriter().print(respmsg); + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/mvc/WechatWebSupport.java b/src/main/java/io/github/elkan1788/mpsdk4j/mvc/WechatWebSupport.java new file mode 100644 index 0000000..08aac88 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/mvc/WechatWebSupport.java @@ -0,0 +1,58 @@ +package io.github.elkan1788.mpsdk4j.mvc; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.github.elkan1788.mpsdk4j.core.WechatKernel; + +/** + * 各种WEB容器环境接入 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@SuppressWarnings("unchecked") +public abstract class WechatWebSupport { + + protected static WechatKernel _wk = new WechatKernel(); + + /** + * !!!实际生产中需要重写此方法内的数据!!! + *
      + *
    1. 开发者的微信公众号信息
    2. + *
    3. 微信消息处理器
    4. + *
    + */ + public void init() {} + + /** + * 与微信服务器互动 + * + *
    +     * !!!实际生产中写个入口方法调用即可!!!
    +     * 
    +     * @param req
    +     *            微信服务器请求
    +     * @param resp
    +     *            响应微信服务器
    +     * @throws IOException
    +     */
    +    protected void interact(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    +        init();
    +        _wk.setParams(req.getParameterMap());
    +        String respmsg = "success";
    +        if ("GET".equals(req.getMethod())) {
    +            respmsg = _wk.check();
    +        }
    +        else {
    +            respmsg = _wk.handle(req.getInputStream());
    +        }
    +        // 输出回复消息
    +        resp.setCharacterEncoding("UTF-8");
    +        resp.setContentType("text/html");
    +        resp.getWriter().print(respmsg);
    +    }
    +
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/mvc/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/mvc/package-info.java
    new file mode 100644
    index 0000000..6ba9ac0
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/mvc/package-info.java
    @@ -0,0 +1,7 @@
    +/**
    + * 各类MVC框架融合设计
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +package io.github.elkan1788.mpsdk4j.mvc;
    \ No newline at end of file
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/package-info.java
    new file mode 100644
    index 0000000..6e21e7c
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/package-info.java
    @@ -0,0 +1,7 @@
    +/**
    + * Java微信公平台开发SDK,没有复杂的功能,一切源于微信API,愿你会喜欢使用.
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +package io.github.elkan1788.mpsdk4j;
    \ No newline at end of file
    diff --git a/src/main/java/com/qq/weixin/mp/aes/AesException.java b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/AesException.java
    similarity index 87%
    rename from src/main/java/com/qq/weixin/mp/aes/AesException.java
    rename to src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/AesException.java
    index d230e57..658e140 100644
    --- a/src/main/java/com/qq/weixin/mp/aes/AesException.java
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/AesException.java
    @@ -1,4 +1,4 @@
    -package com.qq.weixin.mp.aes;
    +package io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes;
     
     /**
      * AES加密异常
    @@ -8,6 +8,8 @@
      */
     public class AesException extends Exception {
     
    +	private static final long serialVersionUID = 6602992230328883295L;
    +
     	public final static int OK = 0;
     	public final static int ValidateSignatureError = -40001;
     	public final static int ParseXmlError = -40002;
    @@ -23,10 +25,10 @@ public class AesException extends Exception {
     
     	private int code;
     
    -    public AesException(int code) {
    -        super(getMessage(code));
    -        this.code = code;
    -    }
    +	public AesException(int code) {
    +		super(getMessage(code));
    +		this.code = code;
    +	}
     
     	private static String getMessage(int code) {
     		switch (code) {
    @@ -61,5 +63,4 @@ public int getCode() {
     		return code;
     	}
     
    -
     }
    diff --git a/src/main/java/com/qq/weixin/mp/aes/ByteGroup.java b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/ByteGroup.java
    similarity index 61%
    rename from src/main/java/com/qq/weixin/mp/aes/ByteGroup.java
    rename to src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/ByteGroup.java
    index dcf0197..08f0f9b 100644
    --- a/src/main/java/com/qq/weixin/mp/aes/ByteGroup.java
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/ByteGroup.java
    @@ -1,4 +1,4 @@
    -package com.qq.weixin.mp.aes;
    +package io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes;
     
     import java.util.ArrayList;
     import java.util.List;
    @@ -11,14 +11,14 @@
      */
     public class ByteGroup {
     
    -    // 定义一个字节容器
    +	// 定义一个字节容器
     	private List byteContainer = new ArrayList();
     
    -    /**
    -     * 将容器转化成字节数组
    -     *
    -     * @return 字节数组
    -     */
    +	/**
    +	 * 将容器转化成字节数组
    +	 *
    +	 * @return 字节数组
    +	 */
     	public byte[] toBytes() {
     		byte[] bytes = new byte[byteContainer.size()];
     		for (int i = 0; i < byteContainer.size(); i++) {
    @@ -27,12 +27,13 @@ public byte[] toBytes() {
     		return bytes;
     	}
     
    -    /**
    -     * 添加字节
    -     *
    -     * @param bytes 字节数组
    -     * @return com.qq.weixin.mp.aes.ByteGroup
    -     */
    +	/**
    +	 * 添加字节
    +	 *
    +	 * @param bytes
    +	 *            字节数组
    +	 * @return com.qq.weixin.mp.aes.ByteGroup
    +	 */
     	public ByteGroup addBytes(byte[] bytes) {
     		for (byte b : bytes) {
     			byteContainer.add(b);
    @@ -40,11 +41,11 @@ public ByteGroup addBytes(byte[] bytes) {
     		return this;
     	}
     
    -    /**
    -     * 获取字节容器长度
    -     *
    -     * @return  容器长度
    -     */
    +	/**
    +	 * 获取字节容器长度
    +	 *
    +	 * @return 容器长度
    +	 */
     	public int size() {
     		return byteContainer.size();
     	}
    diff --git a/src/main/java/com/qq/weixin/mp/aes/PKCS7Encoder.java b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/PKCS7Encoder.java
    similarity index 80%
    rename from src/main/java/com/qq/weixin/mp/aes/PKCS7Encoder.java
    rename to src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/PKCS7Encoder.java
    index d328e8e..806596a 100644
    --- a/src/main/java/com/qq/weixin/mp/aes/PKCS7Encoder.java
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/PKCS7Encoder.java
    @@ -1,4 +1,4 @@
    -package com.qq.weixin.mp.aes;
    +package io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes;
     
     import java.nio.charset.Charset;
     import java.util.Arrays;
    @@ -11,13 +11,14 @@
      */
     public class PKCS7Encoder {
     
    -    private static Charset CHARSET = Charset.forName("utf-8");
    +	private static Charset CHARSET = Charset.forName("utf-8");
     	private static int BLOCK_SIZE = 32;
     
     	/**
     	 * 获得对明文进行补位填充的字节
     	 * 
    -	 * @param count 需要进行填充补位操作的明文字节个数
    +	 * @param count
    +	 *            需要进行填充补位操作的明文字节个数
     	 * @return 补齐用的字节数组
     	 */
     	public static byte[] encode(int count) {
    @@ -39,7 +40,8 @@ public static byte[] encode(int count) {
     	/**
     	 * 删除解密后明文的补位字符
     	 * 
    -	 * @param decrypted 解密后的明文
    +	 * @param decrypted
    +	 *            解密后的明文
     	 * @return 删除补位字符后的明文
     	 */
     	public static byte[] decode(byte[] decrypted) {
    @@ -53,7 +55,8 @@ public static byte[] decode(byte[] decrypted) {
     	/**
     	 * 将数字转化成ASCII码对应的字符,用于对明文进行补码
     	 * 
    -	 * @param a 需要转化的数字
    +	 * @param a
    +	 *            需要转化的数字
     	 * @return 转化得到的字符
     	 */
     	public static char chr(int a) {
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/SHA1.java b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/SHA1.java
    new file mode 100644
    index 0000000..0d6d102
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/SHA1.java
    @@ -0,0 +1,52 @@
    +package io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes;
    +
    +import java.security.MessageDigest;
    +import java.util.Arrays;
    +import java.util.Formatter;
    +
    +/**
    + * SHA1算法计算公众平台的消息签名接口
    + *
    + * @author Tencent
    + * @since 2014/11/4
    + */
    +public class SHA1 {
    +
    +	/**
    +	 * 用SHA1算法生成安全签名
    +	 *
    +	 * @param params
    +	 *            [token, timestamp, nonce, encrypt]
    +	 * @return 安全签名
    +	 * @throws com.qq.weixin.mp.aes.AesException
    +	 */
    +	public static String calculate(String... params) throws AesException {
    +		try {
    +			String[] array = params;
    +			StringBuffer sb = new StringBuffer();
    +			// 字符串排序
    +			Arrays.sort(array);
    +			int len = params.length;
    +			for (int i = 0; i < len; i++) {
    +				sb.append(array[i]);
    +			}
    +
    +			// SHA1签名生成
    +			MessageDigest md = MessageDigest.getInstance("SHA-1");
    +			md.reset();
    +			md.update(new String(sb).getBytes("UTF-8"));
    +
    +			// HEX输出
    +			byte[] hash = md.digest();
    +			Formatter formatter = new Formatter();
    +			for (byte b : hash) {
    +				formatter.format("%02x", b);
    +			}
    +			String hex = formatter.toString();
    +			formatter.close();
    +			return hex;
    +		} catch (Exception e) {
    +			throw new AesException(AesException.ComputeSignatureError);
    +		}
    +	}
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/WXBizMsgCrypt.java b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/WXBizMsgCrypt.java
    new file mode 100644
    index 0000000..0ba71a5
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/WXBizMsgCrypt.java
    @@ -0,0 +1,284 @@
    +package io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes;
    +
    +import io.github.elkan1788.mpsdk4j.core.XmlMsgBuilder;
    +
    +import java.io.InputStream;
    +import java.nio.charset.Charset;
    +import java.util.Arrays;
    +import java.util.Random;
    +
    +import javax.crypto.Cipher;
    +import javax.crypto.spec.IvParameterSpec;
    +import javax.crypto.spec.SecretKeySpec;
    +
    +import org.nutz.repo.Base64;
    +
    +/**
    + * 提供接收和推送给公众平台消息的加解密接口(UTF8编码的字符串).
    + * 
      + *
    1. 第三方回复加密消息给公众平台
    2. + *
    3. 第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。
    4. + *
    + * 说明:异常java.security.InvalidKeyException:illegal Key Size的解决方案 + *
      + *
    1. 在本项目中的jce-patch目录中找到对应JDK版本的文件
    2. + *
    3. 下载后解压,可以看到local_policy.jar和US_export_policy.jar以及readme.txt
    4. + *
    5. 如果安装了JRE,将两个jar文件放到%JRE_HOME%\lib\security目录下覆盖原来的文件
    6. + *
    7. 如果安装了JDK,将两个jar文件放到%JDK_HOME%\jre\lib\security目录下覆盖原来文件
    8. + *
    + */ +public class WXBizMsgCrypt { + + private static Charset CHARSET = Charset.forName("utf-8"); + private byte[] aesKey; + private String token; + private String appId; + private String fromAppId; + + /** + * 构造函数 + * + * @param token + * 公众平台上,开发者设置的token + * @param encodingAesKey + * 公众平台上,开发者设置的EncodingAESKey + * @param appId + * 公众平台appid + * @throws AesException + * 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public WXBizMsgCrypt(String token, String encodingAesKey, String appId) throws AesException { + if (encodingAesKey.length() != 43) { + throw new AesException(AesException.IllegalAesKey); + } + + this.token = token; + this.appId = appId; + aesKey = Base64.decode(encodingAesKey + "="); + } + + // 生成4个字节的网络字节序 + private byte[] getNetworkBytesOrder(int sourceNumber) { + byte[] orderBytes = new byte[4]; + orderBytes[3] = (byte) (sourceNumber & 0xFF); + orderBytes[2] = (byte) (sourceNumber >> 8 & 0xFF); + orderBytes[1] = (byte) (sourceNumber >> 16 & 0xFF); + orderBytes[0] = (byte) (sourceNumber >> 24 & 0xFF); + return orderBytes; + } + + // 还原4个字节的网络字节序 + private int recoverNetworkBytesOrder(byte[] orderBytes) { + int sourceNumber = 0; + for (int i = 0; i < 4; i++) { + sourceNumber <<= 8; + sourceNumber |= orderBytes[i] & 0xff; + } + return sourceNumber; + } + + // 随机生成16位字符串 + private String getRandomStr() { + StringBuffer sb = new StringBuffer(); + Random ran = new Random(); + for (int i = 0; i < 16; i++) { + boolean flag = ran.nextInt(2) % 2 == 0; + if (flag) { + char c = (char) (int) (Math.random() * 26 + 97); + sb.append(c); + } + else { + sb.append(ran.nextInt(10)); + } + } + return sb.toString(); + } + + /** + * 对明文进行加密. + * + * @param text + * 需要加密的明文 + * @return 加密后base64编码的字符串 + * @throws AesException + * aes加密失败 + */ + private String encrypt(String randomStr, String text) throws AesException { + ByteGroup byteCollector = new ByteGroup(); + byte[] randomStrBytes = randomStr.getBytes(CHARSET); + byte[] textBytes = text.getBytes(CHARSET); + byte[] networkBytesOrder = getNetworkBytesOrder(textBytes.length); + byte[] appidBytes = appId.getBytes(CHARSET); + + // randomStr + networkBytesOrder + text + appid + byteCollector.addBytes(randomStrBytes); + byteCollector.addBytes(networkBytesOrder); + byteCollector.addBytes(textBytes); + 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, false); + + return base64Encrypted; + } + catch (Exception e) { + throw new AesException(AesException.EncryptAESError); + } + } + + /** + * 对密文进行解密. + * + * @param text + * 需要解密的密文 + * @return 解密得到的明文 + * @throws AesException + * aes解密失败 + */ + private String decrypt(String text) throws AesException { + 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.decode(text); + + // 解密 + original = cipher.doFinal(encrypted); + } + catch (Exception e) { + throw new AesException(AesException.DecryptAESError); + } + + String xmlContent; + try { + // 去除补位字符 + byte[] bytes = PKCS7Encoder.decode(original); + + // 分离16位随机字符串,网络字节序和AppId + byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); + + int xmlLength = recoverNetworkBytesOrder(networkOrder); + + xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET); + fromAppId = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); + } + catch (Exception e) { + throw new AesException(AesException.IllegalBuffer); + } + + // appid不相同的情况 + if (!fromAppId.equals(appId)) { + throw new AesException(AesException.ValidateAppidError); + } + return xmlContent; + + } + + /** + * 将公众平台回复用户的消息加密打包. + *
      + *
    1. 对要发送的消息进行AES-CBC加密
    2. + *
    3. 生成安全签名
    4. + *
    5. 将消息密文和安全签名打包成xml格式
    6. + *
    + * + * @param replyMsg + * 公众平台待回复用户的消息,xml格式的字符串 + * @param timeStamp + * 时间戳,可以自己生成,也可以用URL参数的timestamp + * @param nonce + * 随机串,可以自己生成,也可以用URL参数的nonce + * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, + * encrypt的xml格式的字符串 + * @throws AesException + * 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String encryptMsg(String replyMsg, String timeStamp, String nonce) throws AesException { + // 加密 + String encrypt = encrypt(getRandomStr(), replyMsg); + + // 生成安全签名 + if ("".equals(timeStamp)) { + timeStamp = Long.toString(System.currentTimeMillis()); + } + + String signature = SHA1.calculate(token, timeStamp, nonce, encrypt); + + // 生成发送的xml + String result = XmlMsgBuilder.create().encrypt(encrypt, signature, timeStamp, nonce); + return result; + } + + /** + * 检验消息的真实性,并且获取解密后的明文. + *
      + *
    1. 利用收到的密文生成安全签名,进行签名验证
    2. + *
    3. 若验证通过,则提取xml中的加密消息
    4. + *
    5. 对消息进行解密
    6. + *
    + * + * @param msgSignature + * 签名串,对应URL参数的msg_signature + * @param timeStamp + * 时间戳,对应URL参数的timestamp + * @param nonce + * 随机串,对应URL参数的nonce + * @param postData + * 密文,对应POST请求的数据 + * @return 解密后的原文 + * @throws AesException + * 执行失败,请查看该异常的错误码和具体的错误信息 + */ + public String decryptMsg(String msgSignature, + String timeStamp, + String nonce, + InputStream postData) throws AesException { + + // 密钥,公众账号的app secret + // 提取密文 + Object[] encrypt = XMLParse.extract(postData); + + // 验证安全签名 + String signature = SHA1.calculate(token, timeStamp, nonce, String.valueOf(encrypt[1])); + + // 和URL中的签名比较是否相等 + if (!signature.equals(msgSignature)) { + throw new AesException(AesException.ValidateSignatureError); + } + + // 解密 + String result = decrypt(String.valueOf(encrypt[1])); + return result; + } + + /** + * 获取加密消息中的APPID[为订阅号提供] + * + * @return APPID + */ + public String getFromAppid() { + return this.fromAppId; + } +} \ No newline at end of file diff --git a/src/main/java/com/qq/weixin/mp/aes/XMLParse.java b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/XMLParse.java similarity index 63% rename from src/main/java/com/qq/weixin/mp/aes/XMLParse.java rename to src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/XMLParse.java index 3709ec2..7495c5e 100644 --- a/src/main/java/com/qq/weixin/mp/aes/XMLParse.java +++ b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/XMLParse.java @@ -1,16 +1,16 @@ -package com.qq.weixin.mp.aes; +package io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; -import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.HashMap; import java.util.Map; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + /** * 提供提取消息格式中的密文及生成回复消息格式的接口. * @@ -22,39 +22,45 @@ class XMLParse { /** * 提取出xml数据包中的加密消息 * - * @param xmltext 待提取的xml字符串 + * @param is + * 待提取的xml输入流 * @return 提取出的加密消息字符串 * @throws com.qq.weixin.mp.aes.AesException */ - public static Object[] extract(String xmltext) throws AesException { + public static Object[] extract(InputStream is) throws AesException { try { SAXParserFactory sax = SAXParserFactory.newInstance(); SAXParser parser = sax.newSAXParser(); - final Map map = new HashMap<>(); - DefaultHandler handler = new DefaultHandler(){ - private Object[] result = new Object[3]; + final Map map = new HashMap(); + DefaultHandler handler = new DefaultHandler() { + private Object[] result = new Object[3]; private String temp; @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + public void startElement(String uri, + String localName, + String qName, + Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); } @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - if (qName.equalsIgnoreCase("Encrypt")){ + public void endElement(String uri, String localName, String qName) + throws SAXException { + if (qName.equalsIgnoreCase("Encrypt")) { result[1] = temp; - return ; + return; } - if (qName.equalsIgnoreCase("ToUserName")){ + if (qName.equalsIgnoreCase("ToUserName")) { result[2] = temp; - return ; + return; } if (qName.equalsIgnoreCase("xml")) { result[0] = 0; map.put("result", result); + return; } } @@ -64,10 +70,10 @@ public void characters(char[] ch, int start, int length) throws SAXException { } }; - InputStream is = new ByteArrayInputStream(xmltext.getBytes()); parser.parse(is, handler); return map.get("result"); - } catch (Exception e) { + } + catch (Exception e) { throw new AesException(AesException.ParseXmlError); } diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/package-info.java new file mode 100644 index 0000000..427a6e2 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/package-info.java @@ -0,0 +1,4 @@ +/** + * 腾讯官方消息AES加密工具 + */ +package io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes; \ No newline at end of file diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/repo/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/repo/package-info.java new file mode 100644 index 0000000..e7d025b --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/repo/package-info.java @@ -0,0 +1,7 @@ +/** + * 第三方依赖轻量引入 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +package io.github.elkan1788.mpsdk4j.repo; \ No newline at end of file diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/session/AccessTokenMemoryCache.java b/src/main/java/io/github/elkan1788/mpsdk4j/session/AccessTokenMemoryCache.java new file mode 100644 index 0000000..f211a65 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/session/AccessTokenMemoryCache.java @@ -0,0 +1,33 @@ +package io.github.elkan1788.mpsdk4j.session; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.github.elkan1788.mpsdk4j.vo.api.AccessToken; + +/** + * 本地缓存AccessToken信息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class AccessTokenMemoryCache implements MemoryCache { + + private Map ats = new ConcurrentHashMap(); + + @Override + public AccessToken get(String mpId) { + return ats.get(mpId); + } + + @Override + public void set(String mpId, AccessToken object) { + ats.put(mpId, object); + } + + @Override + public void remove(String mpId) { + ats.remove(mpId); + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/session/JSTicketMemoryCache.java b/src/main/java/io/github/elkan1788/mpsdk4j/session/JSTicketMemoryCache.java new file mode 100644 index 0000000..1547180 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/session/JSTicketMemoryCache.java @@ -0,0 +1,33 @@ +package io.github.elkan1788.mpsdk4j.session; + +import io.github.elkan1788.mpsdk4j.vo.api.JSTicket; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 本地缓存JSTicket信息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class JSTicketMemoryCache implements MemoryCache { + + private Map jsts = new ConcurrentHashMap(); + + @Override + public JSTicket get(String key) { + return jsts.get(key); + } + + @Override + public void set(String key, JSTicket jsTicket) { + jsts.put(key, jsTicket); + } + + @Override + public void remove(String key) { + jsts.remove(key); + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/session/MemoryCache.java b/src/main/java/io/github/elkan1788/mpsdk4j/session/MemoryCache.java new file mode 100644 index 0000000..15b68b2 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/session/MemoryCache.java @@ -0,0 +1,17 @@ +package io.github.elkan1788.mpsdk4j.session; + +/** + * 本地缓存接口设计 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public interface MemoryCache { + + T get(String key); + + void set(String key, T object); + + void remove(String key); + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/session/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/session/package-info.java new file mode 100644 index 0000000..8dcb346 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/session/package-info.java @@ -0,0 +1,7 @@ +/** + * 用于存储会话数据(主要是access_token,jsticket) + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +package io.github.elkan1788.mpsdk4j.session; \ No newline at end of file diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/util/ConfigReader.java b/src/main/java/io/github/elkan1788/mpsdk4j/util/ConfigReader.java new file mode 100644 index 0000000..e055953 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/util/ConfigReader.java @@ -0,0 +1,105 @@ +package io.github.elkan1788.mpsdk4j.util; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +import org.nutz.lang.Lang; +import org.nutz.lang.Streams; +import org.nutz.lang.Strings; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * 读取配置文件 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class ConfigReader { + + private static final Log log = Logs.get(); + + private Map confs = new LinkedHashMap(); + + public ConfigReader(String path) { + this.load(path); + } + + protected synchronized void load(String path) { + if (log.isDebugEnabled()) { + log.debugf("Loading config file[%s].", path); + } + InputStream is = null; + BufferedReader br = null; + Properties p = new Properties(); + try { + is = this.getClass().getResourceAsStream(path); + br = new BufferedReader(new InputStreamReader(is)); + p.load(br); + } + catch (Exception e) { + throw Lang.wrapThrow(e); + } + finally { + Streams.safeClose(is); + Streams.safeClose(br); + } + putAll(p); + } + + @SuppressWarnings({ + "unchecked", "rawtypes" + }) + protected void putAll(Map p) { + confs.putAll(p); + } + + public synchronized void clear() { + confs.clear(); + } + + protected void valid(String key) { + if (Strings.isBlank(key)) { + throw new NullPointerException("Key can't not be NULL or empty value."); + } + } + + public void put(String key, String value) { + confs.put(key, value); + } + + public List keys() { + return new ArrayList(confs.keySet()); + } + + public Collection values() { + return confs.values(); + } + + public String get(String key) { + valid(key); + return confs.get(key); + } + + public int getInt(String key) { + valid(key); + return Integer.parseInt(get(key)); + } + + public long getLong(String key) { + valid(key); + return Long.valueOf(get(key)).longValue(); + } + + public boolean getBoolean(String key) { + valid(key); + return Boolean.valueOf(get(key)); + } +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/util/HttpTool.java b/src/main/java/io/github/elkan1788/mpsdk4j/util/HttpTool.java new file mode 100644 index 0000000..187f766 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/util/HttpTool.java @@ -0,0 +1,156 @@ +package io.github.elkan1788.mpsdk4j.util; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; + +import org.nutz.http.Http; +import org.nutz.http.Request; +import org.nutz.http.Request.METHOD; +import org.nutz.http.Response; +import org.nutz.http.Sender; +import org.nutz.http.sender.FilePostSender; +import org.nutz.lang.Lang; +import org.nutz.lang.Streams; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * Http工具包,用于访问API,上传或下载微信素材 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +// TODO Http.disableJvmHttpsCheck(); +public class HttpTool { + + private static final Log log = Logs.get(); + + private static final int CONNECT_TIME_OUT = 5 * 1000; + private static final String FILE_NAME_FLAG = "filename="; + + public static String get(String url) { + if (log.isDebugEnabled()) { + log.debugf("Request url: %s, default timeout: %d", url, CONNECT_TIME_OUT); + } + + try { + Response resp = Http.get(url, CONNECT_TIME_OUT); + if (resp.isOK()) { + String content = resp.getContent("UTF-8"); + if (log.isInfoEnabled()) { + log.infof("GET Request success. Response content: %s", content); + } + return content; + } + + throw Lang.wrapThrow(new RuntimeException(String.format("Get request [%s] failed. status: %d", + url, + resp.getStatus()))); + } + catch (Exception e) { + throw Lang.wrapThrow(e); + } + } + + public static String post(String url, String body) { + if (log.isDebugEnabled()) { + log.debugf("Request url: %s, post data: %s, default timeout: %d", + url, + body, + CONNECT_TIME_OUT); + } + + try { + Request req = Request.create(url, METHOD.POST); + req.setEnc("UTF-8"); + req.setData(body); + Response resp = Sender.create(req, CONNECT_TIME_OUT).send(); + if (resp.isOK()) { + String content = resp.getContent(); + if (log.isInfoEnabled()) { + log.infof("POST Request success. Response content: %s", content); + } + return content; + } + + throw Lang.wrapThrow(new RuntimeException(String.format("Post request [%s] failed. status: %d", + url, + resp.getStatus()))); + } + catch (Exception e) { + throw Lang.wrapThrow(e); + } + } + + public static String upload(String url, File file) { + if (log.isDebugEnabled()) { + log.debugf("Upload url: %s, file name: %s, default timeout: %d", + url, + file.getName(), + CONNECT_TIME_OUT); + } + + try { + Request req = Request.create(url, METHOD.POST); + req.getParams().put("media", file); + Response resp = new FilePostSender(req).send(); + if (resp.isOK()) { + String content = resp.getContent(); + return content; + } + + throw Lang.wrapThrow(new RuntimeException(String.format("Upload file [%s] failed. status: %d", + url, + resp.getStatus()))); + } + catch (Exception e) { + throw Lang.wrapThrow(e); + } + } + + public static Object download(String url) { + if (log.isDebugEnabled()) { + log.debugf("Upload url: %s, default timeout: %d", url, CONNECT_TIME_OUT); + } + + try { + Response resp = Http.get(url); + if (resp.isOK()) { + String cd = resp.getHeader().get("Content-disposition"); + if (log.isInfoEnabled()) { + log.infof("Get download file info: %s", cd); + } + + if (Lang.isEmpty(cd)) { + return resp.getContent(); + } + + cd = cd.substring(cd.indexOf(FILE_NAME_FLAG) + FILE_NAME_FLAG.length()); + String tmp = cd.startsWith("\"") ? cd.substring(1) : cd; + tmp = tmp.endsWith("\"") ? cd.replace("\"", "") : cd; + String filename = tmp.substring(0, tmp.lastIndexOf(".")); + String fileext = tmp.substring(tmp.lastIndexOf(".")); + if (log.isInfoEnabled()) { + log.infof("Download file name: %s", filename); + log.infof("Download file ext: %s", fileext); + } + + File tmpfile = File.createTempFile(filename, fileext); + InputStream is = resp.getStream(); + OutputStream os = new FileOutputStream(tmpfile); + Streams.writeAndClose(os, is); + return tmpfile; + } + + throw Lang.wrapThrow(new RuntimeException(String.format("Download file [%s] failed. status: %d, content: %s", + url, + resp.getStatus(), + resp.getContent()))); + } + catch (Exception e) { + throw Lang.wrapThrow(e); + } + } +} diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/util/StreamTool.java b/src/main/java/io/github/elkan1788/mpsdk4j/util/StreamTool.java similarity index 75% rename from src/main/java/org/elkan1788/osc/weixin/mp/util/StreamTool.java rename to src/main/java/io/github/elkan1788/mpsdk4j/util/StreamTool.java index 594f98f..9def6ff 100644 --- a/src/main/java/org/elkan1788/osc/weixin/mp/util/StreamTool.java +++ b/src/main/java/io/github/elkan1788/mpsdk4j/util/StreamTool.java @@ -1,28 +1,28 @@ -package org.elkan1788.osc.weixin.mp.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +package io.github.elkan1788.mpsdk4j.util; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import org.nutz.log.Log; +import org.nutz.log.Logs; + /** * 输入流与字符串处理工具 * * @author 凡梦星尘(elkan1788@gmail.com) - * @since 2014/11/8 - * @version 1.0.0 + * @since 1.0 */ public class StreamTool { - private static final Logger log = LoggerFactory.getLogger(StreamTool.class); + private static final Log log = Logs.get(); /** * 将字符串转换成输入流 * - * @param str 字符串 + * @param str + * 字符串 * @return 输入流 */ public static InputStream toStream(String str) { @@ -30,7 +30,8 @@ public static InputStream toStream(String str) { try { // UTF-8 解决网络传输中的字符集问题 stream = new ByteArrayInputStream(str.getBytes("UTF-8")); - } catch (UnsupportedEncodingException e) { + } + catch (UnsupportedEncodingException e) { log.error("转换输出Stream异常,不支持的字符集!!!"); log.error(e.getLocalizedMessage(), e); } @@ -40,7 +41,8 @@ public static InputStream toStream(String str) { /** * 将输入流转换成字符串 * - * @param is 输入流 + * @param is + * 输入流 * @return 字符串 * @throws IOException */ @@ -49,10 +51,11 @@ public static String toString(InputStream is) { byte[] b = new byte[1024]; try { - for (int n; (n = is.read(b)) != -1; ) { + for (int n; (n = is.read(b)) != -1;) { str.append(new String(b, 0, n)); } - } catch (IOException e) { + } + catch (IOException e) { log.error("读取输入流出现异常!!!"); log.error(e.getLocalizedMessage(), e); } diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/util/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/util/package-info.java new file mode 100644 index 0000000..cf9a886 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/util/package-info.java @@ -0,0 +1,7 @@ +/** + * 常用的工具类 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +package io.github.elkan1788.mpsdk4j.util; \ No newline at end of file diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/ApiResult.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/ApiResult.java new file mode 100644 index 0000000..72b5f03 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/ApiResult.java @@ -0,0 +1,79 @@ +package io.github.elkan1788.mpsdk4j.vo; + +import java.util.Map; + +import org.nutz.json.Json; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +import io.github.elkan1788.mpsdk4j.util.ConfigReader; + +/** + * 封装微信api返回结果, 输出实体类 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@SuppressWarnings("unchecked") +public class ApiResult { + + private static final Log log = Logs.get(); + + private static ConfigReader _cr; + + static { + _cr = new ConfigReader("/ErrorCode.properties"); + } + + private Map content; + private String json; + private Integer errCode; + private String errMsg; + private String errCNMsg; + + public ApiResult(String json) { + this.json = json; + this.content = Json.fromJson(Map.class, json); + this.errCode = (Integer) this.content.get("errcode"); + this.errMsg = (String) this.content.get("errmsg"); + this.errCNMsg = this.errCode == null ? "请求成功." : _cr.get(String.valueOf(this.errCode)); + + if (log.isInfoEnabled()) { + log.infof("Wechat api result: %s", json); + log.infof("%s", this.getErrCNMsg()); + } + } + + public static ApiResult create(String json) { + return new ApiResult(json); + } + + public Object get(String key) { + return content.get(key); + } + + public Map getContent() { + return content; + } + + public String getJson() { + return json; + } + + public Integer getErrCode() { + return this.errCode; + } + + public String getErrMsg() { + return this.errMsg == null ? "Unknow Error!" : this.errMsg; + } + + public String getErrCNMsg() { + return this.errCNMsg == null ? "未知错误!" : this.errCNMsg; + } + + public boolean isSuccess() { + return (this.errCode == null || this.errCode.intValue() == 0); + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/MPAccount.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/MPAccount.java new file mode 100644 index 0000000..7f32beb --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/MPAccount.java @@ -0,0 +1,138 @@ +package io.github.elkan1788.mpsdk4j.vo; + +/** + * 微信公众号信息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class MPAccount { + + /** + * 公众号原始ID + */ + private String mpId; + + /** + * 公众号昵称 + */ + private String nickName; + + /** + * 应用Id + */ + private String appId; + + /** + * 应用密钥 + */ + private String appSecret; + + /** + * 令牌 + */ + private String token; + + /** + * AES安全加密密钥 + */ + private String AESKey; + + /** + * 公众号类型 + * D:订阅号 + * E:企业号 + * S:服务号 + */ + private String mpType; + + /** + * 是否认证 + */ + private boolean pass; + + public String getMpId() { + return mpId; + } + + public void setMpId(String mpId) { + this.mpId = mpId; + } + + public String getNickName() { + return nickName; + } + + public void setNickName(String nickName) { + this.nickName = nickName; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getAppSecret() { + return appSecret; + } + + public void setAppSecret(String appSecret) { + this.appSecret = appSecret; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getAESKey() { + return AESKey; + } + + public void setAESKey(String aESKey) { + AESKey = aESKey; + } + + public String getMpType() { + return mpType; + } + + public void setMpType(String mpType) { + this.mpType = mpType; + } + + public boolean isPass() { + return pass; + } + + public void setPass(boolean pass) { + this.pass = pass; + } + + @Override + public String toString() { + return "MPAccount [mpId=" + + mpId + + ", nickName=" + + nickName + + ", appId=" + + appId + + ", appSecret=" + + appSecret + + ", token=" + + token + + ", AESKey=" + + AESKey + + ", mpType=" + + mpType + + ", pass=" + + pass + + "]"; + } +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/AccessToken.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/AccessToken.java new file mode 100644 index 0000000..2ecad93 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/AccessToken.java @@ -0,0 +1,53 @@ +package io.github.elkan1788.mpsdk4j.vo.api; + +import org.nutz.json.JsonField; +import org.nutz.lang.Lang; + +/** + * 微信API凭证 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class AccessToken { + + /** + * 获取到的凭证 + */ + @JsonField(value = "access_token") + private String accessToken; + + /** + * 凭证有效时间,单位:秒 + */ + @JsonField(value = "expires_in") + private long expiresIn; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public long getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(long expiresIn) { + this.expiresIn = (expiresIn - 30) * 1000; + } + + public boolean isAvailable() { + if (!Lang.isEmpty(accessToken) || this.expiresIn >= System.currentTimeMillis()) { + return true; + } + return false; + } + + @Override + public String toString() { + return "AccessToken [accessToken=" + accessToken + ", expiresIn=" + expiresIn + "]"; + } +} diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/FollowList.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/FollowList.java similarity index 58% rename from src/main/java/org/elkan1788/osc/weixin/mp/vo/FollowList.java rename to src/main/java/io/github/elkan1788/mpsdk4j/vo/api/FollowList.java index db2c934..310682d 100644 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/FollowList.java +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/FollowList.java @@ -1,39 +1,34 @@ -package org.elkan1788.osc.weixin.mp.vo; - -import com.alibaba.fastjson.annotation.JSONField; +package io.github.elkan1788.mpsdk4j.vo.api; import java.util.ArrayList; import java.util.List; +import org.nutz.json.JsonField; + /** * 关注者列表 * - * @author 凡梦星尘(senhuili@mdc.cn) - * @since 2014/11/8 - * @version 1.0.0 + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 */ public class FollowList { - /** * 关注该公众账号的总用户数 */ - private int total; - + private int total; /** - * 拉取的OPENID个数,最大值为10000 + * 拉取的OPENID个数,最大值为10000 */ - private int count; - + private int count; /** - * 列表数据,OPENID的列表 + * 列表数据,OPENID的列表 */ - private List openIds = new ArrayList<>(); - + private List openIds = new ArrayList(); /** * 拉取列表的后一个用户的OPENID */ - @JSONField(name = "next_openid") - private String nextOpenId; + @JsonField(value = "next_openid") + private String nextOpenId; public int getTotal() { return total; @@ -69,11 +64,15 @@ public void setNextOpenId(String nextOpenId) { @Override public String toString() { - return "FollowList{" + - "total=" + total + - ", count=" + count + - ", openIds=" + (openIds.size()) + - ", nextOpenId='" + nextOpenId + '\'' + - '}'; + return "FollowList [total=" + + total + + ", count=" + + count + + ", openIds=" + + openIds + + ", nextOpenId=" + + nextOpenId + + "]"; } + } diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Follower.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Follower.java similarity index 50% rename from src/main/java/org/elkan1788/osc/weixin/mp/vo/Follower.java rename to src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Follower.java index 30fc60b..1c0aebc 100644 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Follower.java +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Follower.java @@ -1,73 +1,69 @@ -package org.elkan1788.osc.weixin.mp.vo; +package io.github.elkan1788.mpsdk4j.vo.api; -import com.alibaba.fastjson.annotation.JSONField; +import org.nutz.json.JsonField; /** - * 微信订阅者信息 - * - * @author 凡梦星尘(senhuili@mdc.cn) - * @since 2014/11/8 - * @version 1.0.0 + * 微信用户信息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 */ public class Follower { - /** * 是否订阅(0 退订, 1 订阅) */ - private int subscribe; - + private int subscribe; /** * 关注公众号唯一标识 */ - private String openId; - + private String openid; /** * 微信昵称 */ - private String nickName; - + private String nickname; /** * 性别(1 男, 2 女, 0 未知) */ - private int sex; - + private int sex; /** * 用户所在城市 */ - private String city; - + private String city; /** * 用户所在国家 */ - private String country; - + private String country; /** * 用户所在省份 */ - private String province; - + private String province; /** * 用户的语言,简体中文为zh_CN */ - private String language; - + private String language; /** * 用户头像,最后一个数值代表正方形头像大小
    * (有0、46、64、96、132数值可选,0代表640*640正方
    * 形头像),用户没有头像时该项为空 */ - private String headImgUrl; - + private String headimgurl; /** * 用户关注时间,为时间戳。如果用户曾多次关注,则取最后关注时间 */ - @JSONField(name = "subscribe_time") - private long subscribeTime; - + @JsonField(value = "subscribe_time") + private long subscribeTime; /** * 只有在用户将公众号绑定到微信开放平台帐号后,才会出现该字段 */ - private String unionid; + private String unionid; + /** + * 粉丝的备注 + */ + private String remark; + /** + * 用户所在的分组ID + */ + private int groupid; public int getSubscribe() { return subscribe; @@ -77,20 +73,20 @@ public void setSubscribe(int subscribe) { this.subscribe = subscribe; } - public String getOpenId() { - return openId; + public String getOpenid() { + return openid; } - public void setOpenId(String openId) { - this.openId = openId; + public void setOpenid(String openid) { + this.openid = openid; } - public String getNickName() { - return nickName; + public String getNickname() { + return nickname; } - public void setNickName(String nickName) { - this.nickName = nickName; + public void setNickname(String nickname) { + this.nickname = nickname; } public int getSex() { @@ -133,12 +129,12 @@ public void setLanguage(String language) { this.language = language; } - public String getHeadImgUrl() { - return headImgUrl; + public String getHeadimgurl() { + return headimgurl; } - public void setHeadImgUrl(String headImgUrl) { - this.headImgUrl = headImgUrl; + public void setHeadimgurl(String headimgurl) { + this.headimgurl = headimgurl; } public long getSubscribeTime() { @@ -157,20 +153,51 @@ public void setUnionid(String unionid) { this.unionid = unionid; } + public String getRemark() { + return remark; + } + + public void setRemark(String remark) { + this.remark = remark; + } + + public int getGroupid() { + return groupid; + } + + public void setGroupid(int groupid) { + this.groupid = groupid; + } + @Override public String toString() { - return "Follower{" + - "subscribe=" + subscribe + - ", openId='" + openId + '\'' + - ", nickName='" + nickName + '\'' + - ", sex=" + sex + - ", city='" + city + '\'' + - ", country='" + country + '\'' + - ", province='" + province + '\'' + - ", language='" + language + '\'' + - ", headImgUrl='" + headImgUrl + '\'' + - ", subscribeTime=" + subscribeTime + - ", unionid='" + unionid + '\'' + - '}'; + return "Follower [subscribe=" + + subscribe + + ", openid=" + + openid + + ", nickname=" + + nickname + + ", sex=" + + sex + + ", city=" + + city + + ", country=" + + country + + ", province=" + + province + + ", language=" + + language + + ", headimgurl=" + + headimgurl + + ", subscribeTime=" + + subscribeTime + + ", unionid=" + + unionid + + ", remark=" + + remark + + ", groupid=" + + groupid + + "]"; } + } diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Follower2.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Follower2.java new file mode 100644 index 0000000..a4b116a --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Follower2.java @@ -0,0 +1,56 @@ +/** + * @author senhui.li + */ +package io.github.elkan1788.mpsdk4j.vo.api; + +/** + * 批量获取用户信息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class Follower2 { + + /** + * 用户的标识 + */ + private String openid; + + /** + * 国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语,默认为zh-CN + */ + private String lang; + + public Follower2() {} + + public Follower2(String openid) { + this(openid, "zh_CN"); + } + + public Follower2(String openid, String lang) { + this.openid = openid; + this.lang = lang; + } + + public String getOpenid() { + return openid; + } + + public void setOpenid(String openid) { + this.openid = openid; + } + + public String getLang() { + return lang; + } + + public void setLang(String lang) { + this.lang = lang; + } + + @Override + public String toString() { + return "Follower2 [openid=" + openid + ", lang=" + lang + "]"; + } + +} diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Group.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Groups.java similarity index 63% rename from src/main/java/org/elkan1788/osc/weixin/mp/vo/Group.java rename to src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Groups.java index 6019329..f4413b5 100644 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Group.java +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Groups.java @@ -1,26 +1,23 @@ -package org.elkan1788.osc.weixin.mp.vo; +package io.github.elkan1788.mpsdk4j.vo.api; /** - * 微信分组信息 - * + * 用户分组 + * * @author 凡梦星尘(elkan1788@gmail.com) - * @since 2014/11/10 - * @version 1.0.0 + * @since 2.0 */ -public class Group { +public class Groups { /** - * 分组唯一ID + * 分组id,由微信分配 */ private int id; - /** - * 分组名称 + * 分组名字,UTF8编码 */ private String name; - /** - * 用户统计 + * 分组内用户数量 */ private int count; @@ -50,10 +47,6 @@ public void setCount(int count) { @Override public String toString() { - return "Group{" + - "id=" + id + - ", name='" + name + '\'' + - ", count=" + count + - '}'; + return "Groups [id=" + id + ", name=" + name + ", count=" + count + "]"; } } diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/JSTicket.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/JSTicket.java new file mode 100644 index 0000000..0dff803 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/JSTicket.java @@ -0,0 +1,53 @@ +package io.github.elkan1788.mpsdk4j.vo.api; + +import org.nutz.json.JsonField; +import org.nutz.lang.Lang; + +/** + * 微信JSSDK凭证 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class JSTicket { + + /** + * 调用微信JS接口的临时票据 + */ + private String ticket; + + /** + * 凭证有效时间,单位:秒 + */ + @JsonField(value = "expires_in") + private long expiresIn; + + public String getTicket() { + return ticket; + } + + public void setTicket(String ticket) { + this.ticket = ticket; + } + + public long getExpiresIn() { + return expiresIn; + } + + public void setExpiresIn(long expiresIn) { + this.expiresIn = (expiresIn - 30) * 1000; + } + + public boolean isAvailable() { + if (!Lang.isEmpty(ticket) || this.expiresIn >= System.currentTimeMillis()) { + return true; + } + return false; + } + + @Override + public String toString() { + return "JSTicket [ticket=" + ticket + ", expiresIn=" + expiresIn + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Media.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Media.java new file mode 100644 index 0000000..d399349 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Media.java @@ -0,0 +1,67 @@ +package io.github.elkan1788.mpsdk4j.vo.api; + +import org.nutz.json.JsonField; + +/** + * 多媒体文件 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class Media { + + /** + * 媒体文件类型,分别有 + * + *
    +     * 图片(image)
    +     * 

    + * 语音(voice) + *

    + * 视频(video)和 + *

    + * 缩略图(thumb,主要用于视频与音乐格式的缩略图) + */ + private String type; + + /** + * 媒体文件上传后,获取时的唯一标识 + */ + @JsonField(value = "media_id") + private String mediaId; + + /** + * 媒体文件上传时间戳 + */ + @JsonField(value = "created_at") + 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; + } + + @Override + public String toString() { + return "Media [type=" + type + ", mediaId=" + mediaId + ", createdAt=" + createdAt + "]"; + } +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Menu.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Menu.java new file mode 100644 index 0000000..1367bed --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Menu.java @@ -0,0 +1,158 @@ +/** + * @author senhui.li + */ +package io.github.elkan1788.mpsdk4j.vo.api; + +import java.util.List; + +import org.nutz.json.JsonField; + +import io.github.elkan1788.mpsdk4j.common.EventType; + +/** + * 自定义菜单 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class Menu { + + /** + * 菜单标题,不超过16个字节,子菜单不超过40个字节 + */ + private String name; + + /** + * 菜单的响应动作类型 + *

    + * click: 点击推事件 + *

    + * view: 跳转URL + *

    + * scancode_push: 扫码推事件 + *

    + * scancode_waitmsg: 扫码推事件 + *

    + * pic_sysphoto: 弹出系统拍照发图 + *

    + * pic_photo_or_album: 弹出拍照或者相册发 + *

    + * pic_weixin: 弹出微信相册发图器 + *

    + * location_select: 弹出地理位置选择器 + *

    + * media_id: 下发消息(除文本消息) + *

    + * view_limited: 跳转图文消息URL + *

    + */ + private String type; + + /** + * 点击类型菜单KEY值,用于消息接口推送,不超过128字节 + */ + private String key; + + /** + * 网页链接,用户点击菜单可打开链接,不超过256字节 + */ + private String url; + + /** + * 二级菜单 + */ + @JsonField(value = "sub_button") + private List

    subButtons; + + /** + * 构造函数 + */ + public Menu() {} + + /** + * 一级菜单构造函数 + * + * @param name + * 菜单名字 + */ + public Menu(String name) { + setName(name); + } + + /** + * 二级菜单构造函数 + * + * @param name + * 菜单名称 + * @param type + * 菜单类型 + * @param val + * KEY值/URL + */ + public Menu(String name, String type, String val) { + setName(name); + setType(type); + if (EventType.VIEW.name().equals(type)) { + setUrl(val); + } + else { + setKey(val); + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type.toLowerCase(); + } + + 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 getSubButtons() { + return subButtons; + } + + public void setSubButtons(List subButtons) { + this.subButtons = subButtons; + } + + @Override + public String toString() { + return "Menu [name=" + + name + + ", type=" + + type + + ", key=" + + key + + ", url=" + + url + + ", subButtons=" + + subButtons + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/QRTicket.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/QRTicket.java new file mode 100644 index 0000000..33d4763 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/QRTicket.java @@ -0,0 +1,63 @@ +package io.github.elkan1788.mpsdk4j.vo.api; + +import org.nutz.json.JsonField; + +/** + * 二维码Ticket + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class QRTicket { + + /** + * 获取的二维码ticket,凭借此ticket可以在有效时间内换取二维码 + */ + private String ticket; + + /** + * 维码的有效时间,以秒为单位,最大不超过1800 + */ + @JsonField(value = "expire_seconds") + private int expireSeconds; + + /** + * 二维码图片解析后的地址,开发者可根据该地址自行生成需要的二维码图片 + */ + private String url; + + public String getTicket() { + return ticket; + } + + public void setTicket(String ticket) { + this.ticket = ticket; + } + + public int getExpireSeconds() { + return expireSeconds; + } + + public void setExpireSeconds(int expireSeconds) { + this.expireSeconds = expireSeconds; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Override + public String toString() { + return "QRTicket [ticket=" + + ticket + + ", expireSeconds=" + + expireSeconds + + ", url=" + + url + + "]"; + } +} diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Template.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Template.java similarity index 68% rename from src/main/java/org/elkan1788/osc/weixin/mp/vo/Template.java rename to src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Template.java index 37df9c0..6c5aa85 100644 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Template.java +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/Template.java @@ -1,37 +1,54 @@ -package org.elkan1788.osc.weixin.mp.vo; +package io.github.elkan1788.mpsdk4j.vo.api; /** * 微信模板消息 - * - * @author 凡梦星尘(senhuili@mdc.cn) - * @version 1.0.0 - * @since 2014/11/7 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 */ public class Template { - /** - * 默认颜色(蓝色) - */ - private String DEFAULT_COLOR = "#119EF3"; - /** * 模板字段名称 */ private String name; - /** * 显示颜色 */ private String color; - /** * 显示数据 */ private String value; public Template() { + + } + + /** + * 默认颜色(蓝色) + * + * @param name + * 参数名称 + * @param value + * 参数值 + */ + public Template(String name, String value) { + this.name = name; + this.color = "#119EF3"; + this.value = value; } + /** + * 带参数构造方法 + * + * @param name + * 参数名称 + * @param color + * 字体颜色 + * @param value + * 参数值 + */ public Template(String name, String color, String value) { this.name = name; this.color = color; diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/package-info.java new file mode 100644 index 0000000..ef3cf24 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/api/package-info.java @@ -0,0 +1,7 @@ +/** + * 微信API实体类 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +package io.github.elkan1788.mpsdk4j.vo.api; \ No newline at end of file diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/BasicEvent.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/BasicEvent.java new file mode 100644 index 0000000..f12d082 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/BasicEvent.java @@ -0,0 +1,98 @@ +package io.github.elkan1788.mpsdk4j.vo.event; + +import java.util.Map; + +/** + * 事件消息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class BasicEvent { + + /** + * 微信公众号Id/OpenId + */ + protected String toUserName; + /** + * OpenId/微信公众号Id + */ + protected String fromUserName; + /** + * 消息创建时间 (整型) + */ + protected int createTime; + /** + * 消息类型: event + */ + protected String msgType; + /** + * 事件类型:subscribe(订阅),unsubscribe(取消订阅)... + */ + protected String event; + /** + * 事件KEY值:扫码(二维码场景Id),菜单值... + */ + protected String eventKey; + + public BasicEvent() { + this.msgType = "event"; + } + + public BasicEvent(Map values) { + this.fromUserName = values.get("fromUserName"); + this.toUserName = values.get("toUserName"); + this.createTime = Integer.parseInt(values.get("createTime")); + this.msgType = "event"; + this.event = values.get("event"); + this.eventKey = values.get("eventKey"); + } + + 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 int getCreateTime() { + return createTime; + } + + public void setCreateTime(int createTime) { + this.createTime = createTime; + } + + public String getMsgType() { + return msgType; + } + + public void setMsgType(String msgType) { + this.msgType = msgType; + } + + 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; + } +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/LocationEvent.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/LocationEvent.java new file mode 100644 index 0000000..dfd2dae --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/LocationEvent.java @@ -0,0 +1,83 @@ +package io.github.elkan1788.mpsdk4j.vo.event; + +import java.util.Map; + +/** + * 地理位置事件 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class LocationEvent extends BasicEvent { + + /** + * 地理位置纬度 + */ + private String latitude; + /** + * 地理位置经度 + */ + private String longitude; + /** + * 地理位置精度 + */ + private String precision; + + public LocationEvent() { + this.event = "LOCATION"; + } + + public LocationEvent(Map values) { + super(values); + this.latitude = values.get("latitude"); + this.longitude = values.get("longitude"); + this.precision = values.get("precision"); + this.event = "LOCATION"; + } + + public String getLatitude() { + return latitude; + } + + public void setLatitude(String latitude) { + this.latitude = latitude; + } + + public String getLongitude() { + return longitude; + } + + public void setLongitude(String longitude) { + this.longitude = longitude; + } + + public String getPrecision() { + return precision; + } + + public void setPrecision(String precision) { + this.precision = precision; + } + + @ Override + public String toString() { + return "LocationEvent [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", event=" + + event + + ", latitude=" + + latitude + + ", longitude=" + + longitude + + ", precision=" + + precision + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/MenuEvent.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/MenuEvent.java new file mode 100644 index 0000000..91c402b --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/MenuEvent.java @@ -0,0 +1,38 @@ +package io.github.elkan1788.mpsdk4j.vo.event; + +import java.util.Map; + +/** + * 菜单事件 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class MenuEvent extends BasicEvent { + + public MenuEvent() { + super(); + } + + public MenuEvent(Map values) { + super(values); + } + + @ Override + public String toString() { + return "MenuEvent [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", event=" + + event + + ", eventKey=" + + eventKey + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/PicItem.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/PicItem.java new file mode 100644 index 0000000..d85fb98 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/PicItem.java @@ -0,0 +1,36 @@ +package io.github.elkan1788.mpsdk4j.vo.event; + +/** + * 相片MD5信息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class PicItem { + + /** + * 图片的MD5值,开发者若需要,可用于验证接收到图片 + */ + private String picMd5Sum; + + public PicItem() { + super(); + } + + public PicItem(String picMd5Sum) { + this.picMd5Sum = picMd5Sum; + } + + public String getPicMd5Sum() { + return picMd5Sum; + } + + public void setPicMd5Sum(String picMd5Sum) { + this.picMd5Sum = picMd5Sum; + } + + @ Override + public String toString() { + return "PicItem [picMd5Sum=" + picMd5Sum + "]"; + } +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/ScanCodeEvent.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/ScanCodeEvent.java new file mode 100644 index 0000000..0b6bb43 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/ScanCodeEvent.java @@ -0,0 +1,69 @@ +package io.github.elkan1788.mpsdk4j.vo.event; + +import java.util.Map; + +/** + * 扫码事件 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class ScanCodeEvent extends BasicEvent { + + /** + * 扫描类型,一般是qrcode + */ + private String scanType; + /** + * 扫描结果,即二维码对应的字符串信息 + */ + private String scanResult; + + public ScanCodeEvent() { + super(); + } + + public ScanCodeEvent(Map values) { + super(values); + this.scanType = values.get("scanType"); + this.scanResult = values.get("scanResult"); + } + + public String getScanType() { + return scanType; + } + + public void setScanType(String scanType) { + this.scanType = scanType; + } + + public String getScanResult() { + return scanResult; + } + + public void setScanResult(String scanResult) { + this.scanResult = scanResult; + } + + @ Override + public String toString() { + return "ScanCodeEvent [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", event=" + + event + + ", eventKey=" + + eventKey + + ", scanType=" + + scanType + + ", scanResult=" + + scanResult + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/ScanEvent.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/ScanEvent.java new file mode 100644 index 0000000..8a0f426 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/ScanEvent.java @@ -0,0 +1,53 @@ +package io.github.elkan1788.mpsdk4j.vo.event; + +import java.util.Map; + +/** + * 扫码事件 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class ScanEvent extends BasicEvent { + + /** + * 二维码的ticket,可用来换取二维码图片 + */ + private String ticket; + + public ScanEvent() { + super(); + } + + public ScanEvent(Map values) { + super(values); + } + + public String getTicket() { + return ticket; + } + + public void setTicket(String ticket) { + this.ticket = ticket; + } + + @ Override + public String toString() { + return "ScanEvent [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", event=" + + event + + ", eventKey=" + + eventKey + + ", ticket=" + + ticket + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/SendLocationInfoEvent.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/SendLocationInfoEvent.java new file mode 100644 index 0000000..bc0cc2d --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/SendLocationInfoEvent.java @@ -0,0 +1,110 @@ +package io.github.elkan1788.mpsdk4j.vo.event; + +import java.util.Map; + +/** + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class SendLocationInfoEvent extends MenuEvent { + + /** + * X坐标信息 + */ + private String locationX; + /** + * Y坐标信息 + */ + private String locationY; + /** + * 精度,可理解为精度或者比例尺,越精细的话 scale越高 + */ + private int scale; + /** + * 地理位置的字符串信息 + */ + private String label; + /** + * 朋友圈POI的名字,可能为空 + */ + private String poiname; + + public SendLocationInfoEvent() {} + + public SendLocationInfoEvent(Map values) { + super(values); + this.locationX = values.get("locationX"); + this.locationY = values.get("locationY"); + this.scale = Integer.parseInt(values.get("scale")); + this.label = values.get("label"); + this.poiname = values.get("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 int getScale() { + return scale; + } + + public void setScale(int 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; + } + + @Override + public String toString() { + return "SendLocationInfoEvent [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", event=" + + event + + ", eventKey=" + + eventKey + + ", locationX=" + + locationX + + ", locationY=" + + locationY + + ", scale=" + + scale + + ", label=" + + label + + ", poiname=" + + poiname + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/SendPhotosEvent.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/SendPhotosEvent.java new file mode 100644 index 0000000..6fd6a69 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/SendPhotosEvent.java @@ -0,0 +1,58 @@ +package io.github.elkan1788.mpsdk4j.vo.event; + +import java.util.List; +import java.util.Map; + +import org.nutz.json.Json; + +/** + * 用户拍照/相册发图事件 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class SendPhotosEvent extends MenuEvent { + + /** + * 发送的图片信息 + */ + private SendPicsInfo sendPicsInfo; + + public SendPhotosEvent() { + + } + + public SendPhotosEvent(Map values) { + super(values); + List items = Json.fromJsonAsList(PicItem.class, values.get("picList")); + this.sendPicsInfo = new SendPicsInfo(Integer.parseInt(values.get("count")), items); + } + + public SendPicsInfo getSendPicsInfo() { + return sendPicsInfo; + } + + public void setSendPicsInfo(SendPicsInfo sendPicsInfo) { + this.sendPicsInfo = sendPicsInfo; + } + + @Override + public String toString() { + return "ScanSysPhotoEvent [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", event=" + + event + + ", eventKey=" + + eventKey + + ", sendPicsInfo=" + + sendPicsInfo + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/SendPicsInfo.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/SendPicsInfo.java new file mode 100644 index 0000000..08ab7b4 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/SendPicsInfo.java @@ -0,0 +1,53 @@ +package io.github.elkan1788.mpsdk4j.vo.event; + +import java.util.List; + +/** + * 扫码发图像信息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class SendPicsInfo { + + /** + * 发送的图片数量 + */ + private int count; + /** + * 图片列表 + */ + private List picList; + + public SendPicsInfo() { + super(); + } + + public SendPicsInfo(int count, List picList) { + this(); + this.count = count; + this.picList = picList; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public List getPicList() { + return picList; + } + + public void setPicList(List picList) { + this.picList = picList; + } + + @ Override + public String toString() { + return "SendPicsInfo [count=" + count + ", picList=" + picList + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/package-info.java new file mode 100644 index 0000000..afd954a --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/event/package-info.java @@ -0,0 +1,7 @@ +/** + * 事件消息实体类,如: 订阅消息,菜单消息,地理消息... + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +package io.github.elkan1788.mpsdk4j.vo.event; \ No newline at end of file diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Article.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/Article.java similarity index 60% rename from src/main/java/org/elkan1788/osc/weixin/mp/vo/Article.java rename to src/main/java/io/github/elkan1788/mpsdk4j/vo/message/Article.java index 8b579e2..80f1a94 100644 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Article.java +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/Article.java @@ -1,38 +1,36 @@ -package org.elkan1788.osc.weixin.mp.vo; +package io.github.elkan1788.mpsdk4j.vo.message; /** - * 多图文消息实体 - * - * @author 凡梦星尘(senhuili@mdc.cn) - * @since 2014/11/6 - * @version 1.0.0 + * 图文内容 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 */ public class Article { - /** - * 图文消息标题 - */ - private String title; + /** + * 图文消息标题 + */ + private String title; + /** + * 图文消息描述 + */ + private String description; + /** + * 图片链接,支持JPG,PNG格式, + *

    + * 较好的效果为大图360*200,小图200*200 + */ + private String picUrl; + /** + * 点击图文消息跳转链接 + */ + private String url; - /** - * 图文消息描述 - */ - private String description; - - /** - * 图片链接,支持JPG、PNG格式,较好的效果为大图360*200,小图200*200 - */ - private String picUrl; - - /** - * 点击图文消息跳转链接 - */ - private String url; - - public Article() { - } + public Article() {} public Article(String title, String description, String picUrl, String url) { + super(); this.title = title; this.description = description; this.picUrl = picUrl; diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/BasicMsg.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/BasicMsg.java new file mode 100644 index 0000000..7924f21 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/BasicMsg.java @@ -0,0 +1,109 @@ +package io.github.elkan1788.mpsdk4j.vo.message; + +import java.util.Map; + +import io.github.elkan1788.mpsdk4j.vo.event.BasicEvent; + +/** + * 消息基础类 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class BasicMsg { + + /** + * 微信公众号Id/OpenId + */ + protected String toUserName; + /** + * OpenId/微信公众号Id + */ + protected String fromUserName; + /** + * 消息创建时间 (整型) + */ + protected int createTime; + /** + * 消息类型: text, image, voice ... + */ + protected String msgType; + /** + * 消息Id, 64位整型 + */ + protected long msgId; + + /** + * 默认构造方法 + */ + public BasicMsg() { + this.createTime = Long.valueOf(System.currentTimeMillis() / 1000).intValue(); + } + + public BasicMsg(BasicMsg msg) { + this(); + this.fromUserName = msg.getFromUserName(); + this.toUserName = msg.getToUserName(); + } + + public BasicMsg(BasicEvent event) { + this(); + this.fromUserName = event.getFromUserName(); + this.toUserName = event.getToUserName(); + } + + /** + * 带XML解析值构造方法 + * + * @param values + * XML值 + */ + public BasicMsg(Map values) { + this.fromUserName = values.get("fromUserName"); + this.toUserName = values.get("toUserName"); + this.createTime = Integer.parseInt(values.get("createTime")); + this.msgType = values.get("msgType"); + this.msgId = Long.parseLong(values.get("msgId")); + } + + 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 int getCreateTime() { + return createTime; + } + + public void setCreateTime(int createTime) { + this.createTime = createTime; + } + + public String getMsgType() { + return msgType; + } + + public void setMsgType(String msgType) { + this.msgType = msgType; + } + + public long getMsgId() { + return msgId; + } + + public void setMsgId(long msgId) { + this.msgId = msgId; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/ImageMsg.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/ImageMsg.java new file mode 100644 index 0000000..09457fc --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/ImageMsg.java @@ -0,0 +1,68 @@ +package io.github.elkan1788.mpsdk4j.vo.message; + +import java.util.Map; + +/** + * 图像消息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class ImageMsg extends BasicMsg { + + /** + * 图片链接 + */ + private String picUrl; + /** + * 图片消息媒体id,可以调用多媒体文件下载接口拉取数据 + */ + private String mediaId; + + public ImageMsg() { + super(); + this.msgType = "image"; + } + + public ImageMsg(Map values) { + super(values); + this.picUrl = values.get("picUrl"); + this.mediaId = values.get("mediaId"); + } + + 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; + } + + @Override + public String toString() { + return "ImageMsg [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", msgId=" + + msgId + + ", picUrl=" + + picUrl + + ", mediaId=" + + mediaId + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/LinkMsg.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/LinkMsg.java new file mode 100644 index 0000000..9d911cc --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/LinkMsg.java @@ -0,0 +1,80 @@ +package io.github.elkan1788.mpsdk4j.vo.message; + +import java.util.Map; + +/** + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class LinkMsg extends BasicMsg { + + /** + * 消息标题 + */ + private String title; + /** + * 消息描述 + */ + private String description; + /** + * 消息链接 + */ + private String url; + + public LinkMsg() { + super(); + this.msgType = "link"; + } + + public LinkMsg(Map values) { + super(values); + this.title = values.get("title"); + this.description = values.get("description"); + this.url = values.get("url"); + } + + 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; + } + + @Override + public String toString() { + return "LinkMsg [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", msgId=" + + msgId + + ", title=" + + title + + ", description=" + + description + + ", url=" + + url + + "]"; + } +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/LocationMsg.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/LocationMsg.java new file mode 100644 index 0000000..1158076 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/LocationMsg.java @@ -0,0 +1,97 @@ +package io.github.elkan1788.mpsdk4j.vo.message; + +import java.util.Map; + +/** + * 地理位置消息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class LocationMsg extends BasicMsg { + + /** + * 地理位置维度 + */ + private String x; + /** + * 地理位置经度 + */ + private String y; + /** + * 地图缩放大小 + */ + private int scale; + /** + * 地理位置信息 + */ + private String label; + + public LocationMsg() { + super(); + this.msgType = "location"; + } + + public LocationMsg(Map values) { + super(values); + this.x = values.get("locationX"); + this.y = values.get("locationY"); + this.scale = Integer.parseInt(values.get("scale")); + this.label = values.get("label"); + } + + public String getX() { + return x; + } + + public void setX(String x) { + this.x = x; + } + + public String getY() { + return y; + } + + public void setY(String y) { + this.y = y; + } + + public int getScale() { + return scale; + } + + public void setScale(int scale) { + this.scale = scale; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + @Override + public String toString() { + return "LocationMsg [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", msgId=" + + msgId + + ", x=" + + x + + ", y=" + + y + + ", scale=" + + scale + + ", label=" + + label + + "]"; + } +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/MusicMsg.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/MusicMsg.java new file mode 100644 index 0000000..8a29047 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/MusicMsg.java @@ -0,0 +1,118 @@ +package io.github.elkan1788.mpsdk4j.vo.message; + +import java.util.Map; + +import io.github.elkan1788.mpsdk4j.vo.event.BasicEvent; + +/** + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class MusicMsg extends BasicMsg { + + /** + * 音乐标题 + */ + private String title; + /** + * 音乐描述 + */ + private String description; + /** + * 音乐链接 + */ + private String musicUrl; + /** + * 高质音乐链接 + */ + private String HQMusicUrl; + /** + * 缩略图的媒体id,通过素材管理接口上传多媒体文件,得到的id + */ + private String thumbMediaId; + + public MusicMsg() { + super(); + this.msgType = "music"; + } + + public MusicMsg(BasicEvent event) { + super(event); + this.msgType = "music"; + } + + public MusicMsg(BasicMsg msg) { + super(msg); + this.msgType = "music"; + } + + public MusicMsg(Map values) { + super(values); + } + + 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) { + HQMusicUrl = hQMusicUrl; + } + + public String getThumbMediaId() { + return thumbMediaId; + } + + public void setThumbMediaId(String thumbMediaId) { + this.thumbMediaId = thumbMediaId; + } + + @Override + public String toString() { + return "MusicMsg [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", msgId=" + + msgId + + ", title=" + + title + + ", description=" + + description + + ", musicUrl=" + + musicUrl + + ", HQMusicUrl=" + + HQMusicUrl + + ", thumbMediaId=" + + thumbMediaId + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/NewsMsg.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/NewsMsg.java new file mode 100644 index 0000000..ab5394a --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/NewsMsg.java @@ -0,0 +1,84 @@ +package io.github.elkan1788.mpsdk4j.vo.message; + +import java.util.List; + +import org.nutz.lang.Lang; + +import io.github.elkan1788.mpsdk4j.vo.event.BasicEvent; + +/** + * 多图文消息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class NewsMsg extends BasicMsg { + + /** + * 图文消息个数,限制为10条以内 + */ + private int count; + /** + * 多条图文消息信息,默认第一个item为大图, + *

    + * 注意:如果图文数超过10,则将会无响应 + */ + private List
    articles; + + public NewsMsg() { + super(); + this.msgType = "news"; + } + + public NewsMsg(BasicEvent event) { + super(event); + this.msgType = "news"; + } + + public NewsMsg(BasicMsg msg) { + super(msg); + this.msgType = "news"; + } + + public int getCount() { + return count; + } + + public void setCount(int count) { + this.count = count; + } + + public List
    getArticles() { + if (!Lang.isEmpty(articles) && articles.size() > 10) { + this.articles = articles.subList(0, 10); + setCount(10); + } + else { + this.setCount(Lang.length(articles)); + } + return articles; + } + + public void setArticles(List
    articles) { + this.articles = articles; + } + + @Override + public String toString() { + return "NewsMsg [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", msgId=" + + msgId + + ", count=" + + count + + ", articles=" + + articles + + "]"; + } +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/TextMsg.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/TextMsg.java new file mode 100644 index 0000000..a5d9c3b --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/TextMsg.java @@ -0,0 +1,68 @@ +/** + * @author senhui.li + */ +package io.github.elkan1788.mpsdk4j.vo.message; + +import java.util.Map; + +import io.github.elkan1788.mpsdk4j.vo.event.BasicEvent; + +/** + * 文本消息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class TextMsg extends BasicMsg { + + /** + * 文本内容 + */ + private String content; + + public TextMsg() { + super(); + this.msgType = "text"; + } + + public TextMsg(BasicEvent event) { + super(event); + this.msgType = "text"; + } + + public TextMsg(BasicMsg msg) { + super(msg); + this.msgType = "text"; + } + + public TextMsg(Map values) { + super(values); + this.content = values.get("content"); + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + @Override + public String toString() { + return "TextMsg [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", content=" + + content + + ", msgId=" + + msgId + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/VideoMsg.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/VideoMsg.java new file mode 100644 index 0000000..aa3e0de --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/VideoMsg.java @@ -0,0 +1,111 @@ +package io.github.elkan1788.mpsdk4j.vo.message; + +import java.util.Map; + +import io.github.elkan1788.mpsdk4j.vo.event.BasicEvent; + +/** + * 视频消息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class VideoMsg extends BasicMsg { + + /** + * 视频消息媒体id,可以调用多媒体文件下载接口拉取数据 + */ + private String mediaId; + + /** + * 视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据 + */ + private String thumbMediaId; + + /** + * 视频消息的标题 + */ + private String title; + + /** + * 视频消息的描述 + */ + private String description; + + public VideoMsg() { + super(); + this.msgType = "video"; + } + + public VideoMsg(BasicEvent event) { + super(event); + this.msgType = "video"; + } + + public VideoMsg(BasicMsg msg) { + super(msg); + this.msgType = "video"; + } + + public VideoMsg(Map values) { + super(values); + this.mediaId = values.get("mediaId"); + this.thumbMediaId = values.get("thumbMediaId"); + } + + 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; + } + + @Override + public String toString() { + return "VideoMsg [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", msgId=" + + msgId + + ", mediaId=" + + mediaId + + ", thumbMediaId=" + + thumbMediaId + + ", title=" + + title + + ", description=" + + description + + "]"; + } + +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/VoiceMsg.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/VoiceMsg.java new file mode 100644 index 0000000..ecb15e4 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/VoiceMsg.java @@ -0,0 +1,94 @@ +package io.github.elkan1788.mpsdk4j.vo.message; + +import java.util.Map; + +import io.github.elkan1788.mpsdk4j.vo.event.BasicEvent; + +/** + * 音频消息 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class VoiceMsg extends BasicMsg { + + /** + * 语音消息媒体id,可以调用多媒体文件下载接口拉取数据 + */ + private String mediaId; + /** + * 语音格式,如amr,speex等 + */ + private String format; + /** + * 语音识别结果,UTF8编码 + */ + private String recognition; + + public VoiceMsg() { + super(); + this.msgType = "voice"; + } + + public VoiceMsg(BasicEvent event) { + super(event); + this.msgType = "voice"; + } + + public VoiceMsg(BasicMsg msg) { + super(msg); + this.msgType = "voice"; + } + + public VoiceMsg(Map values) { + super(values); + this.mediaId = values.get("mediaId"); + this.format = values.get("format"); + this.recognition = values.get("recognition"); + } + + 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 getRecognition() { + return recognition; + } + + public void setRecognition(String recognition) { + this.recognition = recognition; + } + + @Override + public String toString() { + return "VoiceMsg [toUserName=" + + toUserName + + ", fromUserName=" + + fromUserName + + ", createTime=" + + createTime + + ", msgType=" + + msgType + + ", msgId=" + + msgId + + ", mediaId=" + + mediaId + + ", format=" + + format + + ", recognition=" + + recognition + + "]"; + } +} diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/package-info.java new file mode 100644 index 0000000..d917251 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/message/package-info.java @@ -0,0 +1,7 @@ +/** + * 普通消息实体类,如: 文本消息,图像消息,语音消息... + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +package io.github.elkan1788.mpsdk4j.vo.message; \ No newline at end of file diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/package-info.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/package-info.java new file mode 100644 index 0000000..a89be27 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/package-info.java @@ -0,0 +1,7 @@ +/** + * 微信公众平台API中对应所有实体类设计 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +package io.github.elkan1788.mpsdk4j.vo; \ No newline at end of file diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/push/SentAllJobEvent.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/push/SentAllJobEvent.java new file mode 100644 index 0000000..146b244 --- /dev/null +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/push/SentAllJobEvent.java @@ -0,0 +1,139 @@ +package io.github.elkan1788.mpsdk4j.vo.push; + +import java.util.Map; + +/** + * 群发消息处理事件 + *

    + * 微信发送消息状态(模板消息,群发消息) + * + *
    + * 群发的结构,为“send success”或“send fail”或“err(num)”。但send success时,
    + * 
    + * 
    + * 也有可能因用户拒收公众号的消息、系统错误等原因造成少量用户接收失败。err(num)是审核失败的具体原因,
    + * 
    + * 
    + * 可能的情况如下:
    + * 
    + * 
    + * err(10001), //涉嫌广告
    + * 
    + * 
    + * err(20001), //涉嫌政治
    + * 
    + * 
    + * err(20004), //涉嫌社会
    + * 
    + * 
    + * err(20002), //涉嫌色情
    + * 
    + * 
    + * err(20006), //涉嫌违法犯罪
    + * 
    + * 
    + * err(20008), //涉嫌欺诈
    + * 
    + * 
    + * err(20013), //涉嫌版权
    + * 
    + * 
    + * err(22000), //涉嫌互推(互相宣传)
    + * 
    + * 
    + * err(21000), //涉嫌其他
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public class SentAllJobEvent extends SentTmlJobEvent {
    +
    +    /**
    +     * group_id下粉丝数;或者openid_list中的粉丝数
    +     */
    +    private int totalCnt;
    +    /**
    +     * 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,
    +     * 用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,
    +     * FilterCount = SentCount + ErrorCount
    +     */
    +    private int filterCnt;
    +    /**
    +     * 发送成功的粉丝数
    +     */
    +    private int sentCnt;
    +    /**
    +     * 发送失败的粉丝数
    +     */
    +    private int errorCnt;
    +
    +    public SentAllJobEvent() {
    +        super();
    +    }
    +
    +    public SentAllJobEvent(Map values) {
    +        super(values);
    +        this.totalCnt = Integer.parseInt(values.get("totalCount"));
    +        this.filterCnt = Integer.parseInt(values.get("filterCount"));
    +        this.sentCnt = Integer.parseInt(values.get("sentCount"));
    +        this.errorCnt = Integer.parseInt(values.get("errorCount"));
    +    }
    +
    +    public int getTotalCnt() {
    +        return totalCnt;
    +    }
    +
    +    public void setTotalCnt(int totalCnt) {
    +        this.totalCnt = totalCnt;
    +    }
    +
    +    public int getFilterCnt() {
    +        return filterCnt;
    +    }
    +
    +    public void setFilterCnt(int filterCnt) {
    +        this.filterCnt = filterCnt;
    +    }
    +
    +    public int getSentCnt() {
    +        return sentCnt;
    +    }
    +
    +    public void setSentCnt(int sentCnt) {
    +        this.sentCnt = sentCnt;
    +    }
    +
    +    public int getErrorCnt() {
    +        return errorCnt;
    +    }
    +
    +    public void setErrorCnt(int errorCnt) {
    +        this.errorCnt = errorCnt;
    +    }
    +
    +    @ Override
    +    public String toString() {
    +        return "SenAllJobEvent [toUserName="
    +               + toUserName
    +               + ", fromUserName="
    +               + fromUserName
    +               + ", createTime="
    +               + createTime
    +               + ", msgType="
    +               + msgType
    +               + ", event="
    +               + event
    +               + ", eventKey="
    +               + eventKey
    +               + ", totalCnt="
    +               + totalCnt
    +               + ", filterCnt="
    +               + filterCnt
    +               + ", sentCnt="
    +               + sentCnt
    +               + ", errorCnt="
    +               + errorCnt
    +               + "]";
    +    }
    +
    +}
    diff --git a/src/main/java/io/github/elkan1788/mpsdk4j/vo/push/SentTmlJobEvent.java b/src/main/java/io/github/elkan1788/mpsdk4j/vo/push/SentTmlJobEvent.java
    new file mode 100644
    index 0000000..1704c4c
    --- /dev/null
    +++ b/src/main/java/io/github/elkan1788/mpsdk4j/vo/push/SentTmlJobEvent.java
    @@ -0,0 +1,70 @@
    +package io.github.elkan1788.mpsdk4j.vo.push;
    +
    +import java.util.Map;
    +
    +import io.github.elkan1788.mpsdk4j.vo.event.BasicEvent;
    +
    +/**
    + * 模板消息处理事件
    + * 
    + * @author 凡梦星尘(elkan1788@gmail.com)
    + * @since 2.0
    + */
    +public class SentTmlJobEvent extends BasicEvent {
    +
    +    /**
    +     * 消息id
    +     */
    +    private String msgId;
    +    /**
    +     * 发送状态: success,failed:user block,failed: system failed
    +     */
    +    private String status;
    +
    +    public SentTmlJobEvent() {
    +        super();
    +    }
    +
    +    public SentTmlJobEvent(Map values) {
    +        super(values);
    +        this.msgId = values.get("msgId");
    +        this.status = values.get("status");
    +    }
    +
    +    public String getMsgId() {
    +        return msgId;
    +    }
    +
    +    public void setMsgId(String msgId) {
    +        this.msgId = msgId;
    +    }
    +
    +    public String getStatus() {
    +        return status;
    +    }
    +
    +    public void setStatus(String status) {
    +        this.status = status;
    +    }
    +
    +    @ Override
    +    public String toString() {
    +        return "TemplateJobEvent [toUserName="
    +               + toUserName
    +               + ", fromUserName="
    +               + fromUserName
    +               + ", createTime="
    +               + createTime
    +               + ", msgType="
    +               + msgType
    +               + ", event="
    +               + event
    +               + ", eventKey="
    +               + eventKey
    +               + ", msgId="
    +               + msgId
    +               + ", status="
    +               + status
    +               + "]";
    +    }
    +}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/commons/WxApiUrl.java b/src/main/java/org/elkan1788/osc/weixin/mp/commons/WxApiUrl.java
    deleted file mode 100644
    index a777f60..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/commons/WxApiUrl.java
    +++ /dev/null
    @@ -1,104 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.commons;
    -
    -/**
    - * 微信所有的API地址
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @version 1.0.2
    - * @since 2014/11/12
    - */
    -public interface WxApiUrl {
    -
    -    /**
    -     * 微信API入口
    -     */
    -    public static final String WX_API = "https://api.weixin.qq.com/cgi-bin";
    -    /**
    -     * 微信开放平台入口
    -     */
    -    public static final String WX_OPEN_API = "https://open.weixin.qq.com";
    -    /**
    -     * 微信多媒体文件API入口
    -     */
    -    public static final String MEDIA_API = "http://file.api.weixin.qq.com/cgi-bin/media";
    -    /**
    -     * 令牌API入口
    -     */
    -    public static final String ACCESS_TOKEN_API = WX_API + "/token?grant_type=client_credential&appid=%1$s&secret=%2$s";
    -    /**
    -     * 获取微信服务器IP
    -     */
    -    public static final String IP_LIST_API = WX_API + "/getcallbackip?access_token=%1$s";
    -    /**
    -     * 自定义菜单API入口[create, get, delete]
    -     */
    -    public static final String CUSTOM_MENU_API = WX_API + "/menu/%1$s?access_token=%2$s";
    -    /**
    -     * 分组管理API入口[create, get, getid, update]
    -     */
    -    public static final String GROUPS_API = WX_API + "/groups/%1$s?access_token=%2$s";
    -    /**
    -     * 用户分组API入口
    -     */
    -    public static final String GROUPS_USER_API = WX_API + "/groups/members/update?access_token=%1$s";
    -    /**
    -     * 微信用户信息API入口
    -     */
    -    public static final String USER_INFO_API = WX_API + "/user/info?access_token=%1$s&openid=%2$s&lang=%3$s";
    -    /**
    -     * 公众号关注者API入口
    -     */
    -    public static final String FOLLOWS_USER_API = WX_API + "/user/get?access_token=%1$s&next_openid=%2$s";
    -    /**
    -     * 下载多媒体文件API入口
    -     */
    -    public static final String MEDIA_DL_API = MEDIA_API + "/get?access_token=%1$s&media_id=%2$s";
    -    /**
    -     * 上传多媒体文件API入口
    -     */
    -    public static final String MEDIA_UP_API = MEDIA_API + "/upload?type=%1$s&access_token=%2$s";
    -    /**
    -     * 发送客服消息入口
    -     */
    -    public static final String CUSTOM_MESSAGE_API = WX_API + "/message/custom/send?access_token=%1$s";
    -    /**
    -     * 网页授权请求地址
    -     */
    -    public static final String WEB_OAUTH2 = "/connect/oauth2/authorize?appid=%1$s&redirect_uri=%2$s&response_type=code&scope=%3$s&state=%4$s;%5$s#wechat_redirect";
    -    /**
    -     * 网页授权获取TOKEN
    -     */
    -    public static final String OAUTH_TOKEN = "/sns/oauth2/access_token?appid=%1$s&secret=%2$s&grant_type=authorization_code&code=%3$s";
    -    /**
    -     * 网页授权取得用户信息地址
    -     */
    -    public static final String OAUTH_USERINFO = "/sns/userinfo?access_token=%1$s&openid=%2$s&lang=%3$s";
    -    /**
    -     * 发送模板消息地址
    -     */
    -    public static final String TEMPLATE_MESSAGE_API = WX_API + "/message/template/send?access_token=%1$s";
    -    /**
    -     * 上传图文素材地址
    -     */
    -    public static final String NEWS_UPLOAD_API = WX_API + "/media/uploadnews?access_token=%1$s";
    -    /**
    -     * 分组群消息[sendall,send,delete]入口
    -     */
    -    public static final String GROUP_NEWS_MESSAGE_API = WX_API + "/message/mass/%1$s?access_token=%2$s";
    -    /**
    -     * 群发消息上传视频地址
    -     */
    -    public static final String MEDIA_UPVIDEO_API = MEDIA_API + "/uploadvideo?access_token=%1$s";
    -    /**
    -     * 服务组件API入口
    -     */
    -    public static final String COMPONENT_API = WX_API + "/component/%1$s?component_access_token=%2$s";
    -    /**
    -     * 获取服务组件token地址
    -     */
    -    public static final String COMPONENT_TOKEN_API = WX_API + "/component/api_component_token";
    -    /**
    -     * 获取JSTICKET地址
    -     */
    -    public static final String JSAPI_TICKET_URL = WX_API + "/ticket/getticket?type=jsapi&access_token=";
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/commons/WxErrCode.java b/src/main/java/org/elkan1788/osc/weixin/mp/commons/WxErrCode.java
    deleted file mode 100644
    index bbd27d6..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/commons/WxErrCode.java
    +++ /dev/null
    @@ -1,273 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.commons;
    -
    -/**
    - * 微信通讯的错误代码
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/11/6
    - * @version 1.0.0
    - */
    -public class WxErrCode {
    -
    -    public static final int SYS_BUSY = -1;
    -    public static final int OK = 0;
    -    public static final int ERROR_SECRET = 40001;
    -    public static final int INVALID_CERTIFICATE = 40002;
    -    public static final int INVALID_OPENID = 40003;
    -    public static final int INVALID_MEDIATYPE = 40004;
    -    public static final int INVALID_FILETYPE = 40005;
    -    public static final int INVALID_FILESIZE = 40006;
    -    public static final int INVALID_MEDIAID = 40007;
    -    public static final int INVALID_MESGTYPE = 40008;
    -    public static final int INVALID_IMAGESIZE = 40009;
    -    public static final int INVALID_VOICESIZE = 40010;
    -    public static final int INVALID_VIDEOSIZE = 40011;
    -    public static final int INVALID_THUMBSIZE = 40012;
    -    public static final int INVALID_APPID = 40013;
    -    public static final int INVALID_ACCESSTOKEN = 40014;
    -    public static final int INVALID_MENUTYPE = 40015;
    -    public static final int INVALID_BUTTONSIZE = 40016;
    -    public static final int INVALID_BUTTONSIZE2 = 40017;
    -    public static final int INVALID_BUTTONNAMESIZE = 40018;
    -    public static final int INVALID_BUTTONKEYSIZE = 40019;
    -    public static final int INVALID_BUTTONURLSIZE = 40020;
    -    public static final int INVALID_BUTTONVERSION = 40021;
    -    public static final int INVALID_MENULEVEL = 40022;
    -    public static final int INVALID_SUBBUTTONSIZE = 40023;
    -    public static final int INVALID_SUBMENUTYPE = 40024;
    -    public static final int INVALID_SUBBUTTONNAMESIZE = 40025;
    -    public static final int INVALID_SUBBUTTONKEYSIZE = 40026;
    -    public static final int INVALID_SUBBUTTONURLSIZE = 40027;
    -    public static final int INVALID_MENUUSER = 40028;
    -    public static final int INVALID_OAUTHCODE = 40029;
    -    public static final int INVALID_REFRESHCODE = 40030;
    -    public static final int INVALID_OPENIDLIST = 40031;
    -    public static final int INVALID_OPENIDLENGTH = 40032;
    -    public static final int INVALID_REQUESTSTRING = 40033;
    -    public static final int INVALID_PARAMETERS = 40035;
    -    public static final int INVALID_REQUESTSTYLE = 40038;
    -    public static final int INVALID_URLLENGTH = 40039;
    -    public static final int INVALID_GROUPID = 40050;
    -    public static final int INVALID_GROUPNAME = 40051;
    -    public static final int INVALID_MSGID = 40059;
    -    public static final int LOST_AESSTOKEN = 41001;
    -    public static final int LOST_APPID = 41002;
    -    public static final int LOST_REFRESHTOKEN = 41003;
    -    public static final int LOST_APPSECRET = 41004;
    -    public static final int LOST_MEDIAFILE_DATA = 41005;
    -    public static final int LOST_MEDIAID = 41006;
    -    public static final int LOST_SUBMENUS = 41007;
    -    public static final int LOST_OAUTHCODE = 41008;
    -    public static final int LOST_OPENID = 41009;
    -    public static final int ACCESSTOKEN_OVERTIME = 42001;
    -    public static final int REFRESHTOKEN_OVERTIME = 42002;
    -    public static final int OAUTHCODE_OVERTIME = 42003;
    -    public static final int GET_REUEST = 43001;
    -    public static final int POST_REUEST = 43002;
    -    public static final int HTTPS_REUEST = 43003;
    -    public static final int FOLLOW_REUEST = 43004;
    -    public static final int FRIEND_REUEST = 43005;
    -    public static final int NULL_MEDIAFILE = 44001;
    -    public static final int NULL_POSTDATA = 44002;
    -    public static final int NULL_NEWSDATA = 44003;
    -    public static final int NULL_TEXTDATA = 44004;
    -    public static final int MEDIASIZE_LIMIT_OVER = 45001;
    -    public static final int CONTENT_LIMIT_OVER = 45002;
    -    public static final int TITLE_LIMIT_OVER = 45003;
    -    public static final int DIGEST_LIMIT_OVER = 45004;
    -    public static final int LINKURL_LIMIT_OVER = 45005;
    -    public static final int PICURL_LIMIT_OVER = 45006;
    -    public static final int VIOCE_PLAY_LIMIT_OVER = 45007;
    -    public static final int NEWS_LIMIT_OVER = 45008;
    -    public static final int INTERFACE_LIMIT_OVER = 45009;
    -    public static final int MENUSIZE_LIMIT_OVER = 45010;
    -    public static final int REPLYTIME_LIMIT_OVER = 45015;
    -    public static final int SYSTEM_GROUP = 45016;
    -    public static final int GROUPNAME_LIMIT_OVER = 45017;
    -    public static final int GROUPSIZE_LIMIT_OVER = 45018;
    -    public static final int NOEXIST_MEDIAFILE = 46001;
    -    public static final int NOEXIST_MENUVERSION = 46002;
    -    public static final int NOEXIST_MENUDATA = 46003;
    -    public static final int NOEXIST_USER = 46004;
    -    public static final int PARSE_XML_JSON_ERR = 47001;
    -    public static final int UNOAUTH_MODULE_API = 48001;
    -    public static final int UNOATH_API = 50001;
    -
    -    /**
    -     * 获取错误信息描述
    -     *
    -     * @param errCode   错误代码
    -     * @return  错误描述
    -     */
    -    public static String getErrDesc(int errCode){
    -        switch (errCode) {
    -            case OK:
    -                return "请求成功";
    -            case SYS_BUSY:
    -                return "系统繁忙";
    -            case ERROR_SECRET:
    -                return "获取access_token时AppSecret错误,或者access_token无效";
    -            case INVALID_CERTIFICATE:
    -                return "不合法的凭证类型";
    -            case INVALID_OPENID:
    -                return "不合法的OpenID";
    -            case INVALID_MEDIATYPE:
    -                return "不合法的媒体文件类型";
    -            case INVALID_FILETYPE:
    -                return "不合法的文件类型";
    -            case INVALID_FILESIZE:
    -                return "不合法的文件大小";
    -            case INVALID_MEDIAID:
    -                return "不合法的媒体文件id";
    -            case INVALID_MESGTYPE:
    -                return "不合法的消息类型";
    -            case INVALID_IMAGESIZE:
    -                return "不合法的图片文件大小";
    -            case INVALID_VOICESIZE:
    -                return "不合法的语音文件大小";
    -            case INVALID_VIDEOSIZE:
    -                return "不合法的视频文件大小";
    -            case INVALID_THUMBSIZE:
    -                return "不合法的缩略图文件大小";
    -            case INVALID_APPID:
    -                return "不合法的APPID";
    -            case INVALID_ACCESSTOKEN:
    -                return "不合法的access_token";
    -            case INVALID_MENUTYPE:
    -                return "不合法的菜单类型";
    -            case INVALID_BUTTONSIZE:
    -            case INVALID_BUTTONSIZE2:
    -                return "不合法的按钮个数";
    -            case INVALID_BUTTONNAMESIZE:
    -                return "不合法的按钮名字长度";
    -            case INVALID_BUTTONKEYSIZE:
    -                return "不合法的按钮KEY长度";
    -            case INVALID_BUTTONURLSIZE:
    -                return "不合法的按钮URL长度";
    -            case INVALID_BUTTONVERSION:
    -                return "不合法的菜单版本号";
    -            case INVALID_MENULEVEL:
    -                return "不合法的子菜单级数";
    -            case INVALID_SUBBUTTONSIZE:
    -                return "不合法的子菜单按钮个数";
    -            case INVALID_SUBMENUTYPE:
    -                return "不合法的子菜单按钮类型";
    -            case INVALID_SUBBUTTONNAMESIZE:
    -                return "不合法的子菜单按钮名字长度";
    -            case INVALID_SUBBUTTONKEYSIZE:
    -                return "不合法的子菜单按钮KEY长度";
    -            case INVALID_SUBBUTTONURLSIZE:
    -                return "不合法的子菜单按钮KEY长度";
    -            case INVALID_MENUUSER:
    -                return "不合法的自定义菜单使用用户";
    -            case INVALID_OAUTHCODE:
    -                return "不合法的oauth_code";
    -            case INVALID_REFRESHCODE:
    -                return "不合法的refresh_token";
    -            case INVALID_OPENIDLIST:
    -                return "不合法的openid列表";
    -            case INVALID_OPENIDLENGTH:
    -                return "不合法的openid列表长度";
    -            case INVALID_REQUESTSTRING:
    -                return "不合法的请求字符,不能包含\\uxxxx格式的字符";
    -            case INVALID_PARAMETERS:
    -                return "不合法的参数";
    -            case INVALID_REQUESTSTYLE:
    -                return "不合法的请求格式";
    -            case INVALID_URLLENGTH:
    -                return "不合法的URL长度";
    -            case INVALID_GROUPID:
    -                return "不合法的分组id";
    -            case INVALID_GROUPNAME:
    -                return "分组名字不合法";
    -            case INVALID_MSGID:
    -                return "不合法的消息ID";
    -            case LOST_AESSTOKEN:
    -                return "缺少access_token参数";
    -            case LOST_APPID:
    -                return "缺少appid参数";
    -            case LOST_REFRESHTOKEN:
    -                return "缺少refresh_token参数";
    -            case LOST_APPSECRET:
    -                return "缺少secret参数";
    -            case LOST_MEDIAFILE_DATA:
    -                return "缺少多媒体文件数据";
    -            case LOST_MEDIAID:
    -                return "缺少media_id参数";
    -            case LOST_SUBMENUS:
    -                return "缺少子菜单数据";
    -            case LOST_OAUTHCODE:
    -                return "缺少oauth code";
    -            case LOST_OPENID:
    -                return "缺少openid";
    -            case ACCESSTOKEN_OVERTIME:
    -                return "access_token超时";
    -            case REFRESHTOKEN_OVERTIME:
    -                return "refresh_token超时";
    -            case OAUTHCODE_OVERTIME:
    -                return "oauth code超时";
    -            case GET_REUEST:
    -                return "需求GET请求";
    -            case POST_REUEST:
    -                return "需求POST请求";
    -            case HTTPS_REUEST:
    -                return "需求HTTPS请求";
    -            case FOLLOW_REUEST:
    -                return "需要接收者关注";
    -            case FRIEND_REUEST:
    -                return "需要好友关系";
    -            case NULL_MEDIAFILE:
    -                return "多媒体文件为空";
    -            case NULL_POSTDATA:
    -                return "POST的数据包为空";
    -            case NULL_NEWSDATA:
    -                return "图文消息内容为空";
    -            case NULL_TEXTDATA:
    -                return "文本消息内容为空";
    -            case MEDIASIZE_LIMIT_OVER:
    -                return "多媒体文件大小超过限制";
    -            case CONTENT_LIMIT_OVER:
    -                return "消息内容超过限制";
    -            case TITLE_LIMIT_OVER:
    -                return "标题字段超过限制";
    -            case DIGEST_LIMIT_OVER:
    -                return "描述字段超过限制";
    -            case LINKURL_LIMIT_OVER:
    -                return "链接字段超过限制";
    -            case PICURL_LIMIT_OVER:
    -                return "图片链接字段超过限制";
    -            case VIOCE_PLAY_LIMIT_OVER:
    -                return "语音播放时间超过限制";
    -            case NEWS_LIMIT_OVER:
    -                return "图文消息超过限制";
    -            case INTERFACE_LIMIT_OVER:
    -                return "接口调用超过限制";
    -            case MENUSIZE_LIMIT_OVER:
    -                return "创建菜单个数超过限制";
    -            case REPLYTIME_LIMIT_OVER:
    -                return "回复时间超过限制";
    -            case SYSTEM_GROUP:
    -                return "系统分组,不允许修改";
    -            case GROUPNAME_LIMIT_OVER:
    -                return "分组名字过长";
    -            case GROUPSIZE_LIMIT_OVER:
    -                return "分组数量超过上限";
    -            case NOEXIST_MEDIAFILE:
    -                return "不存在媒体数据";
    -            case NOEXIST_MENUVERSION:
    -                return "不存在的菜单版本";
    -            case NOEXIST_MENUDATA:
    -                return "不存在的菜单数据";
    -            case NOEXIST_USER:
    -                return "不存在的用户";
    -            case PARSE_XML_JSON_ERR:
    -                return "解析JSON/XML内容错误";
    -            case UNOAUTH_MODULE_API:
    -                return "api功能未授权";
    -            case UNOATH_API:
    -                return "用户未授权该api";
    -            default:
    -                return "未知异常";
    -        }
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/commons/package-info.java b/src/main/java/org/elkan1788/osc/weixin/mp/commons/package-info.java
    deleted file mode 100644
    index 21d62fe..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/commons/package-info.java
    +++ /dev/null
    @@ -1,4 +0,0 @@
    -/**
    - * 微信API的公共组件,如api地址,错误代码,消息,事件类型等
    - */
    -package org.elkan1788.osc.weixin.mp.commons;
    \ No newline at end of file
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxApi.java b/src/main/java/org/elkan1788/osc/weixin/mp/core/WxApi.java
    deleted file mode 100644
    index 2392f3e..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxApi.java
    +++ /dev/null
    @@ -1,228 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.core;
    -
    -import org.elkan1788.osc.weixin.mp.exception.WxRespException;
    -import org.elkan1788.osc.weixin.mp.vo.*;
    -
    -import java.io.File;
    -import java.util.List;
    -import java.util.Map;
    -
    -/**
    - * 微信公众平台开发者API接口设计
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/11/10
    - * @version 1.2.2
    - */
    -public interface WxApi {
    -
    -    public static final String TEXT = "text";
    -    public static final String IMAGE = "image";
    -    public static final String VOICE = "voice";
    -    public static final String VIDEO = "video";
    -    public static final String MUSIC = "music";
    -    public static final String NEWS = "news";
    -    public static final String MPNEWS = "mpnews";
    -    public static final String MPVIDEO = "mpvideo";
    -
    -	/**
    -	 * 创建ACCESS_TOKEN
    -     *
    -     * @return 高级API需要的access_token
    -     * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -	 */
    -	String getAccessToken() throws WxRespException;
    -
    -    /**
    -     * 刷新过期的ACCESS_TOKE
    -     *
    -     * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -     */
    -	void refreshAccessToken() throws WxRespException;
    -
    -	/**
    -	 * 获取微信服务器IP列表
    -     *
    -	 * @return IP地址集合
    -	 * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -	 */
    -	List getServerIp() throws WxRespException;
    -
    -    /**
    -     * 上传多媒体文件,微信服务器只保存3天
    -     *
    -     * @param mediaType 媒体文件类型[image, voice, video]
    -     * @param file      文件
    -     * @return  储存媒体ID
    -     * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -     */
    -    String upMedia(String mediaType, File file) throws WxRespException;
    -
    -    /**
    -     * 下载多媒体文件
    -     *
    -     * @param mediaId   多媒体ID
    -     * @param file      本地存储位置[默认使用媒体ID作文件名]
    -     * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -     */
    -    void dlMedia(String mediaId, File file) throws WxRespException;
    -
    -    /**
    -     * 获取微信菜单
    -     *
    -     * @return  微信菜单集合
    -     */
    -	List getMenu() throws WxRespException;
    -
    -    /**
    -     * 创建微信菜单
    -     *
    -     * @param menus 微信菜单
    -     * @return  true或false
    -     */
    -	boolean createMenu(Menu... menus) throws WxRespException;
    -
    -	/**
    -	 * 删除自定义菜单
    -     *
    -	 * @return true或false
    -	 */
    -	boolean deleteMenu() throws WxRespException;
    -
    -	/**
    -	 * 创建分组
    -     *
    -	 * @param name 分组名称
    -	 * @return 分组ID
    -	 */
    -	int creatGroup(String name) throws WxRespException;
    -
    -	/**
    -	 * 获取所有分组
    -     *
    -	 * @return  Groups集合
    -	 */
    -	List getGroups() throws WxRespException;
    -
    -	/**
    -	 * 重命名分组
    -     *
    -	 * @param id	分组ID
    -	 * @param name	新的分组名称
    -	 * @return true或false
    -	 */
    -	boolean renGroup(int id, String name) throws WxRespException;
    -
    -	/**
    -	 * 获取用户分组ID
    -     *
    -	 * @param openId 	用户ID
    -	 * @return 分组ID
    -	 */
    -	int getGroupId(String openId) throws WxRespException;
    -
    -	/**
    -	 * 移动用户分组
    -     *
    -	 * @param openId	用户ID
    -	 * @param groupId	新分组ID
    -	 * @return true或false
    -	 */
    -	boolean move2Group(String openId, int groupId) throws WxRespException;
    -
    -	/**
    -	 * 获取关注用户列表
    -     *
    -	 * @param	nextOpenId	 	拉取列表的后一个用户的OPENID
    -	 * @return 关注用户ID列表
    -	 */
    -	FollowList getFollowerList(String nextOpenId) throws WxRespException;
    -
    -	/**
    -	 * 获取关注者的信息
    -     *
    -	 * @param	openId	用户ID
    -	 * @param 	lang	使用语言
    -	 * @return 关注者的基本信息
    -	 */
    -	Follower getFollower(String openId, String lang) throws WxRespException;
    -
    -    /**
    -     * 发送客服消息
    -     *
    -     * @param msg       消息
    -     * @return  false或true
    -     * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -     */
    -	boolean sendCustomerMsg(OutPutMsg msg) throws WxRespException;
    -
    -    /**
    -     * 发送模板消息
    -     *
    -     * @param openId        接收者ID
    -     * @param templateId    模板ID
    -     * @param topColor      顶部颜色
    -     * @param url           跳转地址
    -     * @param templates     模样数据
    -     * @return false或true
    -     * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -     */
    -	boolean sendTemplateMsg(String openId, String templateId, String topColor, String url, Template... templates) throws WxRespException;
    -
    -	/**
    -	 * 上传图文消息素材
    -	 *
    -	 * @param articles2s 图文消息实体数组
    -	 * @return [0 消息类型, 1 多媒体ID, 2 创建时间]
    -	 * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -	 */
    -	String[] upNews(Article2... articles2s) throws WxRespException;
    -
    -	/**
    -	 * 上传群发消息中的视频文件
    -	 *
    -	 * @param mediaId	多媒体ID[需通过基础支持中的上传下载多媒体文件来得到]
    -	 * @param title		标题
    -	 * @param description	描述
    -	 * @return [0 消息类型, 1 多媒体ID, 2 创建时间]
    -	 * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -	 */
    -	String[] upVideo(String mediaId, String title, String description) throws WxRespException;
    -
    -	/**
    -	 * 群发消息[分组或指定用户]
    -     *
    -	 * @param msg	消息输出实体[groupId或toUsers, content, msgType, mediaId]
    -	 * @return	消息ID
    -	 * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -	 */
    -	String sendAll(OutPutMsg msg) throws WxRespException;
    -
    -	/**
    -	 * 删除群发消息
    -	 *
    -	 * 只有已经发送成功的消息才能删除删除消息只是将消息的图文详情页失效,
    -	 * 已经收到的用户,还是能在其本地看到消息卡片。 另外,删除群发消息只能
    -	 * 删除图文消息和视频消息,其他类型的消息一经发送,无法删除。
    -	 * @param msgId	发送消息的ID
    -	 * @return	false或true
    -	 * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -	 */
    -	boolean delSendAll(String msgId) throws WxRespException;
    -
    -    /**
    -     * 获取JSAPI签名
    -     *
    -     * @param url 使用jsapi页面地址
    -     * @return  集合[url, ticket, nonce, timestamp, sign]
    -     * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -     */
    -    Map getJsAPISign(String url) throws WxRespException;
    -
    -    /**
    -     * 刷新过期的JSAPI TICKET
    -     *
    -     * @throws org.elkan1788.osc.weixin.mp.exception.WxRespException
    -     */
    -    void refreshJsAPITicket() throws WxRespException;
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxApiImpl.java b/src/main/java/org/elkan1788/osc/weixin/mp/core/WxApiImpl.java
    deleted file mode 100644
    index 31efe79..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxApiImpl.java
    +++ /dev/null
    @@ -1,601 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.core;
    -
    -import com.alibaba.fastjson.JSON;
    -import com.alibaba.fastjson.JSONObject;
    -import com.alibaba.fastjson.serializer.PropertyFilter;
    -import com.qq.weixin.mp.aes.AesException;
    -import com.qq.weixin.mp.aes.SHA1;
    -import org.elkan1788.osc.weixin.mp.commons.WxMsgType;
    -import org.elkan1788.osc.weixin.mp.commons.WxApiUrl;
    -import org.elkan1788.osc.weixin.mp.exception.WxRespException;
    -import org.elkan1788.osc.weixin.mp.util.JsonMsgBuilder;
    -import org.elkan1788.osc.weixin.mp.util.SimpleHttpReq;
    -import org.elkan1788.osc.weixin.mp.vo.*;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -import java.io.File;
    -import java.io.IOException;
    -import java.util.*;
    -
    -/**
    - * 微信公众平台开发者API接口实现
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/11/12
    - * @version 1.2.2
    - */
    -public class WxApiImpl implements WxApi {
    -
    -    private static final Logger log = LoggerFactory.getLogger(WxApiImpl.class);
    -
    -    private MPAct mpAct;
    -
    -    /**
    -     * 微信公众平台接口构建
    -     *
    -     * @param mpAct 公众号信息
    -     */
    -    public WxApiImpl(MPAct mpAct) {
    -        this.mpAct = mpAct;
    -    }
    -
    -    @Override
    -    public String getAccessToken() throws WxRespException {
    -        String token = mpAct.getAccessToken();
    -        if (null == token
    -                || token.isEmpty()
    -                || mpAct.getExpiresIn() < System.currentTimeMillis()) {
    -            synchronized (mpAct){
    -                refreshAccessToken();
    -            }
    -            token = mpAct.getAccessToken();
    -        }
    -        return token;
    -    }
    -
    -    @Override
    -    public void refreshAccessToken() throws WxRespException {
    -        String url = String.format(WxApiUrl.ACCESS_TOKEN_API,
    -                mpAct.getAppId(),
    -                mpAct.getAppSecret());
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.get(url);
    -        } catch (IOException e) {
    -            log.error("刷新ACCESS_TOKEN时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.contains("errcode")) {
    -           throw new WxRespException(result);
    -        }
    -
    -        mpAct.createAccessToken(result);
    -    }
    -
    -    @Override
    -    public List getServerIp() throws WxRespException {
    -        String url = String.format(WxApiUrl.IP_LIST_API, getAccessToken());
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.get(url);
    -        } catch (IOException e) {
    -            log.error("获取微信服务器IP时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        JSONObject tmp = JSON.parseObject(result);
    -        List ips = JSONObject.parseArray(tmp.getString("ip_list"), String.class);
    -
    -        return ips;
    -    }
    -
    -    @Override
    -    public List getMenu() throws WxRespException {
    -        String url = String.format(WxApiUrl.CUSTOM_MENU_API, "get", getAccessToken());
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.get(url);
    -        } catch (IOException e) {
    -            log.error("获取当前自定义菜单时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -        JSONObject tmp = JSON.parseObject(result).getJSONObject("menu");
    -        List menus = JSON.parseArray(tmp.getString("button"), Menu.class);
    -        return menus;
    -    }
    -
    -    @Override
    -    public boolean createMenu(Menu... menus) throws WxRespException {
    -        // 创建JSON格式化
    -        PropertyFilter null_filter = new PropertyFilter() {
    -            @Override
    -            public boolean apply(Object object, String name, Object value) {
    -                if(name.equals("key")
    -                        && (null==value || "".equals(value))) {
    -                    return false;
    -                }
    -                if (name.equals("url")
    -                        && (null==value || "".equals(value))) {
    -                    return false;
    -                }
    -                return true;
    -            }
    -        };
    -        // 准备菜单数据
    -        Map buttons = new HashMap<>();
    -        buttons.put("button", Arrays.asList(menus));
    -        String btn_json = JSONObject.toJSONString(buttons, null_filter);
    -        if (log.isInfoEnabled()) {
    -            log.info("创建的微信自定义菜单: {}", btn_json);
    -        }
    -        // 提交并创建菜单
    -        String url = String.format(WxApiUrl.CUSTOM_MENU_API, "create", getAccessToken());
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, btn_json);
    -        } catch (IOException e) {
    -            log.error("创建自定义菜单时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || !result.contains("ok")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        return true;
    -    }
    -
    -    @Override
    -    public boolean deleteMenu() throws WxRespException {
    -        String url = String.format(WxApiUrl.CUSTOM_MENU_API, "delete", getAccessToken());
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.get(url);
    -        } catch (IOException e) {
    -            log.error("删除微信自定义菜单时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        return true;
    -    }
    -
    -    @Override
    -    public int creatGroup(String name) throws WxRespException {
    -        String url = String.format(WxApiUrl.GROUPS_API, "create", getAccessToken());
    -        String data = "{\"group\":{\"name\":\""+name+"\"}}";
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, data);
    -        } catch (IOException e) {
    -            log.error("创建微信分组时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || !result.contains("group")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        JSONObject tmp = JSON.parseObject(result).getJSONObject("group");
    -
    -        return tmp.getInteger("id");
    -    }
    -
    -    @Override
    -    public List getGroups() throws WxRespException {
    -        String url = String.format(WxApiUrl.GROUPS_API, "get", getAccessToken());
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.get(url);
    -        } catch (IOException e) {
    -            log.error("获取所有分组时失败!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.indexOf("errcode") > -1) {
    -            throw new WxRespException(result);
    -        }
    -
    -        String tmp = JSON.parseObject(result).getString("groups");
    -        List groups = JSON.parseArray(tmp, Group.class);
    -
    -        return groups;
    -    }
    -
    -    @Override
    -    public boolean renGroup(int id, String name) throws WxRespException {
    -        String url = String.format(WxApiUrl.GROUPS_API, "update", getAccessToken());
    -        name = name.length() > 30 ? name.substring(0,30) : name;
    -        String data = "{\"group\":{\"id\":"+id+",\"name\":\""+name+"\"}}";
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, data);
    -        } catch (IOException e) {
    -            log.error("修改分组名称时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        return true;
    -    }
    -
    -    @Override
    -    public int getGroupId(String openId) throws WxRespException {
    -        String url = String.format(WxApiUrl.GROUPS_API, "getid", getAccessToken());
    -        String data = "{\"openid\":\""+openId+"\"}";
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, data);
    -        } catch (IOException e) {
    -            log.error("获取用户所在分组时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.indexOf("errcode") > -1) {
    -            throw new WxRespException(result);
    -        }
    -
    -        int group_id = JSON.parseObject(result).getInteger("groupid");
    -
    -        return group_id;
    -    }
    -
    -    @Override
    -    public boolean move2Group(String openId, int groupId) throws WxRespException {
    -        String url = String.format(WxApiUrl.GROUPS_USER_API, getAccessToken());
    -        String data = "{\"openid\":\""+openId+"\",\"to_groupid\":"+groupId+"}";
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, data);
    -        } catch (IOException e) {
    -            log.error("移动用户分组时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || !result.contains("ok")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        return true;
    -    }
    -
    -    @Override
    -    public FollowList getFollowerList(String nextOpenId) throws WxRespException {
    -        String url = String.format(WxApiUrl.FOLLOWS_USER_API, getAccessToken(), nextOpenId);
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.get(url);
    -        } catch (IOException e) {
    -            log.error("获取关注用户列表时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.indexOf("errcode") > -1) {
    -            throw new WxRespException(result);
    -        }
    -
    -        FollowList followers = JSON.parseObject(result, FollowList.class);
    -        String openid = JSON.parseObject(result).getJSONObject("data").getString("openid");
    -        List openids = JSON.parseArray(openid, String.class);
    -        followers.setOpenIds(openids);
    -        return followers;
    -    }
    -
    -    @Override
    -    public Follower getFollower(String openId, String lang) throws WxRespException {
    -        String url = String.format(WxApiUrl.USER_INFO_API, getAccessToken(), openId, lang);
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.get(url);
    -        } catch (IOException e) {
    -            log.error("获取用户信息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.indexOf("errcode") > -1) {
    -            throw new WxRespException(result);
    -        }
    -
    -        Follower follower = JSON.parseObject(result, Follower.class);
    -
    -        return follower;
    -    }
    -
    -    @Override
    -    public boolean sendCustomerMsg(OutPutMsg msg) throws WxRespException {
    -        String url = String.format(WxApiUrl.CUSTOM_MESSAGE_API, getAccessToken());
    -        String custom_msg = "";
    -        WxMsgType type = WxMsgType.valueOf(msg.getMsgType());
    -        switch (type) {
    -            case text:
    -                custom_msg = JsonMsgBuilder.create().text(msg).build();
    -                break;
    -            case image:
    -                custom_msg = JsonMsgBuilder.create().image(msg).build();
    -                break;
    -            case voice:
    -                custom_msg = JsonMsgBuilder.create().voice(msg).build();
    -                break;
    -            case video:
    -                custom_msg = JsonMsgBuilder.create().video(msg).build();
    -                break;
    -            case music:
    -                custom_msg = JsonMsgBuilder.create().music(msg).build();
    -                break;
    -            case news:
    -                custom_msg = JsonMsgBuilder.create().news(msg).build();
    -                break;
    -            default:
    -                break;
    -        }
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, custom_msg);
    -        } catch (IOException e) {
    -            log.error("发送客服消息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty() 
    -                || !result.contains("ok")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        return true;
    -    }
    -
    -    @Override
    -    public boolean sendTemplateMsg(String openId, String templateId,
    -                                   String topColor, String url, Template... templates) throws WxRespException {
    -        String api_url = String.format(WxApiUrl.TEMPLATE_MESSAGE_API, getAccessToken());
    -        String template_msg = JsonMsgBuilder.create()
    -                .template(openId, templateId, topColor, url, templates)
    -                .build();
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.post(api_url, SimpleHttpReq.APPLICATION_JSON, template_msg);
    -        } catch (IOException e) {
    -            log.error("发送模板消息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty() ||
    -                result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        return true;
    -    }
    -
    -    @Override
    -    public String upMedia(String mediaType, File file) throws WxRespException {
    -        String url = String.format(WxApiUrl.MEDIA_UP_API, mediaType, getAccessToken());
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.upload(url, file);
    -        } catch (IOException e) {
    -            log.error("上传多媒体文件时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                ||result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        String media_id = JSON.parseObject(result).getString("media_id");
    -
    -        return media_id;
    -    }
    -
    -    @Override
    -    public void dlMedia(String mediaId, File file) throws WxRespException {
    -
    -        String url = String.format(WxApiUrl.MEDIA_DL_API, getAccessToken(), mediaId);
    -        // 检查文件夹是否存在
    -        if(!file.exists()) {
    -            String path = file.getAbsolutePath();
    -            String separ = System.getProperties().getProperty("file.separator");
    -            File dir = new File(path.substring(0, path.lastIndexOf(separ)));
    -            dir.mkdirs();
    -        }
    -
    -        try {
    -            SimpleHttpReq.download(url, file);
    -        } catch (IOException e) {
    -            log.error("下载多媒体文件时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -    }
    -
    -    @Override
    -    public String[] upNews(Article2... articles2s) throws WxRespException {
    -        String url = String.format(WxApiUrl.NEWS_UPLOAD_API, getAccessToken());
    -        String upnews_msg = JsonMsgBuilder.create().uploadNews(articles2s).build();
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, upnews_msg);
    -        } catch (IOException e) {
    -            log.error("上传群发图文消息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                ||result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -        JSONObject tmp = JSON.parseObject(result);
    -        String[] results = {
    -                tmp.getString("type"),
    -                tmp.getString("media_id"),
    -                tmp.getString("created_at")
    -        };
    -        return results;
    -    }
    -
    -    @Override
    -    public String[] upVideo(String mediaId,
    -                                String title, String description) throws WxRespException {
    -        String url = String.format(WxApiUrl.MEDIA_UPVIDEO_API, getAccessToken());
    -        String result = "";
    -        String upvideo_msg = JsonMsgBuilder.create().uploadVideo(mediaId,title,description).build();
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, upvideo_msg);
    -        } catch (IOException e) {
    -            log.error("上传群发消息中的视频时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                ||result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -        JSONObject tmp = JSON.parseObject(result);
    -        String[] results = {
    -                tmp.getString("type"),
    -                tmp.getString("media_id"),
    -                tmp.getString("created_at")
    -        };
    -        return results;
    -    }
    -
    -    @Override
    -    public String sendAll(OutPutMsg msg) throws WxRespException {
    -        String group_id = msg.getGroupId();
    -        List to_users = msg.getToUsers();
    -        if (null != group_id
    -                && !group_id.isEmpty()
    -                && !to_users.isEmpty()) {
    -            throw new RuntimeException("群发消息只能选择一种模式");
    -        }
    -
    -        String url = "";
    -        if (to_users.isEmpty()) {
    -           url =  String.format(WxApiUrl.GROUP_NEWS_MESSAGE_API, "sendall", getAccessToken());
    -        } else {
    -            url = String.format(WxApiUrl.GROUP_NEWS_MESSAGE_API, "send", getAccessToken());
    -        }
    -
    -        String result = "";
    -        String send_msg = JsonMsgBuilder.create().sendAll(msg).build();
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, send_msg);
    -        } catch (IOException e) {
    -            log.error("发送群消息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                ||!result.contains("msg_id")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        JSONObject tmp = JSON.parseObject(result);
    -
    -        return tmp.getString("msg_id");
    -    }
    -
    -    @Override
    -    public boolean delSendAll(String msgId) throws WxRespException {
    -        String url = String.format(WxApiUrl.GROUP_NEWS_MESSAGE_API, "delete", getAccessToken());
    -        String result = "";
    -        String del_msg = "{\"msg_id\":"+ msgId +"}";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, del_msg);
    -        } catch (IOException e) {
    -            log.error("删除群发消息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                ||!result.contains("ok")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        return true;
    -    }
    -
    -    @Override
    -    public Map getJsAPISign(String url) throws WxRespException {
    -        // 1.创建JSTICKET
    -        String ticket = mpAct.getJsTicket();
    -        if (null == ticket
    -                || ticket.isEmpty()
    -                || mpAct.getJsExpiresIn() < System.currentTimeMillis()) {
    -            synchronized (mpAct){
    -                refreshJsAPITicket();
    -            }
    -            ticket = mpAct.getJsTicket();
    -        }
    -
    -        // 2.生成签名
    -        String sign_param = "jsapi_ticket=%1$s&noncestr=%2$s×tamp=%3$s&url=%4$s";
    -        String nonce = UUID.randomUUID().toString().toLowerCase();
    -        long time = System.currentTimeMillis() / 1000;
    -        String sign = null;
    -        try {
    -            sign = SHA1.calculate(String.format(sign_param, ticket, nonce, time, url));
    -        } catch (AesException e) {
    -            log.error("生成JSTICKET的签名时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        // 3.返回参数与值
    -        Map sign_map = new HashMap<>();
    -        sign_map.put("url", url);
    -        sign_map.put("ticket", ticket);
    -        sign_map.put("nonce", nonce);
    -        sign_map.put("timestamp", time);
    -        sign_map.put("sign", sign);
    -        return sign_map;
    -    }
    -
    -    @Override
    -    public void refreshJsAPITicket() throws WxRespException {
    -
    -        String token_url = WxApiUrl.JSAPI_TICKET_URL+getAccessToken();
    -
    -        String result = "";
    -        try {
    -            result = SimpleHttpReq.get(token_url);
    -        } catch (IOException e) {
    -            log.error("刷新JSTICKET时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || !result.contains("ok")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        mpAct.createJsTicket(result);
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxBase.java b/src/main/java/org/elkan1788/osc/weixin/mp/core/WxBase.java
    deleted file mode 100644
    index c87c891..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxBase.java
    +++ /dev/null
    @@ -1,469 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.core;
    -
    -import com.qq.weixin.mp.aes.AesException;
    -import com.qq.weixin.mp.aes.WXBizMsgCrypt;
    -import org.elkan1788.osc.weixin.mp.MPSDK4J;
    -import org.elkan1788.osc.weixin.mp.commons.WxEventType;
    -import org.elkan1788.osc.weixin.mp.commons.WxMsgType;
    -import org.elkan1788.osc.weixin.mp.util.StreamTool;
    -import org.elkan1788.osc.weixin.mp.util.XMLHandler;
    -import org.elkan1788.osc.weixin.mp.util.XmlMsgBuilder;
    -import org.elkan1788.osc.weixin.mp.vo.MPAct;
    -import org.elkan1788.osc.weixin.mp.vo.OutPutMsg;
    -import org.elkan1788.osc.weixin.mp.vo.ReceiveMsg;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -import org.xml.sax.SAXException;
    -
    -import javax.servlet.http.HttpServletRequest;
    -import javax.xml.parsers.ParserConfigurationException;
    -import javax.xml.parsers.SAXParser;
    -import javax.xml.parsers.SAXParserFactory;
    -import java.io.IOException;
    -import java.io.InputStream;
    -import java.io.UnsupportedEncodingException;
    -
    -/**
    - * 微信普通消息互动
    - *
    - * @author 凡梦星尘(senhuili@mdc.cn)
    - * @version 1.2.0
    - * @since 2014/11/8
    - */
    -public class WxBase {
    -
    -    private static final Logger log = LoggerFactory.getLogger(WxBase.class);
    -    // 消息模式(默认明文)
    -    private boolean aesEncrypt = false;
    -    // 定义公众号信息
    -    private MPAct mpAct;
    -    // 微信加密
    -    private WXBizMsgCrypt wxInMsgCrt;
    -    // XML解析准备
    -    private SAXParserFactory factory = SAXParserFactory.newInstance();
    -    private SAXParser xmlParser;
    -    private XMLHandler xmlHandler = new XMLHandler();
    -    // 微信交互参数
    -    private String signature;
    -    private String msgSignature;
    -    private String timeStamp;
    -    private String nonce;
    -    private String echostr;
    -    // 微信消息流
    -    private InputStream wxInMsg;
    -    // 微信消息处理器
    -    private WxHandler wxHandler;
    -    // 解析/响应微信消息
    -    private ReceiveMsg rm;
    -    private OutPutMsg om;    
    -
    -    public WxBase() {
    -        try {
    -            xmlParser = factory.newSAXParser();
    -        } catch (ParserConfigurationException e) {
    -            log.error("SAX解析配置文件失败!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        } catch (SAXException e) {
    -            log.error("SAX异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -    }
    -
    -    /**
    -     * 微信基础功能参数初始化
    -     *
    -     * @param req 请求
    -     */
    -    public void init(HttpServletRequest req) {
    -        // 请求编码设置
    -        try {
    -            req.setCharacterEncoding("UTF-8");
    -        } catch (UnsupportedEncodingException e) {
    -            log.error("设置微信服务器请求编辑时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -        // 获取各请求参数值
    -        String sign = req.getParameter("signature");
    -        String time = req.getParameter("timestamp");
    -        String nonce = req.getParameter("nonce");
    -        String echo = req.getParameter("echostr");
    -        String encrypt = req.getParameter("encrypt_type");
    -        // 设置各参数值
    -        setSignature(sign);
    -        setTimeStamp(time);
    -        setNonce(nonce);
    -        setEchostr(echo);
    -        // 判断是否启用AES加密
    -        if ("aes".equals(encrypt)) {
    -            String msgSign = req.getParameter("msg_signature");
    -            setMsgSignature(msgSign);
    -            setAesEncrypt(true);
    -        }
    -        // 读取微信消息
    -        try {
    -            InputStream wxInMsg = req.getInputStream();
    -            this.wxInMsg = wxInMsg;
    -        } catch (IOException e) {
    -            log.error("接收微信消息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -    }
    -
    -    /**
    -     * 微信URL接入校验
    -     *
    -     * @return 随机字符
    -     * @throws AesException
    -     */
    -    public String check() throws AesException {
    -
    -        boolean check = wxHandler.check(this.mpAct.getToken(),
    -                this.signature, this.timeStamp, this.nonce);
    -        return check ? echostr : "";
    -    }
    -
    -    /**
    -     * 微信消息处理
    -     *
    -     * @return 回复消息
    -     * @throws Exception
    -     */
    -    public String handler() throws Exception {
    -        // 释放缓存
    -        clear();
    -        String reply = "";
    -        this.rm = convert2VO(this.wxInMsg);
    -        // 处理消息
    -        String msg_type = this.rm.getMsgType();
    -        if ("event".equals(msg_type)) {
    -            this.om = handlerEvent();
    -        } else {
    -            this.om = handlerMsg();
    -        }
    -        // 输出消息
    -        if (null != this.om) {
    -            reply = this.convert2XML(this.om);
    -        }
    -        return reply;
    -    }
    -
    -    /**
    -     * 处理普通的消息
    -     *
    -     * @return 回复消息实体
    -     * @throws Exception
    -     */
    -    private OutPutMsg handlerMsg() throws Exception {
    -        if (log.isInfoEnabled()) {
    -            log.info("[MPSDK4J-{}]处理普通消息...", MPSDK4J.version());
    -        }
    -        OutPutMsg om = null;
    -        WxMsgType type = WxMsgType.valueOf(this.rm.getMsgType());
    -        switch (type) {
    -            case text:
    -                om = wxHandler.text(this.rm);
    -                break;
    -            case image:
    -                om = wxHandler.image(this.rm);
    -                break;
    -            case voice:
    -                om = wxHandler.voice(this.rm);
    -                break;
    -            case video:
    -                om = wxHandler.video(this.rm);
    -                break;
    -            case location:
    -                om = wxHandler.location(this.rm);
    -                break;
    -            case link:
    -                om = wxHandler.link(this.rm);
    -                break;
    -            default:
    -                om = wxHandler.def(this.rm);
    -                break;
    -        }
    -        return om;
    -    }
    -
    -    /**
    -     * 处理事件推送消息
    -     *
    -     * @return 回复消息实体
    -     * @throws Exception
    -     */
    -    private OutPutMsg handlerEvent() throws Exception {
    -        if (log.isInfoEnabled()) {
    -            log.info("[MPSDK4J-{}]处理事件推送消息...", MPSDK4J.version());
    -        }
    -        OutPutMsg om = null;
    -        WxEventType type = WxEventType.valueOf(this.rm.getEvent());
    -        switch (type) {
    -            case subscribe:
    -                om = wxHandler.eSub(this.rm);
    -                break;
    -            case unsubscribe:
    -                wxHandler.eUnSub(this.rm);
    -                break;
    -            case SCAN:
    -                om = wxHandler.eScan(this.rm);
    -                break;
    -            case CLICK:
    -                om = wxHandler.eClick(this.rm);
    -                break;
    -            case VIEW:
    -                wxHandler.eView(this.rm);
    -                break;
    -            case scancode_push:
    -                om = wxHandler.eScanCodePush(this.rm);
    -                break;
    -            case scancode_waitmsg:
    -                om = wxHandler.eScanCodeWait(this.rm);
    -                break;
    -            case pic_sysphoto:
    -                om = wxHandler.ePicSysPhoto(this.rm);
    -                break;
    -            case pic_photo_or_album:
    -                om = wxHandler.ePicPhotoOrAlbum(this.rm);
    -                break;
    -            case pic_weixin:
    -                om = wxHandler.ePicWeixin(this.rm);
    -                break;
    -            case location_select:
    -                om = wxHandler.eLocationSelect(this.rm);
    -                break;
    -            case LOCATION:
    -                om = wxHandler.eLocation(this.rm);
    -                break;
    -            case TEMPLATESENDJOBFINISH:
    -                wxHandler.eTemplateFinish(this.rm);
    -                break;
    -            case MASSSENDJOBFINISH:
    -                wxHandler.eSendJobFinish(this.rm);
    -                break;
    -            default:
    -                om = wxHandler.def(this.rm);
    -                break;
    -        }
    -        return om;
    -    }
    -
    -
    -    /**
    -     * 处理微信开放平台的推送消息
    -     *
    -     * @return 默认返回"success"
    -     * @throws Exception
    -     */
    -    public String handlerPush() throws Exception {
    -        if (log.isInfoEnabled()) {
    -            log.info("[MPSDK4J-{}]处理开放平台推送消息...", MPSDK4J.version());
    -        }
    -        this.rm = convert2VO(this.wxInMsg);
    -        String info_type = this.rm.getInfoType();
    -        switch (info_type) {
    -            case "component_verify_ticket":
    -                wxHandler.eComponentVerifyTicket(this.rm);
    -                break;
    -            case "unauthorized":
    -                wxHandler.eUnAuthorizerMP(this.rm);
    -                break;
    -            default:
    -                break;
    -        }
    -        return "success";
    -    }
    -
    -    /**
    -     * 将微信消息转换成接收消息VO对象
    -     *
    -     * @param msg 微信消息输入流
    -     * @return 接收消息VO对象
    -     * @throws ParserConfigurationException
    -     * @throws SAXException
    -     * @throws IOException
    -     * @throws AesException
    -     */
    -    private ReceiveMsg convert2VO(InputStream msg)
    -            throws ParserConfigurationException,
    -            SAXException,
    -            IOException,
    -            AesException {
    -
    -        if (!this.aesEncrypt) { // 明文
    -            this.xmlParser.parse(msg, this.xmlHandler);
    -        } else {// 密文
    -            String dcrp_msg = this.wxInMsgCrt.decryptMsg(this.msgSignature,
    -                    this.timeStamp, this.nonce, StreamTool.toString(msg));
    -            this.wxInMsg = StreamTool.toStream(dcrp_msg);
    -            this.xmlParser.parse(this.wxInMsg, this.xmlHandler);
    -        }
    -
    -        ReceiveMsg rm = this.xmlHandler.getMsgVO();
    -        this.xmlHandler.clear();
    -        // 调试信息
    -        if (log.isInfoEnabled()) {
    -            log.info("[MPSDK4J-{}]接收到微信消息[{},{}]:...",
    -                    MPSDK4J.version(),
    -                    rm.getMsgId(), rm.getCreateTime());
    -            log.info("{}", rm);
    -        }
    -
    -        return rm;
    -    }
    -
    -    /**
    -     * 将VO对象转换成XML消息体
    -     *
    -     * @param msg 输出消息VO对象
    -     * @return 微信消息
    -     * @throws AesException
    -     */
    -    private String convert2XML(OutPutMsg msg) throws AesException {
    -
    -        String reply_msg = "";
    -
    -        // 自定义内容回复
    -        if (null != msg.getCustomReply()
    -                && !msg.getCustomReply().isEmpty()) {
    -            return  msg.getCustomReply();
    -        }
    -
    -        // 获取消息类型
    -        WxMsgType msg_type = WxMsgType.valueOf(msg.getMsgType());
    -        switch (msg_type) {
    -            case text:
    -                reply_msg = XmlMsgBuilder.create().text(msg).build();
    -                break;
    -            case image:
    -                reply_msg = XmlMsgBuilder.create().image(msg).build();
    -                break;
    -            case voice:
    -                reply_msg = XmlMsgBuilder.create().vioce(msg).build();
    -                break;
    -            case video:
    -                reply_msg = XmlMsgBuilder.create().video(msg).build();
    -                break;
    -            case music:
    -                reply_msg = XmlMsgBuilder.create().music(msg).build();
    -                break;
    -            case news:
    -                reply_msg = XmlMsgBuilder.create().news(msg).build();
    -                break;
    -            default:
    -                break;
    -        }
    -
    -        // 调试信息
    -        if (log.isInfoEnabled()) {
    -            log.info("[MPSDK4J-{}]微信回复消息[{},{}]...",
    -                    MPSDK4J.version(),
    -                    msg.getMsgId(), msg.getCreateTime());
    -            log.info(reply_msg);
    -        }
    -
    -        if (this.aesEncrypt) {// 加密
    -            reply_msg = this.wxInMsgCrt.encryptMsg(reply_msg, this.timeStamp, this.nonce);
    -        }
    -
    -        return reply_msg;
    -    }
    -
    -    private void clear() {
    -        if (null != this.rm) {
    -            this.rm = null;
    -        }
    -        if (null != this.om) {
    -            this.om = null;
    -        }
    -    }
    -
    -    public boolean isAesEncrypt() {
    -        return aesEncrypt;
    -    }
    -
    -    public void setAesEncrypt(boolean aesEncrypt) {
    -        this.aesEncrypt = aesEncrypt;
    -        if (aesEncrypt) {
    -            try {
    -                this.wxInMsgCrt = new WXBizMsgCrypt(this.mpAct.getToken(),
    -                        this.mpAct.getAESKey(), this.mpAct.getAppId());
    -            } catch (AesException e) {
    -                log.error("创建AES加密失败!!!");
    -                log.error(e.getLocalizedMessage(), e);
    -                this.wxInMsgCrt = null;
    -            }
    -        }
    -    }
    -
    -    public MPAct getMpAct() {
    -        return mpAct;
    -    }
    -
    -    public void setMpAct(MPAct mpAct) {
    -        this.mpAct = mpAct;
    -        if (log.isInfoEnabled()) {
    -            log.info("微信公众号信息...");
    -            log.info("{}", this.mpAct);
    -        }
    -    }
    -
    -    public XMLHandler getXmlHandler() {
    -        return xmlHandler;
    -    }
    -
    -    public String getSignature() {
    -        return signature;
    -    }
    -
    -    public void setWxHandler(WxHandler wxHandler) {
    -        this.wxHandler = wxHandler;
    -    }
    -
    -    public void setSignature(String signature) {
    -        this.signature = signature;
    -    }
    -
    -    public String getMsgSignature() {
    -        return msgSignature;
    -    }
    -
    -    public void setMsgSignature(String msgSignature) {
    -        this.msgSignature = msgSignature;
    -    }
    -
    -    public String getTimeStamp() {
    -        return timeStamp;
    -    }
    -
    -    public void setTimeStamp(String timeStamp) {
    -        this.timeStamp = timeStamp;
    -    }
    -
    -    public String getNonce() {
    -        return nonce;
    -    }
    -
    -    public void setNonce(String nonce) {
    -        this.nonce = nonce;
    -    }
    -
    -    public String getEchostr() {
    -        return echostr;
    -    }
    -
    -    public void setEchostr(String echostr) {
    -        this.echostr = echostr;
    -    }
    -
    -    public void setWxInMsg(InputStream wxInMsg) {
    -        this.wxInMsg = wxInMsg;
    -    }
    -
    -    public ReceiveMsg getReceiveMsg() {
    -        return rm;
    -    }
    -
    -    public OutPutMsg getOutPutMsg() {
    -        return om;
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxDefaultHandler.java b/src/main/java/org/elkan1788/osc/weixin/mp/core/WxDefaultHandler.java
    deleted file mode 100644
    index 5ed427e..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxDefaultHandler.java
    +++ /dev/null
    @@ -1,362 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.core;
    -
    -import com.qq.weixin.mp.aes.AesException;
    -import com.qq.weixin.mp.aes.SHA1;
    -import org.elkan1788.osc.weixin.mp.commons.WxMsgType;
    -import org.elkan1788.osc.weixin.mp.vo.OutPutMsg;
    -import org.elkan1788.osc.weixin.mp.vo.PicInfo;
    -import org.elkan1788.osc.weixin.mp.vo.ReceiveMsg;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -/**
    - * 默认的微信消息处理器
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/12/05
    - * @version 1.0.0
    - */
    -public class WxDefaultHandler implements WxHandler {
    -
    -    private static final Logger log = LoggerFactory.getLogger(WxDefaultHandler.class);
    -
    -    @Override
    -    public boolean check(String token,
    -                        String signature,
    -                        String timestamp,
    -                        String nonce) throws AesException {
    -
    -        if (null == signature
    -                || signature.length() > 128
    -                || null == timestamp
    -                || timestamp.length() > 128
    -                || null == nonce
    -                || nonce.length() > 128) {
    -            log.error("验证签名参数失败!!!");
    -            log.error("signature={},timestamp={},nonce={}", signature, timestamp, nonce);
    -            return false;
    -        }
    -
    -        if (log.isInfoEnabled()) {
    -           log.info("微信接入URL验证成功...");
    -           log.info("signature={},timestamp={},nonce={}", signature, timestamp, nonce);
    -        }
    -
    -        String s = SHA1.calculate(token, timestamp, nonce);
    -        if (s.equals(signature)){
    -            return true;
    -        }
    -
    -        return false;
    -    }
    -
    -
    -    @Override
    -    public OutPutMsg def(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        om.setContent("新的功能消息,消息类型为:"+rm.getMsgType());
    -        if (log.isInfoEnabled()) {
    -            log.info("微信新类型消息!!!");
    -            log.info("msgid={}, from={},to={},msgtype={}", rm.getMsgId(),
    -                    rm.getFromUserName(), rm.getTotalCnt(), rm.getMsgType());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg text(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(rm.getMsgType());
    -        om.setContent(rm.getContent() + "\n您的消息已经收到[微笑]");
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到微信文本消息...");
    -            log.info("msgid={}, from={}, to={}, content={}", rm.getMsgId(),
    -                    rm.getFromUserName(), rm.getToUserName(), rm.getContent());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg image(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(rm.getMsgType());
    -        om.setMediaId(rm.getMediaId());
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到微信图像消息...");
    -            log.info("msgid={}, from={}, to={}, mediaid={}", rm.getMsgId(),
    -                    rm.getFromUserName(), rm.getToUserName(), rm.getMediaId());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg voice(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        if (null != rm.getRecognition()) {
    -            om.setMsgType(WxMsgType.text.name());
    -            om.setContent("您的语音消息已接收.[微笑]\n内容为:"+rm.getRecognition());
    -        } else {
    -            om.setMsgType(rm.getMsgType());
    -            om.setMediaId(rm.getMediaId());
    -        }
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到音频消息...");
    -            log.info("msgid={}, from={}, to={}, mediaid={}, trans={}", rm.getMsgId(),
    -                    rm.getFromUserName(), rm.getToUserName(), rm.getMediaId(), rm.getRecognition());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg video(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(rm.getMsgType());
    -        om.setMediaId(rm.getMediaId());
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到视频消息...");
    -            log.info("msgid={}, from={}, to={}, mediaid={}, thumbmid={}", rm.getMsgId(),
    -                    rm.getFromUserName(), rm.getToUserName(), rm.getMediaId(), rm.getThumbMediaId());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg location(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        om.setContent("您当前的位置:"+rm.getLabel()+
    -                ",坐标:["+rm.getLatitude()+","+rm.getLongitude()+
    -                "],地图缩放级别:"+rm.getScale());
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到地理位置消息...");
    -            log.info("msgid={}, from={}, to={}, x={}, y={}, scale={}, label={}",
    -                    rm.getMsgId(), rm.getFromUserName(), rm.getToUserName(),
    -                    rm.getLatitude(), rm.getLongitude(), rm.getScale(), rm.getLabel());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg link(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        om.setContent(rm.getTitle()+"\n点击打开");
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到链接消息...");
    -            log.info("msgid={}, from={}, to={}, title={}, desc={}, url={}",
    -                    rm.getMsgId(), rm.getFromUserName(), rm.getToUserName(),
    -                    rm.getTitle(), rm.getDescription(), rm.getUrl());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg eClick(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        om.setContent("MENU_CLICK:"+rm.getEventKey());
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到菜单点击消息...");
    -            log.info("from={}, to={}, event={}, key={}",
    -                    rm.getFromUserName(), rm.getToUserName(), rm.getEvent(), rm.getEventKey());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public void eView(ReceiveMsg rm) {
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到菜单视图跳转消息...");
    -            log.info("from={}, to={}, event={}, key={}",
    -                    rm.getFromUserName(), rm.getToUserName(), rm.getEvent(), rm.getEventKey());
    -        }
    -    }
    -
    -    @Override
    -    public OutPutMsg eSub(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        om.setContent("做人最重要的是要有自信,记得每天起床时,在镜子前对自己说,你很好,你可以的,树立生活自信,你可以的!");
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到订阅消息...");
    -            log.info("from={}, to={}, event={}",
    -                    rm.getFromUserName(), rm.getToUserName(), rm.getEvent());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public void eUnSub(ReceiveMsg rm) {
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到退订消息...");
    -            log.info("from={}, to={}, event={}",
    -                    rm.getFromUserName(), rm.getToUserName(), rm.getEvent());
    -        }
    -    }
    -
    -    @Override
    -    public OutPutMsg eScan(ReceiveMsg rm) {
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到扫描消息...");
    -            log.info("msgid={}, from={}, to={}, event={}, key={}, ticket={}",
    -                    rm.getMsgId(), rm.getFromUserName(), rm.getToUserName(),
    -                    rm.getEvent(), rm.getEventKey(), rm.getTicket());
    -        }
    -        return null;
    -    }
    -
    -    @Override
    -    public OutPutMsg eLocation(ReceiveMsg rm) {
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到地理位置消息...");
    -            log.info("msgid={}, from={}, to={}, x={}, y={}, precision={}",
    -                    rm.getMsgId(), rm.getFromUserName(), rm.getToUserName(),
    -                    rm.getLatitude(), rm.getLongitude(), rm.getPrecision());
    -        }
    -        return null;
    -    }
    -
    -    @Override
    -    public OutPutMsg eScanCodePush(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        String content = "您此次用二维码扫描菜单["+rm.getEventKey()+"],扫描结果为: "+rm.getScanResult();
    -        om.setContent(content);
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到二维码扫描事件消息...");
    -            log.info("msgid={}, from={}, to={}, eventKey={}, scantype={}, scanresult={}",
    -                    rm.getMsgId(), rm.getFromUserName(),
    -                    rm.getToUserName(), rm.getEventKey(),
    -                    rm.getScanType(), rm.getScanResult());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg eScanCodeWait(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        String content = "您此次用扫描等待菜单["+rm.getEventKey()+"],扫描结果为: "+rm.getScanResult();
    -        om.setContent(content);
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到扫码推事件且弹出“消息接收中”提示消息...");
    -            log.info("msgid={}, from={}, to={}, eventKey={}, scantype={}, scanresult={}",
    -                    rm.getMsgId(), rm.getFromUserName(),
    -                    rm.getToUserName(), rm.getEventKey(),
    -                    rm.getScanType(), rm.getScanResult());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg ePicSysPhoto(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        String content = "您此次用系统拍照["+rm.getEventKey()+"]共发了"+rm.getCount()+"张图片,图片的MD5值为: ";
    -        for (PicInfo pic : rm.getPicList()) {
    -            content += pic.getPicMd5Sum() + ", ";
    -        }
    -        om.setContent(content.substring(0, content.lastIndexOf(",")));
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到菜单弹出系统拍照发图消息...");
    -            log.info("msgid={}, from={}, to={}, eventkey={} count={}, picmd5sum={}",
    -                    rm.getMsgId(), rm.getFromUserName(),
    -                    rm.getToUserName(), rm.getEventKey(),
    -                    rm.getCount(), String.valueOf(rm.getPicList()));
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg ePicPhotoOrAlbum(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        String content = "您此次用拍照或相册["+rm.getEventKey()+"]共发了"+rm.getCount()+"张图片,图片的MD5值为: ";
    -        for (PicInfo pic : rm.getPicList()) {
    -            content += pic.getPicMd5Sum() + ", ";
    -        }
    -        om.setContent(content.substring(0, content.lastIndexOf(",")));
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到菜单弹出拍照或者相册发图消息...");
    -            log.info("msgid={}, from={}, to={}, eventkey={}, count={}, picmd5sum={}",
    -                    rm.getMsgId(), rm.getFromUserName(),
    -                    rm.getToUserName(), rm.getEventKey(),
    -                    rm.getCount(), String.valueOf(rm.getPicList()));
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg ePicWeixin(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        String content = "您此次用微信相册["+rm.getEventKey()+"]共发了"+rm.getCount()+"张图片,图片的MD5值为: ";
    -        for (PicInfo pic : rm.getPicList()) {
    -            content += pic.getPicMd5Sum() + ", ";
    -        }
    -        om.setContent(content.substring(0, content.lastIndexOf(",")));
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到菜单微信相册发图消息...");
    -            log.info("msgid={}, from={}, to={}, eventkey={}, count={}, picmd5sum={}",
    -                    rm.getMsgId(), rm.getFromUserName(),
    -                    rm.getToUserName(), rm.getEventKey(),
    -                    rm.getCount(), String.valueOf(rm.getPicList()));
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public OutPutMsg eLocationSelect(ReceiveMsg rm) {
    -        OutPutMsg om = new OutPutMsg(rm);
    -        om.setMsgType(WxMsgType.text.name());
    -        om.setContent("菜单值:"+rm.getEventKey()+",您当前的位置:"+rm.getLabel()+
    -                ",坐标:["+rm.getLatitude()+","+rm.getLongitude()+
    -                "],地图缩放级别:"+rm.getScale()+",朋友圈:"+rm.getPoiName());
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到菜单地理位置消息...");
    -            log.info("msgid={}, from={}, eventkey={}, to={}, " +
    -                            "x={}, y={}, precision={}, label={}, poiname={}",
    -                    rm.getMsgId(), rm.getFromUserName(),
    -                    rm.getEventKey(), rm.getToUserName(),
    -                    rm.getLatitude(), rm.getLongitude(),
    -                    rm.getPrecision(), rm.getLabel(), rm.getPoiName());
    -        }
    -        return om;
    -    }
    -
    -    @Override
    -    public void eTemplateFinish(ReceiveMsg rm) {
    -       if (log.isInfoEnabled()) {
    -            log.info("接收到模板推送消息...");
    -            log.info("from={}, to={}, msgid={}, status={}",
    -                    rm.getFromUserName(), rm.getToUserName(), rm.getMsgId(), rm.getStatus());
    -        }
    -    }
    -
    -    @Override
    -    public void eSendJobFinish(ReceiveMsg rm) {
    -        if (log.isInfoEnabled()) {
    -            log.info("接收到群发推送消息...");
    -            log.info("from={}, to={}, msgid={}, status={}, total={}, filter={}, sent={}, err={}",
    -                    rm.getFromUserName(), rm.getToUserName(), rm.getMsgId(), rm.getStatus(),
    -                    rm.getTotalCnt(), rm.getFilterCnt(), rm.getSentCnt(), rm.getErrorCnt());
    -        }
    -    }
    -
    -    @Override
    -    public void eComponentVerifyTicket(ReceiveMsg rm) {
    -        if (log.isDebugEnabled()) {
    -            log.info("接收到微信开放平台推送的组件Ticket消息...");
    -            log.info("appid={}, infotype={}, ticket={}", rm.getAppId(), rm.getInfoType(), rm.getTicket());
    -        }
    -    }
    -
    -    @Override
    -    public void eUnAuthorizerMP(ReceiveMsg rm) {
    -        if (log.isDebugEnabled()) {
    -            log.info("接收到微信开放平台推送的公众号取消授权消息...");
    -            log.info("appid={}, infotype={}, unauthorizerappid={}", rm.getAppId(), rm.getInfoType(), rm.getUnAuthAppid());
    -        }
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxHandler.java b/src/main/java/org/elkan1788/osc/weixin/mp/core/WxHandler.java
    deleted file mode 100644
    index 11b62a1..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxHandler.java
    +++ /dev/null
    @@ -1,211 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.core;
    -
    -import com.qq.weixin.mp.aes.AesException;
    -import org.elkan1788.osc.weixin.mp.vo.OutPutMsg;
    -import org.elkan1788.osc.weixin.mp.vo.ReceiveMsg;
    -import org.xml.sax.SAXException;
    -
    -import javax.xml.parsers.ParserConfigurationException;
    -import java.io.IOException;
    -import java.io.InputStream;
    -
    -/**
    - * 微信消息处理接口
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/12/05
    - * @version 1.0.0
    - */
    -public interface WxHandler {
    -
    -    /**
    -     * 微信接入时URL验证
    -     *
    -     * @param token     密钥
    -     * @param signature 签名
    -     * @param timestamp 时间戳
    -     * @param nonce     随机字符
    -     * @return  true或false
    -     * @throws AesException 签名异常
    -     */
    -    boolean check(String token,
    -                 String signature,
    -                 String timestamp,
    -                 String nonce) throws AesException;
    -
    -    /**
    -     * 处理微信新功能的消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg def(ReceiveMsg rm);
    -
    -    /**
    -     * 处理文本消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg text(ReceiveMsg rm);
    -
    -    /**
    -     * 处理图像消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg image(ReceiveMsg rm);
    -
    -    /**
    -     * 处理音频消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg voice(ReceiveMsg rm);
    -
    -    /**
    -     * 处理视频消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg video(ReceiveMsg rm);
    -
    -    /**
    -     * 处理主动上传地理位置消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg location(ReceiveMsg rm);
    -
    -    /**
    -     * 处理链接消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg link(ReceiveMsg rm);
    -
    -    /**
    -     * 处理菜单点击事件消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg eClick(ReceiveMsg rm);
    -
    -    /**
    -     * 处理菜单视图事件消息
    -     *
    -     * @param rm    接收到的消息
    -     */
    -    void eView(ReceiveMsg rm);
    -
    -    /**
    -     * 处理订阅事件消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg eSub(ReceiveMsg rm);
    -
    -    /**
    -     * 处理退订事件消息
    -     *
    -     * @param rm    接收到的消息
    -     */
    -    void eUnSub(ReceiveMsg rm);
    -
    -    /**
    -     * 处理扫描事件消息
    -     *
    -     * @param rm    接收到的消息
    -     */
    -    OutPutMsg eScan(ReceiveMsg rm);
    -
    -    /**
    -     * 处理自动上传地理事件消息
    -     *
    -     * @param rm    接收到的消息
    -     */
    -    OutPutMsg eLocation(ReceiveMsg rm);
    -
    -    /**
    -     * 处理二维码扫描事件消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg eScanCodePush(ReceiveMsg rm);
    -
    -    /**
    -     * 扫码推事件且弹出“消息接收中”提示框
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg eScanCodeWait(ReceiveMsg rm);
    -
    -    /**
    -     * 处理弹出系统拍照发图的事件推送
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg ePicSysPhoto(ReceiveMsg rm);
    -
    -    /**
    -     * 处理弹出拍照或者相册发图的事件推送
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg ePicPhotoOrAlbum(ReceiveMsg rm);
    -
    -    /**
    -     * 处理弹出微信相册发图器的事件推送
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg ePicWeixin(ReceiveMsg rm);
    -
    -    /**
    -     * 处理弹出地理位置选择器的事件推送消息
    -     *
    -     * @param rm    接收到的消息
    -     * @return  回复消息
    -     */
    -    OutPutMsg eLocationSelect(ReceiveMsg rm);
    -
    -    /**
    -     * 处理模板发送事件消息
    -     *
    -     * @param rm    接收到的消息
    -     */
    -    void eTemplateFinish(ReceiveMsg rm);
    -
    -    /**
    -     * 处理群发消息事件消息
    -     *
    -     * @param rm    接收到的消息
    -     */
    -    void eSendJobFinish(ReceiveMsg rm);
    -
    -    /**
    -     * 处理微信开放平台推送的Ticket事件消息
    -     * (只要回复"success"即可)
    -     * @param rm    接收到的消息
    -     */
    -    void eComponentVerifyTicket(ReceiveMsg rm);
    -
    -    /**
    -     * 处理微信开放平台推送的取消授权公众号事件消息
    -     * (只要回复"success"即可)
    -     * @param rm    接收到的消息
    -     */
    -    void eUnAuthorizerMP(ReceiveMsg rm);
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxOpenApi.java b/src/main/java/org/elkan1788/osc/weixin/mp/core/WxOpenApi.java
    deleted file mode 100644
    index 42e135b..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxOpenApi.java
    +++ /dev/null
    @@ -1,113 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.core;
    -
    -import org.elkan1788.osc.weixin.mp.exception.WxRespException;
    -import org.elkan1788.osc.weixin.mp.vo.AuthInfo;
    -import org.elkan1788.osc.weixin.mp.vo.MPAct;
    -
    -/**
    - * 微信开放平台API接口设计
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2015/1/7
    - * @version 1.0.0
    - */
    -public interface WxOpenApi {
    -
    -    /**
    -     * 设置公众号信息
    -     * @param mpAct 公众号信息
    -     */
    -    void setMpAct(MPAct mpAct);
    -
    -    /**
    -     * 设置凭证
    -     * @param ticket    凭证
    -     */
    -    void setTicket(String ticket);
    -
    -    /**
    -     * 获取公众号服务组件的令牌
    -     * @return 令牌
    -     * @throws WxRespException
    -     */
    -    String getComponentToken() throws WxRespException;
    -
    -    /**
    -     * 刷新公众号服务组件的令牌
    -     * @throws WxRespException
    -     */
    -    void refreshComponentToken() throws WxRespException;
    -
    -    /**
    -     * 获取公众号预授权码
    -     * @return  预授权码
    -     * @throws WxRespException
    -     */
    -    String getPreAuthCode() throws WxRespException;
    -
    -    /**
    -     * 创建公众号预授权码
    -     * @throws WxRespException
    -     */
    -    void createPreAuthCode() throws WxRespException;
    -
    -    /**
    -     * 使用授权码换取授权公众号的授权信息
    -     * 并换取authorizer_access_token和authorizer_refresh_token
    -     * @param authCode  授权码
    -     * @return  授权信息
    -     * @throws WxRespException
    -     */
    -    AuthInfo queryAuth(String authCode) throws WxRespException;
    -
    -    /**
    -     * 获取授权公众号的令牌
    -     *
    -     * @param authAppId         授权方appid
    -     * @param authRefreshToken  授权方的刷新令牌
    -     * @return  令牌
    -     * @throws WxRespException
    -     */
    -    String getAuthAccessToken(String authAppId, String authRefreshToken) throws WxRespException;
    -
    -    /**
    -     * 刷新授权公众号的令牌
    -     *
    -     * @param authAppId         授权方appid
    -     * @param authRefreshToken  授权方的刷新令牌
    -     * @throws WxRespException
    -     */
    -    void refreshAuthAccessToken(String authAppId, String authRefreshToken) throws WxRespException;
    -
    -    /**
    -     * 获取授权方的账户信息
    -     *
    -     * @param authAppId 授权方appid
    -     * @return  权方的账户信息
    -     * @throws WxRespException
    -     */
    -    AuthInfo getAuthorizerInfo(String authAppId) throws WxRespException;
    -
    -    /**
    -     * 获取授权方的选项设置信息
    -     *
    -     * @param authAppId     授权方appid
    -     * @param optionName    选项名称(location_report,voice_recognize,customer_service)
    -     * @return  选项值
    -     * @throws WxRespException
    -     */
    -    String getAuthorizerOption(String authAppId, String optionName) throws WxRespException;
    -
    -    /**
    -     * 设置授权方的选项设置信息
    -     *
    -     * @param authAppId     授权方appid
    -     * @param optionName    设置的选项名称
    -     * @param optionValue   设置的选项值
    -     *                      ocation_report(地理位置上报) 0关闭 1进入会话时上报 2每5s上报
    -     *                      voice_recognize(语音识别) 0关闭 1开启
    -     *                      customer_service(客服开关) 0关闭 1开启
    -     * @throws WxRespException
    -     */
    -    void setAuthorizerOption(String authAppId, String optionName, String optionValue) throws WxRespException;
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxOpenApiImpl.java b/src/main/java/org/elkan1788/osc/weixin/mp/core/WxOpenApiImpl.java
    deleted file mode 100644
    index 859b4a3..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/core/WxOpenApiImpl.java
    +++ /dev/null
    @@ -1,273 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.core;
    -
    -import com.alibaba.fastjson.JSON;
    -import com.alibaba.fastjson.JSONObject;
    -import org.elkan1788.osc.weixin.mp.commons.WxApiUrl;
    -import org.elkan1788.osc.weixin.mp.exception.WxRespException;
    -import org.elkan1788.osc.weixin.mp.util.SimpleHttpReq;
    -import org.elkan1788.osc.weixin.mp.vo.AuthInfo;
    -import org.elkan1788.osc.weixin.mp.vo.MPAct;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -import java.io.IOException;
    -import java.util.HashMap;
    -import java.util.Map;
    -
    -/**
    - * 微信开放平台API接口设计
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2015/1/7
    - * @version 1.0.0
    - */
    -public class WxOpenApiImpl implements WxOpenApi {
    -
    -    private static final Logger log = LoggerFactory.getLogger(WxOpenApiImpl.class);
    -
    -    private MPAct mpAct;
    -
    -    private String ticket;
    -
    -    public WxOpenApiImpl() {
    -    }
    -
    -    /**
    -     * 微信开放平台接口构建
    -     *
    -     * @param mpAct     服务组件公众号信息
    -     * @param ticket    允许凭证
    -     */
    -    public WxOpenApiImpl(MPAct mpAct, String ticket) {
    -        this.mpAct = mpAct;
    -        this.ticket = ticket;
    -    }
    -
    -    @Override
    -    public void setMpAct(MPAct mpAct) {
    -        this.mpAct = mpAct;
    -    }
    -
    -    @Override
    -    public void setTicket(String ticket) {
    -        this.ticket = ticket;
    -    }
    -
    -    @Override
    -    public String getComponentToken() throws WxRespException {
    -        String token = mpAct.getAccessToken();
    -        if (null == token
    -                || token.isEmpty()
    -                || mpAct.getExpiresIn() < System.currentTimeMillis()) {
    -            synchronized (this){
    -                refreshComponentToken();
    -            }
    -            token = mpAct.getAccessToken();
    -        }
    -        return token;
    -    }
    -
    -    @Override
    -    public void refreshComponentToken() throws WxRespException {
    -        String result = "";
    -
    -        String data = "{" +
    -                "\"component_appid\":\"" + mpAct.getAppId() + "\"," +
    -                "\"component_appsecret\":\"" + mpAct.getAppSecret() + "\"," +
    -                "\"component_verify_ticket\":\"" + ticket + "\"" +
    -                "}";
    -
    -        try {
    -            result = SimpleHttpReq.post(WxApiUrl.COMPONENT_TOKEN_API,
    -                    SimpleHttpReq.APPLICATION_JSON,
    -                    data);
    -        } catch (IOException e) {
    -            log.error("刷新服务组件ACCESS_TOKEN时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        mpAct.createAccessToken(result);
    -    }
    -
    -    @Override
    -    public void createPreAuthCode() throws WxRespException {
    -        String url = String.format(WxApiUrl.COMPONENT_API,
    -                "api_create_preauthcode", getComponentToken());
    -        String result = "";
    -        String data = "{" +
    -                "\"component_appid\":\"" + mpAct.getAppId() + "\"" +
    -                "}";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, data);
    -        } catch (IOException e) {
    -            log.error("创建公众权预授权码时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        mpAct.createPreAuthCode(result);
    -    }
    -
    -    @Override
    -    public String getPreAuthCode() throws WxRespException {
    -        String auth_code = mpAct.getPreAuthCode();
    -        if (null == auth_code
    -                || mpAct.getPreAuthExpiresIn() < System.currentTimeMillis()){
    -            synchronized (this.mpAct){
    -                createPreAuthCode();
    -            }
    -        }
    -        return auth_code;
    -    }
    -
    -    @Override
    -    public AuthInfo queryAuth(String authCode) throws WxRespException {
    -        String url = String.format(WxApiUrl.COMPONENT_API, "api_query_auth", getComponentToken());
    -        String result = "";
    -        String data = "{" +
    -                "\"component_appid\":\"" + mpAct.getAppId() + "\" ," +
    -                "\" authorization_code\": \"" + authCode + "\"" +
    -                "}";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, data);
    -        } catch (IOException e) {
    -            log.error("换取授权公众号信息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        AuthInfo tmp = JSON.parseObject(result, AuthInfo.class);
    -        return tmp;
    -    }
    -
    -    @Override
    -    public String getAuthAccessToken(String authAppId, String authRefreshToken) throws WxRespException {
    -        return null;
    -    }
    -
    -    @Override
    -    public void refreshAuthAccessToken(String authAppId, String authRefreshToken) throws WxRespException {
    -
    -    }
    -
    -    @Override
    -    public AuthInfo getAuthorizerInfo(String authAppId) throws WxRespException {
    -        return null;
    -    }
    -
    -    @Override
    -    public String getAuthorizerOption(String authAppId, String optionName) throws WxRespException {
    -        String url = String.format(WxApiUrl.COMPONENT_API, "api_get_authorizer_option", getComponentToken());
    -        String result = "";
    -        String data = "{" +
    -                "\"component_appid\":\""+mpAct.getAppId()+"\"," +
    -                "\"authorizer_appid\": \""+authAppId+"\"," +
    -                "\"option_name\": \""+optionName+"\"" +
    -                "}";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, data);
    -        } catch (IOException e) {
    -            log.error("获取授权公众号[{}]的选项值时出现异常!!!",authAppId);
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || result.contains("errcode")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        JSONObject tmp = JSON.parseObject(result);
    -        String option_value = tmp.getString("option_value");
    -        if (log.isInfoEnabled()) {
    -            String info = "获取授权公众号["+authAppId+"]";
    -            switch (optionName) {
    -                case "location_report":
    -                    info += "地理位置上报选项成功,当前状态为: ";
    -                    if (option_value.equals("0")) {
    -                        info += "无上报";
    -                    } else if (option_value.equals("1")) {
    -                        info += "进入会话时上报";
    -                    } else {
    -                        info += "每5s上报";
    -                    }
    -                    break;
    -                case "voice_recognize":
    -                    info += "语音识别选项成功,当前状态为: ";
    -                    info += (option_value.equals("0")?"关闭":"开启");
    -                    break;
    -                case "customer_service":
    -                    info += "多客服选项成功,当前状态为: ";
    -                    info += (option_value.equals("0")?"关闭":"开启");
    -                    break;
    -                default:
    -                    break;
    -            }
    -            log.info("{}",info);
    -        }
    -
    -        return result;
    -    }
    -
    -    @Override
    -    public void setAuthorizerOption(String authAppId, String optionName, String optionValue) throws WxRespException {
    -        String url = String.format(WxApiUrl.COMPONENT_API, "api_set_authorizer_option", getComponentToken());
    -        String result = "";
    -        String data = "{" +
    -                "\"component_appid\":\""+mpAct.getAppId()+"\"," +
    -                "\"authorizer_appid\": \""+authAppId+"\"," +
    -                "\"option_name\": \""+optionName+"\"," +
    -                "\"option_value\":\""+optionValue+"\"" +
    -                "}";
    -        try {
    -            result = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, data);
    -        } catch (IOException e) {
    -            log.error("设置授权公众号[{}]的选项值时出现异常!!!",authAppId);
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        if (result.isEmpty()
    -                || !result.contains("ok")) {
    -            throw new WxRespException(result);
    -        }
    -
    -        if (log.isInfoEnabled()) {
    -            String info = "设置授权公众号["+authAppId+"]";
    -            switch (optionName) {
    -                case "location_report":
    -                    info += "地理位置上报选项成功,当前状态为: ";
    -                    if (optionValue.equals("0")) {
    -                        info += "无上报";
    -                    } else if (optionValue.equals("1")) {
    -                        info += "进入会话时上报";
    -                    } else {
    -                        info += "每5s上报";
    -                    }
    -                    break;
    -                case "voice_recognize":
    -                    info += "语音识别选项成功,当前状态为: ";
    -                    info += (optionValue.equals("0")?"关闭":"开启");
    -                    break;
    -                case "customer_service":
    -                    info += "多客服选项成功,当前状态为: ";
    -                    info += (optionValue.equals("0")?"关闭":"开启");
    -                    break;
    -                default:
    -                    break;
    -            }
    -            log.info("{}",info);
    -        }
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/core/package-info.java b/src/main/java/org/elkan1788/osc/weixin/mp/core/package-info.java
    deleted file mode 100644
    index 62ec0b6..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/core/package-info.java
    +++ /dev/null
    @@ -1,4 +0,0 @@
    -/**
    - * 微信API实现的核心代码
    - */
    -package org.elkan1788.osc.weixin.mp.core;
    \ No newline at end of file
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/exception/WxRespException.java b/src/main/java/org/elkan1788/osc/weixin/mp/exception/WxRespException.java
    deleted file mode 100644
    index da3bb46..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/exception/WxRespException.java
    +++ /dev/null
    @@ -1,65 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.exception;
    -
    -import com.alibaba.fastjson.JSON;
    -import com.alibaba.fastjson.JSONObject;
    -import org.elkan1788.osc.weixin.mp.commons.WxErrCode;
    -
    -/**
    - * 微信响应错误异常
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/11/6
    - * @version 1.0.0
    - */
    -public class WxRespException extends Exception {
    -
    -    /**
    -     * 错误代码
    -     */
    -    private final int errCode;
    -    /**
    -     * 错误中文描述
    -     */
    -    private final String errMesg;
    -    // 临时JSON对象
    -    protected static JSONObject error;
    -
    -    public WxRespException(String message) {
    -        super(convertMesg(message));
    -        this.errCode = error.getInteger("errcode");
    -        this.errMesg = error.getString("errmsg");
    -    }
    -
    -    /**
    -     * 将消息转成JSON对象
    -     * @param message   消息
    -     * @return  error对象
    -     */
    -    protected static String convertMesg(String message) {
    -        if (null == message || message.isEmpty()) {
    -            throw new RuntimeException("网络通讯异常,请检查!!!");
    -        }
    -        String err_desc = "";
    -        error = JSON.parseObject(message);
    -        err_desc = WxErrCode.getErrDesc(error.getInteger("errcode"));
    -        return err_desc;
    -    }
    -
    -    /**
    -     * 获取错误代码
    -     *
    -     * @return errCode
    -     */
    -    public int getErrCode() {
    -        return errCode;
    -    }
    -
    -    /**
    -     * 获取错误描述
    -     *
    -     * @return  errmsg
    -     */
    -    public String getErrMesg() {
    -        return errMesg;
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/exception/package-info.java b/src/main/java/org/elkan1788/osc/weixin/mp/exception/package-info.java
    deleted file mode 100644
    index 8b7dd54..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/exception/package-info.java
    +++ /dev/null
    @@ -1,4 +0,0 @@
    -/**
    - * 微信API运行时的异常处理
    - */
    -package org.elkan1788.osc.weixin.mp.exception;
    \ No newline at end of file
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/package-info.java b/src/main/java/org/elkan1788/osc/weixin/mp/package-info.java
    deleted file mode 100644
    index 2f73410..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/package-info.java
    +++ /dev/null
    @@ -1,4 +0,0 @@
    -/**
    - * JAVA微信公平台开发SDK,没有复杂的功能,一切源于微信API,愿你会喜欢使用。
    - */
    -package org.elkan1788.osc.weixin.mp;
    \ No newline at end of file
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/util/JsonMsgBuilder.java b/src/main/java/org/elkan1788/osc/weixin/mp/util/JsonMsgBuilder.java
    deleted file mode 100644
    index d259669..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/util/JsonMsgBuilder.java
    +++ /dev/null
    @@ -1,296 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.util;
    -
    -import org.elkan1788.osc.weixin.mp.commons.WxMsgType;
    -import org.elkan1788.osc.weixin.mp.vo.Article;
    -import org.elkan1788.osc.weixin.mp.vo.Article2;
    -import org.elkan1788.osc.weixin.mp.vo.OutPutMsg;
    -import org.elkan1788.osc.weixin.mp.vo.Template;
    -
    -/**
    - * 创建微信客服消息
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/11/10
    - * @version 1.0.0
    - */
    -public class JsonMsgBuilder {
    -
    -    private final StringBuffer msgBuf = new StringBuffer("{");
    -
    -    /**
    -     * 创建
    -     */
    -    public static JsonMsgBuilder create() {
    -        return new JsonMsgBuilder();
    -    }
    -
    -    /**
    -     * 创建消息体前缀
    -     *
    -     * @param msg   客服消息实体
    -     */
    -    void msgPrefix(OutPutMsg msg) {
    -        msgBuf.append("\"touser\":\"")
    -                .append(msg.getToUserName())
    -                .append("\",");
    -        msgBuf.append("\"msgtype\":\"")
    -                .append(msg.getMsgType())
    -                .append("\",");
    -    }
    -
    -    void msgSuffix(OutPutMsg msg) {
    -        msgBuf.append("\"msgtype\":\"").append(msg.getMsgType()).append("\"");
    -    }
    -
    -    /**
    -     * 文本客服消息
    -     *
    -     * @param msg   客服消息实体
    -     */
    -    public JsonMsgBuilder text(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        msgBuf.append("\"text\": {");
    -        msgBuf.append(" \"content\":\"")
    -                .append(msg.getContent())
    -                .append("\"");
    -        msgBuf.append("}");
    -        return this;
    -    }
    -
    -    /**
    -     * 图像客服消息
    -     *
    -     * @param msg   客服消息实体
    -     */
    -    public JsonMsgBuilder image(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        msgBuf.append("\"image\": {");
    -        msgBuf.append(" \"media_id\":\"")
    -                .append(msg.getMediaId())
    -                .append("\"");
    -        msgBuf.append("}");
    -        return this;
    -    }
    -
    -    /**
    -     * 语音客服消息
    -     *
    -     * @param msg   客服消息实体
    -     */
    -    public JsonMsgBuilder voice(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        msgBuf.append("\"voice\": {");
    -        msgBuf.append(" \"media_id\":\"")
    -                .append(msg.getMediaId())
    -                .append("\"");
    -        msgBuf.append("}");
    -        return this;
    -    }
    -
    -    /**
    -     * 视频客服消息
    -     *
    -     * @param msg   客服消息实体
    -     */
    -    public JsonMsgBuilder video(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        msgBuf.append("\"video\": {");
    -        msgBuf.append(" \"media_id\":\"")
    -                .append(msg.getMediaId())
    -                .append("\",");
    -        msgBuf.append(" \"thumb_media_id\":\"")
    -                .append(msg.getThumbMediaId())
    -                .append("\",");
    -        msgBuf.append(" \"title\":\"")
    -                .append(msg.getTitle())
    -                .append("\",");
    -        msgBuf.append(" \"description\":\"")
    -                .append(msg.getDescription())
    -                .append("\"");
    -        msgBuf.append("}");
    -        return this;
    -    }
    -
    -    /**
    -     * 音乐客服消息
    -     *
    -     * @param msg   客服消息实体
    -     */
    -    public JsonMsgBuilder music(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        msgBuf.append("\"music\": {");
    -        msgBuf.append(" \"title\":\"")
    -                .append(msg.getTitle())
    -                .append("\",");
    -        msgBuf.append(" \"description\":\"")
    -                .append(msg.getDescription())
    -                .append("\",");
    -        msgBuf.append(" \"musicurl\":\"")
    -                .append(msg.getMusicUrl())
    -                .append("\",");
    -        msgBuf.append(" \"hqmusicurl\":\"")
    -                .append(msg.gethQMusicUrl())
    -                .append("\",");
    -        msgBuf.append(" \"thumb_media_id\":\"")
    -                .append(msg.getThumbMediaId())
    -                .append("\"");
    -        msgBuf.append("}");
    -        return this;
    -    }
    -
    -    /**
    -     * 多图文客服消息
    -     *
    -     * @param msg   客服消息实体
    -     */
    -    public JsonMsgBuilder news(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        StringBuffer arts_buf = new StringBuffer("\"articles\": [");
    -        StringBuffer art_buf = new StringBuffer();
    -        for (Article art : msg.getArticles()) {
    -            art_buf.setLength(0);
    -            art_buf.append("{");
    -            art_buf.append(" \"title\":\"")
    -                    .append(art.getTitle())
    -                    .append("\",");
    -            art_buf.append(" \"description\":\"")
    -                    .append(art.getDescription())
    -                    .append("\",");
    -            art_buf.append(" \"picurl\":\"")
    -                    .append(art.getPicUrl())
    -                    .append("\",");
    -            art_buf.append(" \"url\":\"")
    -                    .append(art.getUrl());
    -            art_buf.append("\"},");
    -        }
    -        arts_buf.append(art_buf.substring(0, art_buf.lastIndexOf(",")));
    -        arts_buf.append("]");
    -        msgBuf.append("\"news\": {");
    -        msgBuf.append(arts_buf);
    -        msgBuf.append("}");
    -        return this;
    -    }
    -
    -    /**
    -     * 模板消息
    -     *
    -     * @param openId    接收者
    -     * @param templateId    模板ID
    -     * @param topColor  顶部颜色
    -     * @param url   链接地址
    -     * @param templates 模板数据
    -     */
    -    public JsonMsgBuilder template(String openId, String templateId,
    -                                   String topColor, String url, Template... templates) {
    -        msgBuf.append("\"touser\":\"").append(openId).append("\",");
    -        msgBuf.append("\"template_id\":\"").append(templateId).append("\",");
    -        msgBuf.append("\"url\":\"").append(url).append("\",");
    -        msgBuf.append("\"topcolor\":\"").append(topColor).append("\",");
    -        msgBuf.append("\"data\":{");
    -        StringBuffer data = new StringBuffer("");
    -        for (Template t : templates) {
    -           data.append(t.templateData()).append(",");
    -        }
    -        msgBuf.append(data.substring(0, data.lastIndexOf(",")));
    -        msgBuf.append("}");
    -        return this;
    -    }
    -
    -    /**
    -     * 上传多图文消息
    -     *
    -     * @param articles2s    图文消息
    -     */
    -    public JsonMsgBuilder uploadNews(Article2... articles2s) {
    -        msgBuf.append("\"articles\":[");
    -        StringBuffer art2_buf = new StringBuffer();
    -        for (Article2 art2 : articles2s) {
    -            art2_buf.append("{");
    -            art2_buf.append("\"thumb_media_id\":\"").append(art2.getMediaId()).append("\",");
    -            art2_buf.append("\"author\":\"").append(art2.getAuthor()).append("\",");
    -            art2_buf.append("\"title\":\"").append(art2.getTitle()).append("\",");
    -            art2_buf.append("\"content_source_url\":\"").append(art2.getSourceUrl()).append("\",");
    -            art2_buf.append("\"content\":\"").append(art2.getContent()).append("\",");
    -            art2_buf.append("\"digest\":\"").append(art2.getDigest()).append("\",");
    -            art2_buf.append("\"show_cover_pic\":\"").append(art2.getShowCover()).append("\"");
    -            art2_buf.append("},");
    -        }
    -        msgBuf.append(art2_buf.substring(0, art2_buf.lastIndexOf(",")));
    -        msgBuf.append("]");
    -        return this;
    -    }
    -
    -    /**
    -     * 上传视频
    -     *
    -     * @param mediaId   多媒体ID
    -     * @param title 视频标题
    -     * @param description   视频描述
    -     */
    -    public JsonMsgBuilder uploadVideo(String mediaId, String title, String description) {
    -        msgBuf.append("\"media_id\":\"").append(mediaId).append("\",");
    -        msgBuf.append("\"title\":\"").append(title).append("\",");
    -        msgBuf.append("\"description\":\"").append(description).append("\"");
    -        return this;
    -    }
    -
    -    /**
    -     * 群发消息
    -     *
    -     * @param msg 输出消息实体
    -     */
    -    public JsonMsgBuilder sendAll(OutPutMsg msg) {
    -        if (msg.getToUsers().isEmpty()) {
    -          msgBuf.append("\"filter\":{")
    -                  .append("\"group_id\":\"")
    -                  .append(msg.getGroupId())
    -                  .append("\"},");
    -        } else {
    -            msgBuf.append("\"touser\":[");
    -            StringBuffer users_buf = new StringBuffer();
    -            for (String touser : msg.getToUsers()) {
    -                users_buf.append("\"").append(touser).append("\",");
    -            }
    -            msgBuf.append(users_buf.substring(0, users_buf.lastIndexOf(",")));
    -            msgBuf.append("],");
    -        }
    -
    -        WxMsgType type = WxMsgType.valueOf(msg.getMsgType());
    -        switch (type) {
    -            case text:
    -                msgBuf.append("\"text\":{\"content\":\"").append(msg.getContent()).append("\"}");
    -                break;
    -            case image:
    -                msgBuf.append("\"image\":{\"media_id\":\"").append(msg.getMediaId()).append("\"}");
    -                break;
    -            case voice:
    -                msgBuf.append("\"voice\":{\"media_id\":\"").append(msg.getMediaId()).append("\"}");
    -                break;
    -            case mpvideo:
    -                msgBuf.append("\"mpvideo\":{\"media_id\":\"").append(msg.getMediaId()).append("\"}");
    -                break;
    -            case video:
    -                msgBuf.append("\"video\":{\"media_id\":\"")
    -                        .append(msg.getMediaId())
    -                        .append("\",\"title\":\"")
    -                        .append(msg.getTitle())
    -                        .append("\",\"description\":\"")
    -                        .append(msg.getDescription()).append("\"}");
    -                break;
    -            case mpnews:
    -                msgBuf.append("\"mpnews\":{\"media_id\":\"").append(msg.getMediaId()).append("\"}");
    -                break;
    -            default:
    -                break;
    -        }
    -
    -        msgBuf.append(",\"msgtype\":\"").append(msg.getMsgType()).append("\"");
    -
    -        return this;
    -    }
    -
    -    public String build() {
    -        msgBuf.append("}");
    -        return msgBuf.toString();
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/util/SimpleHttpReq.java b/src/main/java/org/elkan1788/osc/weixin/mp/util/SimpleHttpReq.java
    deleted file mode 100644
    index 7ab54c2..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/util/SimpleHttpReq.java
    +++ /dev/null
    @@ -1,89 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.util;
    -
    -import org.apache.http.HttpEntity;
    -import org.apache.http.client.fluent.Request;
    -import org.apache.http.entity.ContentType;
    -import org.apache.http.entity.mime.MultipartEntityBuilder;
    -import org.apache.http.entity.mime.content.FileBody;
    -
    -import java.io.File;
    -import java.io.IOException;
    -
    -/**
    - * HTTP请求实现
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/11/7
    - * @version 1.0.0
    - */
    -public class SimpleHttpReq {
    -
    -    public static final String TEXT_PLAIN = "text/plain";
    -
    -    public static final String TEXT_HTML = "text/html";
    -
    -    public static final String TEXT_XML = "text/xml";
    -
    -    public static final String APPLICATION_XML = "application/xml";
    -
    -    public static final String APPLICATION_JSON = "application/json";
    -
    -    private static final StrResponseHandler respHandler = new StrResponseHandler();
    -
    -    /**
    -     * GET请求
    -     *
    -     * @param url 请求地址
    -     * @return 响应内容
    -     * @throws java.io.IOException
    -     */
    -    public static String get(String url) throws IOException {
    -        String content = Request.Get(url).execute().handleResponse(respHandler);
    -        return content;
    -    }
    -
    -    /**
    -     * POST请求
    -     *
    -     * @param url 请求地址
    -     * @param contentType 请求体类型[text, xml, json, html]
    -     * @param body 请求体
    -     * @return 响应内容
    -     * @throws java.io.IOException
    -     */
    -    public static String post(String url,
    -                              String contentType,
    -                              String body) throws IOException {
    -        String content = Request.Post(url)
    -                .bodyString(body, ContentType.create(contentType))
    -                .execute().handleResponse(respHandler);
    -        return content;
    -    }
    -
    -    /**
    -     * 上传文件
    -     *
    -     * @param url 请求地址
    -     * @param file 上传文件
    -     * @return 响应内容
    -     * @throws java.io.IOException
    -     */
    -    public static String upload(String url, File file) throws IOException {
    -        HttpEntity media = MultipartEntityBuilder.create()
    -                .addPart("media", new FileBody(file)).build();
    -        String content = Request.Post(url).body(media)
    -                .execute().handleResponse(respHandler);
    -        return content;
    -    }
    -
    -    /**
    -     * 下载文件
    -     *
    -     * @param url   请求地址
    -     * @param file 文件保存位置
    -     * @throws java.io.IOException
    -     */
    -    public static void download(String url, File file) throws IOException {
    -        Request.Get(url).execute().saveContent(file);
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/util/StrResponseHandler.java b/src/main/java/org/elkan1788/osc/weixin/mp/util/StrResponseHandler.java
    deleted file mode 100644
    index c6bbbf4..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/util/StrResponseHandler.java
    +++ /dev/null
    @@ -1,32 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.util;
    -
    -import org.apache.http.HttpEntity;
    -import org.apache.http.HttpResponse;
    -import org.apache.http.client.ClientProtocolException;
    -import org.apache.http.client.ResponseHandler;
    -import org.apache.http.util.EntityUtils;
    -
    -import java.io.IOException;
    -
    -/**
    - * HTTP请求响应处理
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/11/6
    - * @version 1.0.0
    - */
    -public class StrResponseHandler implements ResponseHandler {
    -
    -    @Override
    -    public String handleResponse(HttpResponse resp)
    -            throws ClientProtocolException, IOException {
    -        int status = resp.getStatusLine().getStatusCode();
    -        if (status >= 200 && status < 300) {
    -            HttpEntity entity = resp.getEntity();
    -            String body = (null!= entity) ? EntityUtils.toString(entity,"UTF-8") : "";
    -            return body;
    -        } else {
    -            throw new ClientProtocolException("请求失败,服务器响应代码: " + status);
    -        }
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/util/XMLHandler.java b/src/main/java/org/elkan1788/osc/weixin/mp/util/XMLHandler.java
    deleted file mode 100644
    index 8f76f00..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/util/XMLHandler.java
    +++ /dev/null
    @@ -1,216 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.util;
    -
    -import org.elkan1788.osc.weixin.mp.vo.PicInfo;
    -import org.elkan1788.osc.weixin.mp.vo.ReceiveMsg;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -import org.xml.sax.Attributes;
    -import org.xml.sax.SAXException;
    -import org.xml.sax.ext.DefaultHandler2;
    -import org.xml.sax.helpers.DefaultHandler;
    -
    -import java.util.ArrayList;
    -import java.util.List;
    -
    -/**
    - * 微信消息内容处理器[更新JDK7特性]
    - *
    - * @author 凡梦星尘(senhuili@mdc.cn)
    - * @since 2014/11/7
    - * @version 1.0.4
    - */
    -public class XMLHandler extends DefaultHandler2 {
    -
    -    private static final Logger log = LoggerFactory.getLogger(XMLHandler.class);
    -
    -	/**
    -	 * 消息实体定义
    -	 */
    -	private ReceiveMsg msg = new ReceiveMsg();
    -
    -    /**
    -     * 图片信息
    -     */
    -    private PicInfo picInfo;
    -
    -    private List picList;
    -
    -    /**
    -     * 节点属性值
    -     */
    -    private String attrVal;
    -
    -	/**
    -	 * 获取消息实体对象
    -     *
    -     * @return 带数据的消息实体
    -	 */
    -	public ReceiveMsg getMsgVO() {
    -		return this.msg;
    -	}
    -
    -    @Override
    -    public void startElement(String uri,
    -                             String localName,
    -                             String qName,
    -                             Attributes attributes) throws SAXException {
    -
    -        switch (qName) {
    -            case "PicList":
    -                this.picList = new ArrayList<>();
    -                break;
    -            case "item":
    -                this.picInfo = new PicInfo();
    -                break;
    -            default:
    -                break;
    -        }
    -    }
    -
    -    @Override
    -    public void endElement(String uri,
    -                           String localName,
    -                           String qName) throws SAXException {
    -
    -        if (log.isInfoEnabled()) {
    -            if (!"xml".equals(qName)) {
    -                log.info("当前节点值[{}]: {}", qName, attrVal);
    -            }
    -        }
    -
    -        // 本想用反射实现,但耗时长,所以还是手动编码吧,累(更新JDK7特性)
    -        switch (qName) {
    -            case "MsgId":
    -            case "MsgID":
    -                msg.setMsgId(Long.valueOf(attrVal));
    -                break;
    -            case "CreateTime":
    -                msg.setCreateTime(Long.valueOf(attrVal));
    -                break;
    -            case "MsgType":
    -                msg.setMsgType(attrVal);
    -                break;
    -            case "Event":
    -                msg.setEvent(attrVal);
    -                break;
    -            case "ToUserName":
    -                msg.setToUserName(attrVal);
    -                break;
    -            case "FromUserName" :
    -                msg.setFromUserName(attrVal);
    -                break;
    -            case "Content":
    -                msg.setContent(attrVal);
    -                break;
    -            case "PicUrl":
    -                msg.setPicUrl(attrVal);
    -                break;
    -            case "MediaId":
    -                msg.setMediaId(attrVal);
    -                break;
    -            case "Format":
    -                msg.setFormat(attrVal);
    -                break;
    -            case "Recognition":
    -                msg.setRecognition(attrVal);
    -                break;
    -            case "ThumbMediaId":
    -                msg.setThumbMediaId(attrVal);
    -                break;
    -            case "Location_X":
    -            case "Latitude":
    -                msg.setLatitude(Double.valueOf(attrVal));
    -                break;
    -            case "Location_Y":
    -            case "Longitude":
    -                msg.setLongitude(Double.valueOf(attrVal));
    -                break;
    -            case "Scale":
    -                msg.setScale(Integer.parseInt(attrVal));
    -                break;
    -            case "Label":
    -                msg.setLabel(attrVal);
    -                break;
    -            case "Title":
    -                msg.setTitle(attrVal);
    -                break;
    -            case "Description":
    -                msg.setDescription(attrVal);
    -                break;
    -            case "Url":
    -                msg.setUrl(attrVal);
    -                break;
    -            case "EventKey":
    -                msg.setEventKey(attrVal);
    -                break;
    -            case "Ticket":
    -            case "ComponentVerifyTicket":
    -                msg.setTicket(attrVal);
    -                break;
    -            case "Precision":
    -                msg.setPrecision(Double.valueOf(attrVal));
    -                break;
    -            case "ScanType":
    -                msg.setScanType(attrVal);
    -                break;
    -            case "ScanResult":
    -                msg.setScanResult(attrVal);
    -                break;
    -            case "Count":
    -                msg.setCount(Integer.parseInt(attrVal));
    -                break;
    -            case "PicMd5Sum":
    -                picInfo.setPicMd5Sum(attrVal);
    -                break;
    -            case "item":
    -                picList.add(picInfo);
    -                break;
    -            case "PicList":
    -                msg.setPicList(picList);
    -                break;
    -            case "Poiname":
    -                msg.setPoiName(attrVal);
    -                break;
    -            case "Status":
    -                msg.setStatus(attrVal);
    -                break;
    -            case "TotalCount":
    -                msg.setTotalCnt(Integer.parseInt(attrVal));
    -                break;
    -            case "FilterCount":
    -                msg.setFilterCnt(Integer.parseInt(attrVal));
    -                break;
    -            case "SentCount":
    -                msg.setSentCnt(Integer.parseInt(attrVal));
    -                break;
    -            case "ErrorCount":
    -                msg.setErrorCnt(Integer.parseInt(attrVal));
    -                break;
    -            case "AppId":
    -                msg.setAppId(attrVal);
    -                break;
    -            case "InfoType":
    -                msg.setInfoType(attrVal);
    -                break;
    -            case "AuthorizerAppid":
    -                msg.setUnAuthAppid(attrVal);
    -                break;
    -            default:
    -                break;
    -        }
    -    }
    -
    -    @Override
    -    public void characters(char[] ch, int start, int length) throws SAXException {
    -        this.attrVal = new String(ch, start, length);
    -    }
    -
    -    /**
    -     * 清除当前VO对象缓存数据
    -     */
    -    public void clear() {
    -        this.picInfo = null;
    -        this.msg = null;
    -        this.msg = new ReceiveMsg();
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/util/XmlMsgBuilder.java b/src/main/java/org/elkan1788/osc/weixin/mp/util/XmlMsgBuilder.java
    deleted file mode 100644
    index 1918b46..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/util/XmlMsgBuilder.java
    +++ /dev/null
    @@ -1,207 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.util;
    -
    -import org.elkan1788.osc.weixin.mp.vo.Article;
    -import org.elkan1788.osc.weixin.mp.vo.OutPutMsg;
    -
    -/**
    - * 创建微信被动消息回复
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/11/9
    - * @version 1.0.0
    - */
    -public class XmlMsgBuilder {
    -
    -    private final StringBuffer msgBuf = new StringBuffer("\n");;
    -
    -    /**
    -     * 创建
    -     */
    -    public static XmlMsgBuilder create() {
    -        return new XmlMsgBuilder();
    -    }
    -
    -    /**
    -     * 创建消息体前缀
    -     *
    -     * @param msg   输出消息实体
    -     */
    -    void msgPrefix(OutPutMsg msg) {
    -        msgBuf.append("\n");
    -        msgBuf.append("\n");
    -        msgBuf.append("")
    -                .append(msg.getCreateTime())
    -                .append("\n");
    -        msgBuf.append("\n");
    -    }
    -
    -    /**
    -     * 被动文本消息
    -     * @param msg   输出消息实体
    -     */
    -    public XmlMsgBuilder text(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        msgBuf.append("\n");
    -        return this;
    -    }
    -
    -    /**
    -     * 被动图像消息
    -     *
    -     * @param msg   输出消息实体
    -     */
    -    public XmlMsgBuilder image(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        msgBuf.append("");
    -        msgBuf.append("\n");
    -        msgBuf.append("");
    -        return this;
    -    }
    -
    -    /**
    -     * 被动语音消息
    -     *
    -     * @param msg   输出消息实体
    -     */
    -    public XmlMsgBuilder vioce(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        msgBuf.append("");
    -        msgBuf.append("\n");
    -        msgBuf.append("\n");
    -        return this;
    -    }
    -
    -    /**
    -     * 被动视频消息
    -     *
    -     * @param msg   输出消息实体
    -     */
    -    public XmlMsgBuilder video(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        msgBuf.append("\n");
    -        return this;
    -    }
    -
    -    /**
    -     * 被动音乐消息
    -     *
    -     * @param msg   输出消息实体
    -     */
    -    public XmlMsgBuilder music(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        msgBuf.append("");
    -        msgBuf.append("<![CDATA[")
    -                .append(msg.getTitle())
    -                .append("]]>\n");
    -        msgBuf.append("\n");
    -        msgBuf.append("\n");
    -        msgBuf.append("\n");
    -        msgBuf.append("\n");
    -        msgBuf.append("\n");
    -        return this;
    -    }
    -
    -    /**
    -     * 被动多图文消息
    -     *
    -     * @param msg   输出消息实体
    -     */
    -    public XmlMsgBuilder news(OutPutMsg msg) {
    -        msgPrefix(msg);
    -        StringBuffer arts_buf = new StringBuffer("\n");
    -        StringBuffer item_buf = new StringBuffer();
    -            for (Article art : msg.getArticles()) {
    -                item_buf.setLength(0);
    -                item_buf.append("\n");
    -                item_buf.append("<![CDATA[")
    -                        .append(art.getTitle())
    -                        .append("]]>\n");
    -                item_buf.append("\n");
    -                item_buf.append("\n");
    -                item_buf.append("\n");
    -                item_buf.append("\n");
    -                arts_buf.append(item_buf);
    -            }
    -        arts_buf.append("\n");
    -        msgPrefix(msg);
    -        msgBuf.append("")
    -                .append(msg.getArticles().size())
    -                .append("\n");
    -        msgBuf.append(arts_buf);
    -        return this;
    -    }
    -
    -    /**
    -     * AES加密信息
    -     *
    -     * @param xml           消息原文
    -     * @param msgSignature  消息签名
    -     * @param timeStamp     时间戳
    -     * @param nonce         随机字符
    -     */
    -    public String encrypt(String xml, String msgSignature,
    -                                 String timeStamp, String nonce) {
    -
    -        msgBuf.setLength(0);
    -        msgBuf.append("\n");
    -        msgBuf.append("\n");
    -        msgBuf.append("\n");
    -        msgBuf.append("")
    -                .append(timeStamp)
    -                .append("\n");
    -        msgBuf.append("\n");
    -        msgBuf.append("");
    -        return  msgBuf.toString();
    -    }
    -
    -    /**
    -     * 创建回复消息
    -     *
    -     * @return  回复消息
    -     */
    -    public String build() {
    -        msgBuf.append("");
    -        return msgBuf.toString();
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/util/package-info.java b/src/main/java/org/elkan1788/osc/weixin/mp/util/package-info.java
    deleted file mode 100644
    index b30c8a4..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/util/package-info.java
    +++ /dev/null
    @@ -1,4 +0,0 @@
    -/**
    - * 微信API常用的工具类
    - */
    -package org.elkan1788.osc.weixin.mp.util;
    \ No newline at end of file
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Article2.java b/src/main/java/org/elkan1788/osc/weixin/mp/vo/Article2.java
    deleted file mode 100644
    index 8d65fd9..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Article2.java
    +++ /dev/null
    @@ -1,109 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.vo;
    -
    -/**
    - * 高级群发消息的多图文
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/11/11
    - * @version 1.0.0
    - */
    -public class Article2 {
    -
    -    /**
    -     * 缩略图的media_id
    -     */
    -    private String mediaId;
    -    /**
    -     * 图文消息作者
    -     */
    -    private String author;
    -    /**
    -     * 标题
    -     */
    -    private String title;
    -    /**
    -     * 图文消息页面点击“阅读原文”后的页面
    -     */
    -    private String sourceUrl;
    -    /**
    -     * 图文消息页面的内容,支持HTML标签
    -     */
    -    private String content;
    -    /**
    -     * 图文消息的描述
    -     */
    -    private String digest;
    -    /**
    -     * 是否显示封面,1为显示,0为不显示
    -     */
    -    private int showCover;
    -
    -    public String getMediaId() {
    -        return mediaId;
    -    }
    -
    -    public void setMediaId(String mediaId) {
    -        this.mediaId = mediaId;
    -    }
    -
    -    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 getSourceUrl() {
    -        return sourceUrl;
    -    }
    -
    -    public void setSourceUrl(String sourceUrl) {
    -        this.sourceUrl = sourceUrl;
    -    }
    -
    -    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 int getShowCover() {
    -        return showCover;
    -    }
    -
    -    public void setShowCover(int showCover) {
    -        this.showCover = showCover;
    -    }
    -
    -    @Override
    -    public String toString() {
    -        return "Article2{" +
    -                "mediaId='" + mediaId + '\'' +
    -                ", author='" + author + '\'' +
    -                ", title='" + title + '\'' +
    -                ", sourceUrl='" + sourceUrl + '\'' +
    -                ", content='" + content + '\'' +
    -                ", digest='" + digest + '\'' +
    -                ", showCover=" + showCover +
    -                '}';
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Articles2.java b/src/main/java/org/elkan1788/osc/weixin/mp/vo/Articles2.java
    deleted file mode 100644
    index 89c3133..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Articles2.java
    +++ /dev/null
    @@ -1,109 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.vo;
    -
    -/**
    - * 高级群发消息的多图文
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2014/11/11
    - * @version 1.0.0
    - */
    -public class Articles2 {
    -
    -    /**
    -     * 缩略图的media_id
    -     */
    -    private String mediaId;
    -    /**
    -     * 图文消息作者
    -     */
    -    private String author;
    -    /**
    -     * 标题
    -     */
    -    private String title;
    -    /**
    -     * 图文消息页面点击“阅读原文”后的页面
    -     */
    -    private String sourceUrl;
    -    /**
    -     * 图文消息页面的内容,支持HTML标签
    -     */
    -    private String content;
    -    /**
    -     * 图文消息的描述
    -     */
    -    private String digest;
    -    /**
    -     * 是否显示封面,1为显示,0为不显示
    -     */
    -    private int showCover;
    -
    -    public String getMediaId() {
    -        return mediaId;
    -    }
    -
    -    public void setMediaId(String mediaId) {
    -        this.mediaId = mediaId;
    -    }
    -
    -    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 getSourceUrl() {
    -        return sourceUrl;
    -    }
    -
    -    public void setSourceUrl(String sourceUrl) {
    -        this.sourceUrl = sourceUrl;
    -    }
    -
    -    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 int getShowCover() {
    -        return showCover;
    -    }
    -
    -    public void setShowCover(int showCover) {
    -        this.showCover = showCover;
    -    }
    -
    -    @Override
    -    public String toString() {
    -        return "Articles2{" +
    -                "mediaId='" + mediaId + '\'' +
    -                ", author='" + author + '\'' +
    -                ", title='" + title + '\'' +
    -                ", sourceUrl='" + sourceUrl + '\'' +
    -                ", content='" + content + '\'' +
    -                ", digest='" + digest + '\'' +
    -                ", showCover=" + showCover +
    -                '}';
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/AuthInfo.java b/src/main/java/org/elkan1788/osc/weixin/mp/vo/AuthInfo.java
    deleted file mode 100644
    index faa6b63..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/AuthInfo.java
    +++ /dev/null
    @@ -1,225 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.vo;
    -
    -import com.alibaba.fastjson.annotation.JSONField;
    -
    -import java.util.List;
    -
    -/**
    - * 微信开放平台授权公众号信息
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2015/1/8
    - */
    -public class AuthInfo {
    -    /**
    -     * 授权方昵称
    -     */
    -    @JSONField(name = "nick_name")
    -    private String nickName;
    -    /**
    -     * 授权方appid
    -     */
    -    @JSONField(name = "authorizer_appid")
    -    private String appId;
    -    /**
    -     * 授权方令牌(在授权的公众号具备API权限时

    才有此返回值) - */ - @JSONField(name = "authorizer_access_token") - private String accessToken; - /** - * 有效期(在授权的公众号具备API权限时

    才有此返回值) - */ - @JSONField(name = "expires_in") - private long expiresIn; - /** - * 授权方头像 - */ - @JSONField(name = "head_img") - private String headImg; - /** - * 授权方公众号类型

    - * 0代表订阅号

    - * 1代表由历史老帐号升级后的订阅号

    - * 2代表服务号 - */ - @JSONField(name = "service_type_info") - private String serTypeInfo; - /** - * 授权方认证类型

    - * -1代表未认证

    - * 0代表微信认证

    - * 1代表新浪微博认证

    - * 2代表腾讯微博认证

    - * 3代表已资质认证通过但还未通过名称认证

    - * 4代表已资质认证通过、还未通过名称认证

    - * 但通过了新浪微博认证

    - * 5代表已资质认证通过、还未通过名称认证

    但通过了腾讯微博认证 - */ - @JSONField(name = "verify_type_info") - private String verTypeInfo; - /** - * 授权方公众号的原始ID - */ - @JSONField(name = "user_name") - private String mpId; - /** - * 授权方公众号所设置的微信号

    可能为空 - */ - @JSONField(name = "alias") - private String alias; - /** - * 刷新令牌(在授权的公众号具备API权限时

    才有此返回值)

    - * 刷新令牌主要用于公众号服务获取和刷新已授权用户的access_token

    - * 只会在授权时刻提供

    请妥善保存。 一旦丢失

    只能让用户重新授权

    - * 才能再次拿到新的刷新令牌 - */ - @JSONField(name = "authorizer_refresh_token") - private String refreshToken; - - /** - * 公众号授权给开发者的权限集列表 - */ - @JSONField(name = "func_info") - private List funs; - - public String getNickName() { - return nickName; - } - - public void setNickName(String nickName) { - this.nickName = nickName; - } - - public String getAppId() { - return appId; - } - - public void setAppId(String appId) { - this.appId = appId; - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public long getExpiresIn() { - return expiresIn; - } - - public void setExpiresIn(long expiresIn) { - this.expiresIn = expiresIn; - } - - public String getHeadImg() { - return headImg; - } - - public void setHeadImg(String headImg) { - this.headImg = headImg; - } - - public String getSerTypeInfo() { - return serTypeInfo; - } - - public void setSerTypeInfo(String serTypeInfo) { - this.serTypeInfo = serTypeInfo; - } - - public String getVerTypeInfo() { - return verTypeInfo; - } - - public void setVerTypeInfo(String verTypeInfo) { - this.verTypeInfo = verTypeInfo; - } - - public String getMpId() { - return mpId; - } - - public void setMpId(String mpId) { - this.mpId = mpId; - } - - public String getAlias() { - return alias; - } - - public void setAlias(String alias) { - this.alias = alias; - } - - public String getRefreshToken() { - return refreshToken; - } - - public void setRefreshToken(String refreshToken) { - this.refreshToken = refreshToken; - } - - public List getFuns() { - return funs; - } - - public void setFuns(List funs) { - this.funs = funs; - } - - @Override - public String toString() { - return "AuthInfo{" + - "nickName='" + nickName + '\'' + - ", appId='" + appId + '\'' + - ", accessToken='" + accessToken + '\'' + - ", expiresIn='" + expiresIn + '\'' + - ", headImg='" + headImg + '\'' + - ", serTypeInfo='" + serTypeInfo + '\'' + - ", verTypeInfo='" + verTypeInfo + '\'' + - ", mpId='" + mpId + '\'' + - ", alias='" + alias + '\'' + - ", refreshToken='" + refreshToken + '\'' + - ", funs=" + String.valueOf(funs) + - '}'; - } - - /** - * 公众号授权给开发者的权限集 - */ - public class FunctionInfo { - - /** - * 公众号授权给开发者的权限集列表[1-9]分别代表

    - * 1.消息与菜单权限集

    - * 2.用户管理权限集

    - * 3.帐号管理权限集

    - * 4.网页授权权限集

    - * 5.微信小店权限集

    - * 6.多客服权限集

    - * 7.业务通知权限集

    - * 8.微信卡券权限集

    - * 9.微信扫一扫权限集

    - */ - @JSONField(name = "funcscope_category") - private String category; - - public String getCategory() { - return category; - } - - public void setCategory(String category) { - this.category = category; - } - - @Override - public String toString() { - return "FunctionInfo{" + - "category='" + category + '\'' + - '}'; - } - } -} diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/BaseMsg.java b/src/main/java/org/elkan1788/osc/weixin/mp/vo/BaseMsg.java deleted file mode 100644 index 00300e4..0000000 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/BaseMsg.java +++ /dev/null @@ -1,159 +0,0 @@ -package org.elkan1788.osc.weixin.mp.vo; - -/** - * 微信消息实体基类 - * - * @author 凡梦星尘(senhuili@mdc.cn) - * @since 2014/11/7 - * @version 1.0.0 - */ -public abstract class BaseMsg { - - /** - * 消息唯一ID(64位整型) - */ - protected long msgId; - - /** - * 消息创建时间 (整型) - */ - protected long createTime; - - /** - * 消息类型(text, image, video, voice, location, link,event) - */ - protected String msgType; - - /** - * 消息事件: - * subscribe:订阅 - * unsubscribe:取消订阅 - * SCAN:关注后场景扫描 - * LOCATION:主动上传位置 - * VIEW,CLICK:菜单点击事件 - * TEMPLATESENDJOBFINISH:模板消息推送 - */ - protected String event; - - /** - * 接收消息用户ID - */ - protected String toUserName; - - /** - * 消息来自用户ID - */ - protected String fromUserName; - - /** - * 文本消息内容 - */ - protected String content; - - /** - * 多媒体消息ID(微信服务器有效时间为3天) - */ - protected String mediaId; - - /** - * 链接,文章消息标题 - */ - protected String title; - - /** - * 详细描述 - */ - protected String description; - - /** - * 视频消息缩略图的媒体id - */ - protected String thumbMediaId; - public long getMsgId() { - return msgId; - } - - public void setMsgId(long msgId) { - this.msgId = msgId; - } - - 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 getEvent() { - return event; - } - - public void setEvent(String event) { - this.event = event; - } - - 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 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 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; - } -} diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/MPAct.java b/src/main/java/org/elkan1788/osc/weixin/mp/vo/MPAct.java deleted file mode 100644 index 2095769..0000000 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/MPAct.java +++ /dev/null @@ -1,264 +0,0 @@ -package org.elkan1788.osc.weixin.mp.vo; - -import com.alibaba.fastjson.JSON; -import com.alibaba.fastjson.JSONObject; - -/** - * 微信公众号信息 - * - * @author 凡梦星尘(senhuili@mdc.cn) - * @since 2014/11/8 - * @version 1.0.2 - */ -public class MPAct { - - /** - * 公众号原始ID - */ - private String mpId; - - /** - * 公众号昵称 - */ - private String nickName; - - /** - * 应用Id - */ - private String appId; - - /** - * 应用密钥 - */ - private String appSecret; - - /** - * 令牌 - */ - private String token; - - /** - * AES安全加密密钥 - */ - private String AESKey; - - /** - * 公众号类型 - * D:订阅号 - * E:企业号 - * S:服务号 - * - */ - private String mpType; - - /** - * 是否认证 - */ - private boolean pass; - - /** - * 应用凭证 - */ - private String accessToken; - - /** - * 凭证有效时间(秒) - */ - private long expiresIn; - - /** - * 预授权码 - */ - private String preAuthCode; - - /** - * 预授权码有效时间(秒) - */ - private long preAuthExpiresIn; - - /** - * JSAPI凭证 - */ - private String jsTicket; - - /** - * JSAPI凭证有效时间(秒) - */ - private long jsExpiresIn; - - public String getMpId() { - return mpId; - } - - public void setMpId(String mpId) { - this.mpId = mpId; - } - - public String getNickName() { - return nickName; - } - - public void setNickName(String nickName) { - this.nickName = nickName; - } - - public String getAppId() { - return appId; - } - - public void setAppId(String appId) { - this.appId = appId; - } - - public String getAppSecret() { - return appSecret; - } - - public void setAppSecret(String appSecret) { - this.appSecret = appSecret; - } - - public String getToken() { - return token; - } - - public void setToken(String token) { - this.token = token; - } - - public String getAESKey() { - return AESKey; - } - - public void setAESKey(String AESKey) { - this.AESKey = AESKey; - } - - public String getMpType() { - return mpType; - } - - public void setMpType(String mpType) { - this.mpType = mpType; - } - - public boolean isPass() { - return pass; - } - - public void setPass(boolean pass) { - this.pass = pass; - } - - public String getAccessToken() { - return accessToken; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - public long getExpiresIn() { - return expiresIn; - } - - public void setExpiresIn(long expiresIn) { - this.expiresIn = expiresIn; - } - - /** - * 解析微信服务器返回消息生成高级API或服务的凭证 - * - * @param result 返回消息 - */ - public void createAccessToken(String result) { - - JSONObject tmp = JSON.parseObject(result); - if (tmp.containsKey("access_token")) { - setAccessToken(tmp.getString("access_token")); - } else { - setAccessToken(tmp.getString("component_access_token")); - } - long lose_time = (tmp.getLong("expires_in")-60) * 1000 - + System.currentTimeMillis(); - setExpiresIn(lose_time); - } - - public String getPreAuthCode() { - return preAuthCode; - } - - public void setPreAuthCode(String preAuthCode) { - this.preAuthCode = preAuthCode; - } - - public long getPreAuthExpiresIn() { - return preAuthExpiresIn; - } - - public void setPreAuthExpiresIn(long preAuthExpiresIn) { - this.preAuthExpiresIn = preAuthExpiresIn; - } - - /** - * 解析微信服务器返回消息生成预授权码 - * - * @param result 返回消息 - */ - public void createPreAuthCode(String result) { - JSONObject tmp = JSONObject.parseObject(result); - setPreAuthCode(tmp.getString("pre_auth_code")); - long lose_time = (tmp.getLong("expires_in")-60) * 1000 - + System.currentTimeMillis(); - setPreAuthExpiresIn(lose_time); - } - - public String getJsTicket() { - return jsTicket; - } - - public void setJsTicket(String jsTicket) { - this.jsTicket = jsTicket; - } - - public long getJsExpiresIn() { - return jsExpiresIn; - } - - public void setJsExpiresIn(long jsExpiresIn) { - this.jsExpiresIn = jsExpiresIn; - } - - /** - * 解析微信服务器返回消息生成JSTICKET - * - * @param result 返回消息 - */ - public void createJsTicket(String result) { - JSONObject tmp = JSONObject.parseObject(result); - setJsTicket(tmp.getString("ticket")); - long lose_time = (tmp.getLong("expires_in")-60) * 1000 - + System.currentTimeMillis(); - setJsExpiresIn(lose_time); - } - - @Override - public String toString() { - return "MPAct{" + - "mpId='" + mpId + '\'' + - ", nickName='" + nickName + '\'' + - ", appId='" + appId + '\'' + - ", appSecret='" + appSecret + '\'' + - ", token='" + token + '\'' + - ", AESKey='" + AESKey + '\'' + - ", mpType='" + mpType + '\'' + - ", pass=" + pass + - ", accessToken='" + accessToken + '\'' + - ", expiresIn=" + expiresIn + - ", preAuthCode='" + preAuthCode + '\'' + - ", preAuthExpiresIn=" + preAuthExpiresIn + - ", jsTicket='" + jsTicket + '\'' + - ", jsExpiresIn=" + jsExpiresIn + - '}'; - } -} diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Menu.java b/src/main/java/org/elkan1788/osc/weixin/mp/vo/Menu.java deleted file mode 100644 index 6337d62..0000000 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/Menu.java +++ /dev/null @@ -1,134 +0,0 @@ -package org.elkan1788.osc.weixin.mp.vo; - -import com.alibaba.fastjson.annotation.JSONField; - -import java.util.List; - -/** - * 微信自定义菜单实体 - * - * @author 凡梦星尘(elkan1788@gmail.com) - * @since 2014/11/7 - * @version 1.0.0 - */ -public class Menu { - - public static final String CLICK = "click"; - public static final String VIEW = "view"; - public static final String SCANCODE_PUSH = "scancode_push"; - public static final String 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"; - - /** - * 菜单标题,不超过16个字节,子菜单不超过40个字节 - */ - private String name; - - /** - * 菜单的响应动作类型 - * click:点击推事件 - * view:跳转URL - * scancode_push:扫码推事件 - * scancode_waitmsg:扫码推事件 - * pic_sysphoto:弹出系统拍照发图 - * pic_photo_or_album:弹出拍照或者相册发 - * pic_weixin:弹出微信相册发图器 - * location_select:弹出地理位置选择器 - * - */ - private String type; - - /** - * 点击类型菜单KEY值,用于消息接口推送,不超过128字节 - */ - private String key; - - /** - * 网页链接,用户点击菜单可打开链接,不超过256字节 - */ - private String url; - - /** - * 二级菜单 - */ - @JSONField(name = "sub_button") - private List

    subButtons; - - public Menu() { - } - - public Menu(String name) { - this.name = name; - } - - /** - * 构造函数 - * - * @param name 菜单名称 - * @param type 菜单类型 - * @param val KEY值/URL - */ - public Menu(String name, String type, String val) { - this.name = name; - this.type = type; - if (VIEW.equals(type)) { - this.url = val; - } else { - this.key = val; - } - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - - 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 getSubButtons() { - return subButtons; - } - - public void setSubButtons(List subButtons) { - this.subButtons = subButtons; - } - - @Override - public String toString() { - return "Menu{" + - "name='" + name + '\'' + - ", type='" + type + '\'' + - ", key='" + key + '\'' + - ", url='" + url + '\'' + - ", subButtons=" + subButtons + - '}'; - } -} diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/OutPutMsg.java b/src/main/java/org/elkan1788/osc/weixin/mp/vo/OutPutMsg.java deleted file mode 100644 index 094c514..0000000 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/OutPutMsg.java +++ /dev/null @@ -1,146 +0,0 @@ -package org.elkan1788.osc.weixin.mp.vo; - -import java.util.ArrayList; -import java.util.List; - -/** - * 输出消息实体 - * - * @author 凡梦星尘(elkan1788@gmail.com) - * @since 2014/11/7 - * @version 1.0.0 - */ -public class OutPutMsg extends BaseMsg { - - /** - * 音乐链接 - */ - private String musicUrl; - - /** - * 高质量音乐链接 - */ - private String hQMusicUrl; - - /** - * 多图文消息 - */ - private List
    articles = new ArrayList<>(); - - /** - * 用于群发消息中的分组ID - */ - private String groupId; - - /** - * 用于群发消息中的用户ID,最多支持1000个 - */ - private List toUsers = new ArrayList<>(); - - /** - * 自定义回复内容

    - * 微信太坑人,在开放平台中,LOCATION事件居然可以回复消息

    - * 因此为后续扩展考虑,增加此字段 - */ - private String customReply; - - /** - * 带基础参数的构造函数 - * @param fromUserName 微信用户openId - * @param toUserName 微信公众号原始ID - * @param msgType 消息类型 - */ - public OutPutMsg(String fromUserName, String toUserName, String msgType) { - this.fromUserName = toUserName; - this.toUserName = fromUserName; - this.msgType = msgType; - this.createTime = System.currentTimeMillis() / 1000; - } - - public OutPutMsg(ReceiveMsg rm) { - this.fromUserName = rm.getToUserName(); - this.toUserName = rm.getFromUserName(); - this.createTime = System.currentTimeMillis() / 1000; - } - - public OutPutMsg() { - this.createTime = System.currentTimeMillis() / 1000; - } - - public String getMusicUrl() { - return musicUrl; - } - - public void setMusicUrl(String musicUrl) { - this.musicUrl = musicUrl; - } - - public String gethQMusicUrl() { - return hQMusicUrl; - } - - public void sethQMusicUrl(String hQMusicUrl) { - this.hQMusicUrl = hQMusicUrl; - } - - public List

    getArticles() { - - return articles; - } - - public void setArticles(List
    articles) { - if (null != articles - && articles.size() > 10) { - this.articles = articles.subList(0, 10); - } else { - this.articles = articles; - } - } - - public String getGroupId() { - return groupId; - } - - public void setGroupId(String groupId) { - this.groupId = groupId; - } - - public List getToUsers() { - return toUsers; - } - - public void setToUsers(List toUsers) { - this.toUsers = toUsers; - } - - public String getCustomReply() { - return customReply; - } - - public void setCustomReply(String customReply) { - this.customReply = customReply; - } - - @Override - public String toString() { - return "OutPutMsg{" + - "msgId='" + msgId + '\'' + - ", createTime='" + createTime + '\'' + - ", toUserName='" + toUserName + '\'' + - ", fromUserName='" + fromUserName + '\'' + - ", msgType='" + msgType + '\'' + - ", event='" + event + '\'' + - ", content='" + content + '\'' + - ", mediaId='" + mediaId + '\'' + - ", thumbMediaId='" + thumbMediaId + '\'' + - ", title='" + title + '\'' + - ", description='" + description + '\'' + - ", musicUrl='" + musicUrl + '\'' + - ", hQMusicUrl='" + hQMusicUrl + '\'' + - ", groupId='" + groupId + '\'' + - ", articles=" + ((null==articles) ? 0 : articles.size()) + - ", toUsers=" + ((null==toUsers) ? 0 : toUsers.size()) + - ", customReply=" + customReply + - "} " + super.toString(); - } -} diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/PicInfo.java b/src/main/java/org/elkan1788/osc/weixin/mp/vo/PicInfo.java deleted file mode 100644 index 4c3c755..0000000 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/PicInfo.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.elkan1788.osc.weixin.mp.vo; - -/** - * 弹出微信相册发图器或拍照的照片信息 - * - * @author 凡梦星尘(senhuili@mdc.cn) - * @since 2015/1/7 - * @version 1.0.0 - */ -public class PicInfo { - - /** - * 图片的MD5值,开发者若需要,可用于验证接收到图片 - */ - private String picMd5Sum; - - public String getPicMd5Sum() { - return picMd5Sum; - } - - public void setPicMd5Sum(String picMd5Sum) { - this.picMd5Sum = picMd5Sum; - } - - @Override - public String toString() { - return "PicInfo{" + - "picMd5Sum='" + picMd5Sum + '\'' + - '}'; - } -} diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/ReceiveMsg.java b/src/main/java/org/elkan1788/osc/weixin/mp/vo/ReceiveMsg.java deleted file mode 100644 index 8ebf26f..0000000 --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/ReceiveMsg.java +++ /dev/null @@ -1,376 +0,0 @@ -package org.elkan1788.osc.weixin.mp.vo; - -import java.util.List; - -/** - * 接收消息实体 - * - * @author 凡梦星尘(senhuili@mdc.cn) - * @since 2014/11/7 - * @version 1.0.0 - */ -public class ReceiveMsg extends BaseMsg { - - /** - * 图片链接 - */ - private String picUrl; - - /** - * 音频格式(arm,wav,mp3) - */ - private String format; - - /** - * 语音识别结果,UTF-8编码 - */ - private String recognition; - - /** - * 地图缩放大小 - */ - private int scale; - - /** - * 地理位置信息 - */ - private String label; - - /** - * 链接地址 - */ - private String url; - - /** - * 事件KEY值 - */ - private String eventKey; - - /** - * 二维码的ticket或是开放平台中服务方的ComponentVerifyTicket - */ - private String ticket; - - /** - * 地理位置纬度 - */ - private double latitude; - - /** - * 地理位置经度 - */ - private double longitude; - - /** - * 地理位置精度 - */ - private double precision; - - /** - * 扫描类型,一般是qcode - */ - private String scanType; - - /** - * 扫描结果,即二维码对应的字符串信息 - */ - private String scanResult; - - /** - * 发送的图片数量 - */ - private int count; - - /** - * 发送的图片列表 - */ - private List picList; - - /** - * 朋友圈POI的名字,可能为空 - */ - private String poiName; - - /** - * 微信发送消息状态(模板消息,群发消息)
    -     * 群发的结构,为“send success”或“send fail”或“err(num)”。但send success时,
    -     * 也有可能因用户拒收公众号的消息、系统错误等原因造成少量用户接收失败。err(num)是审核失败的具体原因,
    -     * 可能的情况如下:
    -     * err(10001), //涉嫌广告 
    -     * err(20001), //涉嫌政治 
    -     * err(20004), //涉嫌社会 
    -     * err(20002), //涉嫌色情 
    -     * err(20006), //涉嫌违法犯罪 
    -     * err(20008), //涉嫌欺诈 
    -     * err(20013), //涉嫌版权 
    -     * err(22000), //涉嫌互推(互相宣传) 
    -     * err(21000), //涉嫌其他
    -	 */
    -	private String status;
    -
    -    /**
    -     * group_id下粉丝数;或者openid_list中的粉丝数
    -     */
    -    private int totalCnt;
    -
    -    /**
    -     * 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,
    -     * 用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,
    -     * FilterCount = SentCount + ErrorCount
    -     */
    -    private int filterCnt;
    -
    -    /**
    -     * 发送成功的粉丝数
    -     */
    -    private int sentCnt;
    -
    -    /**
    -     * 发送失败的粉丝数
    -     */
    -    private int errorCnt;
    -
    -    /**
    -     * 服务appid
    -     */
    -    private String appId;
    -
    -    /**
    -     * none,代表该消息推送给服务
    -     * unauthorized,代表公众号取消授权
    -     */
    -    private String infoType;
    -
    -    /**
    -     * 取消授权的公众号
    -     */
    -    private String unAuthAppid;
    -
    -    public String getPicUrl() {
    -        return picUrl;
    -    }
    -
    -    public void setPicUrl(String picUrl) {
    -        this.picUrl = picUrl;
    -    }
    -
    -    public String getFormat() {
    -        return format;
    -    }
    -
    -    public void setFormat(String format) {
    -        this.format = format;
    -    }
    -
    -    public String getRecognition() {
    -        return recognition;
    -    }
    -
    -    public void setRecognition(String recognition) {
    -        this.recognition = recognition;
    -    }
    -
    -    public int getScale() {
    -        return scale;
    -    }
    -
    -    public void setScale(int scale) {
    -        this.scale = scale;
    -    }
    -
    -    public String getLabel() {
    -        return label;
    -    }
    -
    -    public void setLabel(String label) {
    -        this.label = label;
    -    }
    -
    -    public String getUrl() {
    -        return url;
    -    }
    -
    -    public void setUrl(String url) {
    -        this.url = url;
    -    }
    -
    -    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 getScanType() {
    -        return scanType;
    -    }
    -
    -    public void setScanType(String scanType) {
    -        this.scanType = scanType;
    -    }
    -
    -    public String getScanResult() {
    -        return scanResult;
    -    }
    -
    -    public void setScanResult(String scanResult) {
    -        this.scanResult = scanResult;
    -    }
    -
    -    public int getCount() {
    -        return count;
    -    }
    -
    -    public void setCount(int count) {
    -        this.count = count;
    -    }
    -
    -    public List getPicList() {
    -        return picList;
    -    }
    -
    -    public void setPicList(List picList) {
    -        this.picList = picList;
    -    }
    -
    -    public String getPoiName() {
    -        return poiName;
    -    }
    -
    -    public void setPoiName(String poiName) {
    -        this.poiName = poiName;
    -    }
    -
    -    public String getStatus() {
    -        return status;
    -    }
    -
    -    public void setStatus(String status) {
    -        this.status = status;
    -    }
    -
    -    public int getTotalCnt() {
    -        return totalCnt;
    -    }
    -
    -    public void setTotalCnt(int totalCnt) {
    -        this.totalCnt = totalCnt;
    -    }
    -
    -    public int getFilterCnt() {
    -        return filterCnt;
    -    }
    -
    -    public void setFilterCnt(int filterCnt) {
    -        this.filterCnt = filterCnt;
    -    }
    -
    -    public int getSentCnt() {
    -        return sentCnt;
    -    }
    -
    -    public void setSentCnt(int sentCnt) {
    -        this.sentCnt = sentCnt;
    -    }
    -
    -    public int getErrorCnt() {
    -        return errorCnt;
    -    }
    -
    -    public void setErrorCnt(int errorCnt) {
    -        this.errorCnt = errorCnt;
    -    }
    -
    -    public String getAppId() {
    -        return appId;
    -    }
    -
    -    public void setAppId(String appId) {
    -        this.appId = appId;
    -    }
    -
    -    public String getInfoType() {
    -        return infoType;
    -    }
    -
    -    public void setInfoType(String infoType) {
    -        this.infoType = infoType;
    -    }
    -
    -    public String getUnAuthAppid() {
    -        return unAuthAppid;
    -    }
    -
    -    public void setUnAuthAppid(String unAuthAppid) {
    -        this.unAuthAppid = unAuthAppid;
    -    }
    -
    -    @Override
    -    public String toString() {
    -        return "ReceiveMsg{" +
    -                "msgId='" + msgId + '\'' +
    -                ", createTime='" + createTime + '\'' +
    -                ", toUserName='" + toUserName + '\'' +
    -                ", fromUserName='" + fromUserName + '\'' +
    -                ", msgType='" + msgType + '\'' +
    -                ", event='" + event + '\'' +
    -                ", content='" + content + '\'' +
    -                ", mediaId='" + mediaId + '\'' +
    -                ", thumbMediaId='" + thumbMediaId + '\'' +
    -                ", title='" + title + '\'' +
    -                ", description='" + description + '\'' +
    -                ", picUrl='" + picUrl + '\'' +
    -                ", format='" + format + '\'' +
    -                ", recognition='" + recognition + '\'' +
    -                ", scale=" + scale +
    -                ", label='" + label + '\'' +
    -                ", url='" + url + '\'' +
    -                ", eventKey='" + eventKey + '\'' +
    -                ", ticket='" + ticket + '\'' +
    -                ", latitude=" + latitude +
    -                ", longitude=" + longitude +
    -                ", precision=" + precision +
    -                ", status='" + status + '\'' +
    -                ", totalCnt='" + totalCnt + '\'' +
    -                ", filterCnt='" + filterCnt + '\'' +
    -                ", sentCnt='" + sentCnt + '\'' +
    -                ", errorCnt='" + errorCnt + '\'' +
    -                ", appId='" + appId + '\'' +
    -                ", infoType='" + infoType + '\'' +
    -                ", unAuthAppid='" + unAuthAppid + '\'' +
    -                "} ";
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/vo/package-info.java b/src/main/java/org/elkan1788/osc/weixin/mp/vo/package-info.java
    deleted file mode 100644
    index c787e6a..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/vo/package-info.java
    +++ /dev/null
    @@ -1,4 +0,0 @@
    -/**
    - * 微信API对应的实体对象
    - */
    -package org.elkan1788.osc.weixin.mp.vo;
    \ No newline at end of file
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/web/WxServletSupport.java b/src/main/java/org/elkan1788/osc/weixin/mp/web/WxServletSupport.java
    deleted file mode 100644
    index b90217d..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/web/WxServletSupport.java
    +++ /dev/null
    @@ -1,109 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.web;
    -
    -import com.qq.weixin.mp.aes.AesException;
    -import org.elkan1788.osc.weixin.mp.core.WxBase;
    -import org.elkan1788.osc.weixin.mp.core.WxHandler;
    -import org.elkan1788.osc.weixin.mp.vo.MPAct;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -import javax.servlet.ServletException;
    -import javax.servlet.http.HttpServlet;
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
    -import java.io.IOException;
    -import java.io.PrintWriter;
    -
    -/**
    - * Servlet环境接入
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2015/1/13
    - */
    -public abstract class WxServletSupport extends HttpServlet implements WxWebSupport {
    -
    -    private static final Logger log = LoggerFactory.getLogger(WxServletSupport.class);
    -    // 微信基础功能
    -    private WxBase wxBase = new WxBase();
    -
    -    @Override
    -    public void init() throws ServletException {
    -        super.init();
    -        // 重写此方法
    -        // 1.设置微信公众号的信息
    -        // 2.setMpAct();
    -        // 3.setWxHandler();
    -    }
    -
    -    /**
    -     * 处理微信接收URL验证
    -     *
    -     * @param req  请求
    -     * @param resp 响应
    -     * @throws ServletException
    -     * @throws IOException
    -     */
    -    @Override
    -    protected void doGet(HttpServletRequest req,
    -                         HttpServletResponse resp) throws ServletException, IOException {
    -        // 响应设置
    -        resp.setCharacterEncoding("UTF-8");
    -        resp.setContentType("text/html");
    -        this.wxBase.init(req);
    -        try {
    -            String echo = this.wxBase.check();
    -            PrintWriter out = resp.getWriter();
    -            if (!echo.isEmpty()) {
    -                out.print(echo);
    -            } else {
    -                out.print("error");
    -                log.error("微信接入验证URL时失败!!!");
    -                log.error("微信服务器echoStr值: {}", this.wxBase.getEchostr());
    -                log.error("SHA1签名echoStr值: {}", echo);
    -            }
    -        } catch (AesException e) {
    -            log.error("微信接入验证URL时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -    }
    -
    -    /**
    -     * 处理微信普通消息
    -     *
    -     * @param req  请求
    -     * @param resp 响应
    -     * @throws ServletException
    -     * @throws IOException
    -     */
    -    @Override
    -    protected void doPost(HttpServletRequest req,
    -                          HttpServletResponse resp) throws ServletException, IOException {
    -        this.wxBase.init(req);
    -        String result = "error";
    -        try {
    -            result = this.wxBase.handler();
    -        } catch (Exception e) {
    -            log.error("解析微信消息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -        // 响应设置
    -        resp.setCharacterEncoding("UTF-8");
    -        resp.setContentType("text/html");
    -        resp.getWriter().print(result);
    -    }
    -
    -    @Override
    -    public void setMpAct(MPAct mpAct) {
    -        this.wxBase.setMpAct(mpAct);
    -    }
    -
    -    @Override
    -    public void setWxHandler(WxHandler wxHandler) {
    -        this.wxBase.setWxHandler(wxHandler);
    -    }
    -
    -    @Override
    -    public WxBase getWxBase() {
    -        return this.wxBase;
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/web/WxSpringSupport.java b/src/main/java/org/elkan1788/osc/weixin/mp/web/WxSpringSupport.java
    deleted file mode 100644
    index 927922b..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/web/WxSpringSupport.java
    +++ /dev/null
    @@ -1,89 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.web;
    -
    -import com.qq.weixin.mp.aes.AesException;
    -import org.elkan1788.osc.weixin.mp.core.WxBase;
    -import org.elkan1788.osc.weixin.mp.core.WxHandler;
    -import org.elkan1788.osc.weixin.mp.vo.MPAct;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -import javax.servlet.http.HttpServletRequest;
    -import java.io.IOException;
    -
    -/**
    - * SpringMVC环境接入
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2015/1/13
    - */
    -public abstract class WxSpringSupport implements WxWebSupport {
    -
    -    private static final Logger log = LoggerFactory.getLogger(WxSpringSupport.class);
    -    // 微信基础功能
    -    private WxBase wxBase = new WxBase();
    -
    -    protected void init() {
    -        // 重写此方法
    -        // 1.设置微信公众号的信息
    -        // 2.setMpAct();
    -        // 3.setWxHandler();
    -    }
    -
    -    /**
    -     * 与微信信息交互
    -     * 实现时只需写个SpringMVC入口调用此方法即可,入口用ResponseBody输出
    -     *
    -     * @param req   响应
    -     * @return  回复消息
    -     * @throws IOException
    -     */
    -    protected String wxInteract(HttpServletRequest req) throws IOException {
    -        // 准备
    -        this.init();
    -        this.wxBase.init(req);
    -        String reply = "";
    -        // 区分POST与GET来源
    -        String method = req.getMethod();
    -        // 微信接入验证
    -        if ("GET".equals(method)) {
    -            try {
    -                reply = this.wxBase.check();
    -                if (reply.isEmpty()) {
    -                    reply = "error";
    -                    log.error("微信接入验证URL时失败!!!");
    -                    log.error("微信服务器echoStr值: {}", this.wxBase.getEchostr());
    -                    log.error("SHA1签名echoStr值: {}", reply);
    -                }
    -            } catch (AesException e) {
    -                log.error("微信接入验证URL时出现异常!!!");
    -                log.error(e.getLocalizedMessage(), e);
    -            }
    -            return reply;
    -        }
    -
    -        // 信息互动
    -        try {
    -            reply = this.wxBase.handler();
    -        } catch (Exception e) {
    -            log.error("解析微信消息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -        }
    -
    -        return reply;
    -    }
    -
    -    @Override
    -    public void setMpAct(MPAct mpAct) {
    -        this.wxBase.setMpAct(mpAct);
    -    }
    -
    -    @Override
    -    public void setWxHandler(WxHandler wxHandler) {
    -        this.wxBase.setWxHandler(wxHandler);
    -    }
    -
    -    @Override
    -    public WxBase getWxBase() {
    -        return this.wxBase;
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/web/WxStruts2Support.java b/src/main/java/org/elkan1788/osc/weixin/mp/web/WxStruts2Support.java
    deleted file mode 100644
    index e89722d..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/web/WxStruts2Support.java
    +++ /dev/null
    @@ -1,115 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.web;
    -
    -import com.opensymphony.xwork2.Action;
    -import com.opensymphony.xwork2.ActionSupport;
    -import com.qq.weixin.mp.aes.AesException;
    -import org.apache.struts2.interceptor.ServletRequestAware;
    -import org.apache.struts2.interceptor.ServletResponseAware;
    -import org.elkan1788.osc.weixin.mp.core.WxBase;
    -import org.elkan1788.osc.weixin.mp.core.WxHandler;
    -import org.elkan1788.osc.weixin.mp.vo.MPAct;
    -import org.slf4j.Logger;
    -import org.slf4j.LoggerFactory;
    -
    -import javax.servlet.http.HttpServletRequest;
    -import javax.servlet.http.HttpServletResponse;
    -import java.io.IOException;
    -import java.io.PrintWriter;
    -
    -/**
    - * Struts2环境接入
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2015/1/13
    - */
    -public abstract class WxStruts2Support extends ActionSupport implements WxWebSupport, ServletRequestAware, ServletResponseAware {
    -
    -    private static final Logger log = LoggerFactory.getLogger(WxStruts2Support.class);
    -    // 微信基础功能
    -    private WxBase wxBase = new WxBase();
    -    // Http请求
    -    private HttpServletRequest req;
    -    // Http响应
    -    private HttpServletResponse resp;
    -
    -    protected void init() {
    -        // 重写此方法
    -        // 1.设置微信公众号的信息
    -        // 2.setMpAct();
    -        // 3.setWxHandler();
    -    }
    -
    -    /**
    -     * 与微信信息交互
    -     * 实现时只需写个Struts2入口调用此方法即可
    -     *
    -     * @return  回复消息
    -     * @throws java.io.IOException
    -     */
    -    protected void wxInteract() throws IOException {
    -        // 准备
    -        this.init();
    -        this.wxBase.init(req);
    -        // 响应设置
    -        resp.setCharacterEncoding("UTF-8");
    -        resp.setContentType("text/html");
    -        PrintWriter out = resp.getWriter();
    -        String reply = "";
    -        // 区分POST与GET来源
    -        String method = req.getMethod();
    -        // 微信接入验证
    -        if ("GET".equals(method)) {
    -            try {
    -                reply = this.wxBase.check();
    -                if (reply.isEmpty()) {
    -                    reply = "error";
    -                    log.error("微信接入验证URL时失败!!!");
    -                    log.error("微信服务器echoStr值: {}", this.wxBase.getEchostr());
    -                    log.error("SHA1签名echoStr值: {}", reply);
    -                } else {
    -                    out.print(reply);
    -                }
    -            } catch (AesException e) {
    -                log.error("微信接入验证URL时出现异常!!!");
    -                log.error(e.getLocalizedMessage(), e);
    -                out.print("error");
    -            }
    -        }
    -
    -        // 信息互动
    -        try {
    -            reply = this.wxBase.handler();
    -            out.print(reply);
    -        } catch (Exception e) {
    -            log.error("解析微信消息时出现异常!!!");
    -            log.error(e.getLocalizedMessage(), e);
    -            out.print("error");
    -        }
    -
    -    }
    -
    -    @Override
    -    public void setMpAct(MPAct mpAct) {
    -        this.wxBase.setMpAct(mpAct);
    -    }
    -
    -    @Override
    -    public void setWxHandler(WxHandler wxHandler) {
    -        this.wxBase.setWxHandler(wxHandler);
    -    }
    -
    -    @Override
    -    public WxBase getWxBase() {
    -        return this.wxBase;
    -    }
    -
    -    @Override
    -    public void setServletRequest(HttpServletRequest req) {
    -        this.req = req;
    -    }
    -
    -    @Override
    -    public void setServletResponse(HttpServletResponse resp) {
    -        this.resp = resp;
    -    }
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/web/WxWebSupport.java b/src/main/java/org/elkan1788/osc/weixin/mp/web/WxWebSupport.java
    deleted file mode 100644
    index 313ad24..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/web/WxWebSupport.java
    +++ /dev/null
    @@ -1,37 +0,0 @@
    -package org.elkan1788.osc.weixin.mp.web;
    -
    -import org.elkan1788.osc.weixin.mp.core.WxBase;
    -import org.elkan1788.osc.weixin.mp.core.WxHandler;
    -import org.elkan1788.osc.weixin.mp.vo.MPAct;
    -
    -import javax.servlet.http.HttpServletRequest;
    -
    -/**
    - * 微信WEB环境接口设计
    - *
    - * @author 凡梦星尘(elkan1788@gmail.com)
    - * @since 2015/1/13
    - */
    -public interface WxWebSupport {
    -
    -    /**
    -     * 设置微信公众号信息
    -     *
    -     * @param mpAct 公众号信息
    -     */
    -    void setMpAct(MPAct mpAct);
    -
    -    /**
    -     * 设置微信消息处理器
    -     *
    -     * @param wxHandler 微信消息处理器
    -     */
    -    void setWxHandler(WxHandler wxHandler);
    -
    -    /**
    -     * 获取微信基础功能
    -     *
    -     * @return 基础功能
    -     */
    -    WxBase getWxBase();
    -}
    diff --git a/src/main/java/org/elkan1788/osc/weixin/mp/web/package-info.java b/src/main/java/org/elkan1788/osc/weixin/mp/web/package-info.java
    deleted file mode 100644
    index 61444f3..0000000
    --- a/src/main/java/org/elkan1788/osc/weixin/mp/web/package-info.java
    +++ /dev/null
    @@ -1,5 +0,0 @@
    -/**
    - * 敏捷开发的各种WEB环境工具包,实现时只需要重写对应方法即可.

    - * (一直很纠结是否需要添加,感觉会对环境过于依赖,暂定吧) - */ -package org.elkan1788.osc.weixin.mp.web; \ No newline at end of file diff --git a/src/main/resources/ErrorCode.properties b/src/main/resources/ErrorCode.properties new file mode 100644 index 0000000..3893b40 --- /dev/null +++ b/src/main/resources/ErrorCode.properties @@ -0,0 +1,142 @@ +#微信公众平台全局返回码说明(中文) +# +#author:凡梦星尘(elkan1788@gmail.com) +# 高轩雾褪(977903096@qq.com) + +#系统类型 +-1=系统繁忙,此时请开发者稍候再试. +0=请求成功. + +#消息类型 +40001=获取access_token时AppSecret错误,或者access_token无效.\ +请开发者认真比对AppSecret的正确性,或查看是否正在为恰当的公众号调用接口. +40002=不合法的凭证类型. +40003=不合法的OpenID,请开发者确认OpenID(该用户)是否已关注公众号,\ +或是否是其他公众号的OpenID. +40004=不合法的媒体文件类型. +40005=不合法的文件类型. +40006=不合法的文件大小. +40007=不合法的媒体文件id. +40008=不合法的消息类型. +40009=不合法的图片文件大小. +40010=不合法的语音文件大小. +40011=不合法的视频文件大小. +40012=不合法的缩略图文件大小. +40013=不合法的AppID,请开发者检查AppID的正确性,避免异常字符,注意大小写. +40014=不合法的access_token,请开发者认真比对access_token的有效性\ +(如是否过期),或查看是否正在为恰当的公众号调用接口. +40015=不合法的菜单类型. +40016=不合法的按钮个数. +40017=不合法的按钮个数. +40018=不合法的按钮名字长度. +40019=不合法的按钮KEY长度. +40020=不合法的按钮URL长度. +40021=不合法的菜单版本号. +40022=不合法的子菜单级数. +40023=不合法的子菜单按钮个数. +40024=不合法的子菜单按钮类型. +40025=不合法的子菜单按钮名字长度. +40026=不合法的子菜单按钮KEY长度. +40027=不合法的子菜单按钮URL长度. +40028=不合法的自定义菜单使用用户. +40029=不合法的oauth_code. +40030=不合法的refresh_token. +40031=不合法的openid列表. +40032=不合法的openid列表长度. +40033=不合法的请求字符,不能包含\\uxxxx格式的字符. +40035=不合法的参数. +40038=不合法的请求格式. +40039=不合法的URL长度. +40050=不合法的分组id. +40051=分组名字不合法. +40117=分组名字不合法. +40118=media_id大小不合法. +40119=button类型错误. +40120=button类型错误. +40121=不合法的media_id类型. +40132=微信号不合法. +40137=不支持的图片格式. +41001=缺少access_token参数. +41002=缺少appid参数. +41003=缺少refresh_token参数. +41004=缺少secret参数. +41005=缺少多媒体文件数据. +41006=缺少media_id参数. +41007=缺少子菜单数据. +41008=缺少oauthcode. +41009=缺少openid. +42001=access_token超时,请检查access_token的有效期,请参考基础支持-获取\ +access_token中,对access_token的详细机制说明. +42002=refresh_token超时. +42003=oauth_code超时. +43001=需要GET请求. +43002=需要POST请求. +43003=需要HTTPS请求. +43004=需要接收者关注. +43005=需要好友关系. +44001=多媒体文件为空. +44002=POST的数据包为空. +44003=图文消息内容为空. +44004=文本消息内容为空. +45001=多媒体文件大小超过限制. +45002=消息内容超过限制. +45003=标题字段超过限制. +45004=描述字段超过限制. +45005=链接字段超过限制. +45006=图片链接字段超过限制. +45007=语音播放时间超过限制. +45008=图文消息超过限制. +45009=接口调用超过限制. +45010=创建菜单个数超过限制. +45011=API调用频率太快受限制. +45015=回复时间超过限制. +45016=系统分组,不允许修改. +45017=分组名字过长. +45018=分组数量超过上限. +46001=不存在媒体数据. +46002=不存在的菜单版本. +46003=不存在的菜单数据. +46004=不存在的用户. +47001=解析JSON/XML内容错误. +48001=api功能未授权,请确认公众号已获得该接口,可以在\ +公众平台官网-开发者中心页中查看接口权限. +50001=用户未授权该api. +50002=用户受限,可能是违规后接口被封禁. +61451=参数错误. +61452=无效客服账号. +61453=客服帐号已存在. +61454=客服帐号名长度超过限制(仅允许10个英文字符,\ +不包括@及@后的公众号的微信号). +61455=客服帐号名包含非法字符(仅允许英文+数字). +61456=客服帐号个数超过限制(10个客服账号). +61457=无效头像文件类型. +61450=系统错误. +61500=日期格式错误. +61501=日期范围错误. +9001001=POST数据参数不合法. +9001002=远端服务不可用. +9001003=Ticket不合法. +9001004=获取摇周边用户信息失败. +9001005=获取商户信息失败. +9001006=获取OpenID失败. +9001007=上传文件缺失. +9001008=上传素材的文件类型不合法. +9001009=上传素材的文件尺寸不合法. +9001010=上传失败. +9001020=帐号不合法. +9001021=已有设备激活率低于50%,不能新增设备. +9001022=设备申请数不合法,必须为大于0的数字. +9001023=已存在审核中的设备ID申请. +9001024=一次查询设备ID数量不能超过50. +9001025=设备ID不合法. +9001026=页面ID不合法. +9001027=页面参数不合法. +9001028=一次删除页面ID数量不能超过10. +9001029=页面已应用在设备中,请先解除应用关系再删除. +9001030=一次查询页面ID数量不能超过50. +9001031=时间区间不合法. +9001032=保存设备与页面的绑定关系参数错误. +9001033=门店ID不合法. +9001034=设备备注信息过长. +9001035=设备申请参数不合法. +9001036=查询起始值begin不合法. diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties deleted file mode 100644 index 65c9a74..0000000 --- a/src/main/resources/log4j.properties +++ /dev/null @@ -1,6 +0,0 @@ -log4j.rootLogger=DEBUG,Console - -log4j.appender.Console=org.apache.log4j.ConsoleAppender -log4j.appender.Console.Target=System.out -log4j.appender.Console.layout=org.apache.log4j.PatternLayout -log4j.appender.Console.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH\:mm\:ss,SSS}][%c] %m%n \ No newline at end of file diff --git a/src/main/resources/test-cfg.properties b/src/main/resources/test-cfg.properties deleted file mode 100644 index 63e4a0f..0000000 --- a/src/main/resources/test-cfg.properties +++ /dev/null @@ -1,14 +0,0 @@ -## 微信API测试公众号信息 -mpId= -openId= -appId= -appSecret= -token= -aesKey= -accessToken= - -mediaId= -msgSing= -timestamp= -echostr= -nonce= \ No newline at end of file diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/Mpsdk4jTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/Mpsdk4jTest.java new file mode 100644 index 0000000..712d24d --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/Mpsdk4jTest.java @@ -0,0 +1,29 @@ +package io.github.elkan1788.mpsdk4j; + +import org.junit.Assert; +import org.junit.Test; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * 测试Mpsdk4j + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class Mpsdk4jTest { + + private static final Log log = Logs.get(); + + /** + * Test method for {@link io.github.elkan1788.mpsdk4j.Mpsdk4j#version()}. + */ + @Test + public void testVersion() { + new Mpsdk4j(); // Just cover testing + log.debug(Mpsdk4j.version()); + String curver = "2.b.1"; + Assert.assertEquals(curver, Mpsdk4j.version()); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/RunTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/RunTest.java new file mode 100644 index 0000000..acfe9a4 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/RunTest.java @@ -0,0 +1,31 @@ +package io.github.elkan1788.mpsdk4j; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +import io.github.elkan1788.mpsdk4j.api.APITest; +import io.github.elkan1788.mpsdk4j.core.CoreTest; +import io.github.elkan1788.mpsdk4j.mvc.MVCTest; +import io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes.AESTest; +import io.github.elkan1788.mpsdk4j.util.UtilTest; +import io.github.elkan1788.mpsdk4j.vo.VOTest; + +/** + * Test all class + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + Mpsdk4jTest.class, + UtilTest.class, + VOTest.class, + APITest.class, + CoreTest.class, + AESTest.class, + MVCTest.class +}) +public class RunTest { + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/TestSupport.java b/src/test/java/io/github/elkan1788/mpsdk4j/TestSupport.java new file mode 100644 index 0000000..8369a4f --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/TestSupport.java @@ -0,0 +1,16 @@ +package io.github.elkan1788.mpsdk4j; + +import io.github.elkan1788.mpsdk4j.util.ConfigReader; + +/** + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class TestSupport { + + protected static ConfigReader _cr; + + static { + _cr = new ConfigReader("/mpconf.properties"); + } +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/api/APITest.java b/src/test/java/io/github/elkan1788/mpsdk4j/api/APITest.java new file mode 100644 index 0000000..6bbd0fb --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/api/APITest.java @@ -0,0 +1,24 @@ +package io.github.elkan1788.mpsdk4j.api; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * API 包测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + CredentialAPITest.class, + MenuAPITest.class, + MediaAPITest.class, + GroupsAPITest.class, + QRCodeAPITest.class, + UserAPITest.class, + TemplateAPITest.class +}) +public class APITest { + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/api/APITestSupport.java b/src/test/java/io/github/elkan1788/mpsdk4j/api/APITestSupport.java new file mode 100644 index 0000000..29a0f76 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/api/APITestSupport.java @@ -0,0 +1,35 @@ +package io.github.elkan1788.mpsdk4j.api; + +import org.junit.Before; + +import io.github.elkan1788.mpsdk4j.TestSupport; +import io.github.elkan1788.mpsdk4j.vo.MPAccount; + +public class APITestSupport extends TestSupport { + + protected MPAccount mpAct; + protected static String openId; + protected static String openId2; + protected static int groupId; + protected static String accessToken; + protected static String mediaId; + protected static String ticket; + protected static String tmplId; + + @Before + public void init() { + mpAct = new MPAccount(); + mpAct.setMpId(_cr.get("mpId")); + mpAct.setAppId(_cr.get("appId")); + mpAct.setAppSecret(_cr.get("appSecret")); + + openId = _cr.get("openId"); + openId2 = _cr.get("openId2"); + groupId = _cr.getInt("groupId"); + accessToken = _cr.get("accessToken"); + mediaId = _cr.get("mediaId"); + ticket = _cr.get("ticket"); + tmplId = _cr.get("tmplId"); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/api/CredentialAPITest.java b/src/test/java/io/github/elkan1788/mpsdk4j/api/CredentialAPITest.java new file mode 100644 index 0000000..43d13a8 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/api/CredentialAPITest.java @@ -0,0 +1,71 @@ +package io.github.elkan1788.mpsdk4j.api; + +import static org.junit.Assert.assertNotNull; + +import java.util.List; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * CredentialApi 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class CredentialAPITest extends APITestSupport { + + private static final Log log = Logs.get(); + + private CredentialAPI ca; + + @Override + @Before + public void init() { + log.info("====== CredentialAPITest ======"); + super.init(); + ca = WechatAPIImpl.create(mpAct); + } + + @Test + public void testGetAccessToken() { + log.info("====== CredentialAPI#getAccessToken ======"); + assertNotNull(ca.getAccessToken()); + accessToken = ca.getAccessToken(); + log.info(ca.getAccessToken()); + } + + @Test + public void testGetServerIps() { + log.info("====== CredentialAPI#getServerIps ======"); + List ips = ca.getServerIps(); + assertNotNull(ips); + int i = 0; + for (String ip : ips) { + i++; + log.infof("Wechat server[%d] ip: %s", i, ip); + } + } + + @Test + public void testGetShorUrl() { + log.info("====== CredentialAPI#getShortUrl ======"); + String longurl = "https://mp.weixin.qq.com/wiki/10/165c9b15eddcfbd8699ac12b0bd89ae6.html"; + String shorurl = ca.getShortUrl(longurl); + assertNotNull(shorurl); + log.info(shorurl); + } + + @Test + public void testGetJSTicket() { + log.info("====== CredentialAPI#getJSTicket ======"); + String jsticket = ca.getJSTicket(); + assertNotNull(jsticket); + log.info(jsticket); + } +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/api/GroupsAPITest.java b/src/test/java/io/github/elkan1788/mpsdk4j/api/GroupsAPITest.java new file mode 100644 index 0000000..ce7d16b --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/api/GroupsAPITest.java @@ -0,0 +1,106 @@ +package io.github.elkan1788.mpsdk4j.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +import io.github.elkan1788.mpsdk4j.vo.api.Groups; + +/** + * GroupsAPI 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@FixMethodOrder(MethodSorters.JVM) +public class GroupsAPITest extends APITestSupport { + + private static final Log log = Logs.get(); + + private GroupsAPI ga; + + @Override + @Before + public void init() { + log.info("====== GroupsAPITest ======"); + super.init(); + ga = WechatAPIImpl.create(mpAct); + } + + @Test + public void testCreateGroup() { + log.info("====== GroupsAPI#createGroup ======"); + groupId = ga.createGroup("测试分组"); + assertTrue(groupId > 0); + log.info(groupId); + } + + @Test + public void testGetGroups() { + log.info("====== GroupsAPI#getGroups ======"); + log.infof("groupId: %d", groupId); + List gs = ga.getGroups(); + assertNotNull(gs); + for (Groups g : gs) { + log.info(g); + } + } + + @Test + public void testRenGroups() { + log.info("====== GroupsAPI#renGroups ======"); + boolean flag = ga.renGroups(groupId, "测试分组改名"); + assertTrue(flag); + log.info(flag); + } + + @Test + public void testMove2Group() throws Exception { + log.info("====== GroupsAPI#move2Group ======"); + log.info(groupId); + boolean flag = ga.move2Group(openId, groupId); + assertTrue(flag); + log.info(flag); + Thread.sleep(5 * 1000); + } + + // TODO 明明已移动用户到新分组,却还在系统分组,莫非腾讯服务器响应缓存/缓慢!!! + @Test + public void testGetGroup() { + log.info("====== GroupsAPI#getGroup ======"); + int id = ga.getGroup(openId); + assertEquals(id, groupId); + log.info(id); + } + + // TODO 一直提示需要关注用户才可以,莫名奇妙!!! + @Test(expected = RuntimeException.class) + public void testBatchMove2Group() { + log.info("====== GroupsAPI#batchMove2Group ======"); + List openIds = new ArrayList(); + openIds.add(openId); + openIds.add(openId2); + boolean flag = ga.batchMove2Group(openIds, groupId); + assertTrue(flag); + log.info(flag); + } + + @Test + public void testDelGroup() { + log.info("====== GroupsAPI#delGroup ======"); + boolean flag = ga.delGroup(groupId); + assertTrue(flag); + log.info(flag); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/api/MediaAPITest.java b/src/test/java/io/github/elkan1788/mpsdk4j/api/MediaAPITest.java new file mode 100644 index 0000000..2745eea --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/api/MediaAPITest.java @@ -0,0 +1,54 @@ +package io.github.elkan1788.mpsdk4j.api; + +import static org.junit.Assert.assertNotNull; +import io.github.elkan1788.mpsdk4j.common.MediaType; +import io.github.elkan1788.mpsdk4j.vo.api.Media; + +import java.io.File; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@FixMethodOrder(MethodSorters.JVM) +public class MediaAPITest extends APITestSupport { + + private static final Log log = Logs.get(); + + private MediaAPI ma; + + @Override + @Before + public void init() { + log.info("====== MediaAPITest ======"); + super.init(); + ma = WechatAPIImpl.create(mpAct); + } + + @Test + public void testUploadImage() { + log.info("====== MediaAPI#upMedia ======"); + File media = new File(this.getClass().getResource("/mpsdk4j-logo.png").getFile()); + Media m = ma.upMedia(MediaType.image.name(), media); + assertNotNull(m); + log.info(m); + } + + @Test + public void testGet() { + log.info("====== MediaAPI#dlMedia ======"); + String mediaId = _cr.get("mediaId"); + File media = ma.dlMedia(mediaId); + assertNotNull(media); + log.info(media.getAbsolutePath()); + log.info(media.getName()); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/api/MenuAPITest.java b/src/test/java/io/github/elkan1788/mpsdk4j/api/MenuAPITest.java new file mode 100644 index 0000000..6a56712 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/api/MenuAPITest.java @@ -0,0 +1,76 @@ +package io.github.elkan1788.mpsdk4j.api; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +import io.github.elkan1788.mpsdk4j.common.EventType; +import io.github.elkan1788.mpsdk4j.vo.api.Menu; + +/** + * CustomMenuAPI 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@FixMethodOrder(MethodSorters.JVM) +public class MenuAPITest extends APITestSupport { + + private static final Log log = Logs.get(); + + private MenuAPI cma; + + @Override + @Before + public void init() { + log.info("====== MenuAPITest ======"); + super.init(); + cma = WechatAPIImpl.create(mpAct); + } + + @Test + public void testDelMenu() { + log.info("====== MenuAPI#delMenu ======"); + List

    menus = cma.getMenu(); + assertNotNull(menus); + for (Menu m : menus) { + log.info(m); + } + + boolean flag = cma.delMenu(); + assertTrue(flag); + + menus = cma.getMenu(); + assertTrue(menus == null); + } + + @Test + public void testCreateMenu() { + log.info("====== MenuAPI#createMenu ======"); + Menu csdn = new Menu("CSND"); + csdn.setType(EventType.VIEW.name()); + csdn.setUrl("http://www.csdn.net"); + + Menu oschina = new Menu("OSCHINA"); + oschina.setType(EventType.VIEW.name()); + oschina.setUrl("http://www.oschina.net"); + + Menu map = new Menu("Map"); + Menu baidu = new Menu("BaiDu", EventType.VIEW.name(), "http://map.baidu.com"); + Menu tencent = new Menu("Tencent", EventType.VIEW.name(), "http://map.qq.com"); + Menu ali = new Menu("Ali", EventType.VIEW.name(), "http://ditu.amap.com/"); + map.setSubButtons(Arrays.asList(baidu, tencent, ali)); + + boolean flag = cma.createMenu(csdn, oschina, map); + assertTrue(flag); + } +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/api/MessageAPITest.java b/src/test/java/io/github/elkan1788/mpsdk4j/api/MessageAPITest.java new file mode 100644 index 0000000..15994dc --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/api/MessageAPITest.java @@ -0,0 +1,44 @@ +package io.github.elkan1788.mpsdk4j.api; + +import static org.junit.Assert.assertTrue; +import io.github.elkan1788.mpsdk4j.vo.api.Template; + +import org.junit.Before; +import org.junit.Test; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * MessageAPI 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class MessageAPITest extends APITestSupport { + + private static final Log log = Logs.get(); + + private MessageAPI ma; + + private String openId; + + @Before + public void init() { + log.info("====== MessageAPITest ======"); + super.init(); + this.openId = _cr.get("openId"); + ma = WechatAPIImpl.create(mpAct); + } + + @Test + public void testSendTemplateMsg() { + log.info("====== MessageAPI#sendTemplateMsg ======"); + String tmlId = _cr.get("tmplId"); + Template tml = new Template("title", "测试"); + String url = "https://www.google.com"; + long msgid = ma.sendTemplateMsg(openId, tmlId, "#119EF3", url, tml); + assertTrue(msgid > 0); + log.info(msgid); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/api/QRCodeAPITest.java b/src/test/java/io/github/elkan1788/mpsdk4j/api/QRCodeAPITest.java new file mode 100644 index 0000000..4bb3bb7 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/api/QRCodeAPITest.java @@ -0,0 +1,57 @@ +package io.github.elkan1788.mpsdk4j.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import io.github.elkan1788.mpsdk4j.vo.api.QRTicket; + +import java.io.File; + +import org.junit.Before; +import org.junit.Test; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * QRCodeAPI 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +// TODO 此API貌似有问题,以后再好好检测 +public class QRCodeAPITest extends APITestSupport { + + private static final Log log = Logs.get(); + + private QRCodeAPI qa; + private int expireSeconds = 604800; + private String ticket; + + @Override + @Before + public void init() { + log.info("====== QRCodeAPITest ======"); + super.init(); + this.ticket = _cr.get("ticket"); + qa = WechatAPIImpl.create(mpAct); + } + + @Test + public void testCreateQR() { + log.info("====== QRCodeAPI#createQR ======"); + QRTicket qrt = qa.createQR(10, expireSeconds); + assertNotNull(qrt); + assertEquals(qrt.getExpireSeconds(), expireSeconds); + log.infof("QR ticket: %s", qrt.getTicket()); + log.infof("QR expire time: %s", qrt.getExpireSeconds()); + log.infof("QR url: %s", qrt.getUrl()); + } + + @Test(expected = RuntimeException.class) + public void testGetQR() { + log.info("====== QRCodeAPI#getQR ======"); + File qrImg = qa.getQR(ticket); + assertNotNull(qrImg); + log.infof("temp path: %s", qrImg.getAbsolutePath()); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/api/TemplateAPITest.java b/src/test/java/io/github/elkan1788/mpsdk4j/api/TemplateAPITest.java new file mode 100644 index 0000000..6fba588 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/api/TemplateAPITest.java @@ -0,0 +1,46 @@ +package io.github.elkan1788.mpsdk4j.api; + +import org.junit.Before; +import org.junit.Test; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * TemplateAPI 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +// TODO 此为新接口待测试 +public class TemplateAPITest extends APITestSupport { + + private static final Log log = Logs.get(); + + @SuppressWarnings("unused") + private TemplateAPI ta; + + @Override + @Before + public void init() { + log.info("====== TemplateAPITest ======"); + super.init(); + ta = WechatAPIImpl.create(mpAct); + } + + @Test + public void testSetIndustry() { + log.info("====== TemplateAPITest#setIndustry ======"); + // boolean flag = ta.setIndustry(1, 2); + // assertTrue(flag); + // log.info(flag); + } + + @Test + public void testGetTemplateId() { + log.info("====== TemplateAPITest#getTemplateId ======"); + // String tmplid = ta.getTemplateId("5A90LqXLMHUVd0d1PFv-TezTxYWf2PBDV1APvAMeb1E"); + // assertNotNull(tmplid); + // log.info(tmplid); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/api/UserAPITest.java b/src/test/java/io/github/elkan1788/mpsdk4j/api/UserAPITest.java new file mode 100644 index 0000000..7e2930e --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/api/UserAPITest.java @@ -0,0 +1,76 @@ +package io.github.elkan1788.mpsdk4j.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import io.github.elkan1788.mpsdk4j.vo.api.FollowList; +import io.github.elkan1788.mpsdk4j.vo.api.Follower; +import io.github.elkan1788.mpsdk4j.vo.api.Follower2; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * UserAPI 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@FixMethodOrder(MethodSorters.JVM) +public class UserAPITest extends APITestSupport { + + private static final Log log = Logs.get(); + + private UserAPI ua; + + @Before + public void init() { + log.info("====== UserAPITest ====== "); + super.init(); + ua = WechatAPIImpl.create(mpAct); + } + + @Test + public void testGetFollowerList() { + log.info("====== UserAPI#getFollowerList ====== "); + FollowList fl = ua.getFollowerList(null); + assertNotNull(fl); + log.info(fl); + } + + @Test + public void testGetFollower() { + log.info("====== UserAPI#getFollower ====== "); + Follower f = ua.getFollower(openId, null); + assertNotNull(f); + log.info(f); + } + + @Test + public void testUpdateRemark() { + log.info("====== UserAPI#updateRemark ====== "); + boolean flag = ua.updateRemark(openId, "Youself"); + assertTrue(flag); + log.info(flag); + } + + @Test + public void testGetFollowers() { + log.info("====== UserAPI#getFollowers ====== "); + List getfs = new ArrayList(); + getfs.add(new Follower2(openId)); + getfs.add(new Follower2(_cr.get("openId2"))); + + List fs = ua.getFollowers(getfs); + assertNotNull(fs); + assertEquals(2, fs.size()); + log.info(fs); + } +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/core/CoreTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/core/CoreTest.java new file mode 100644 index 0000000..cb6d23a --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/core/CoreTest.java @@ -0,0 +1,21 @@ +package io.github.elkan1788.mpsdk4j.core; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * core 包测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + JsonMsgBuilderTest.class, + MessageHandlerTest.class, + XmlMsgBuilderTest.class, + WechatKernelTest.class +}) +public class CoreTest { + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/core/JsonMsgBuilderTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/core/JsonMsgBuilderTest.java new file mode 100644 index 0000000..740ff1c --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/core/JsonMsgBuilderTest.java @@ -0,0 +1,150 @@ +package io.github.elkan1788.mpsdk4j.core; + +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import io.github.elkan1788.mpsdk4j.api.APITestSupport; +import io.github.elkan1788.mpsdk4j.vo.api.Template; +import io.github.elkan1788.mpsdk4j.vo.message.Article; +import io.github.elkan1788.mpsdk4j.vo.message.ImageMsg; +import io.github.elkan1788.mpsdk4j.vo.message.MusicMsg; +import io.github.elkan1788.mpsdk4j.vo.message.NewsMsg; +import io.github.elkan1788.mpsdk4j.vo.message.TextMsg; +import io.github.elkan1788.mpsdk4j.vo.message.VideoMsg; +import io.github.elkan1788.mpsdk4j.vo.message.VoiceMsg; + +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; +import org.nutz.json.Json; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * JsonBuilder 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class JsonMsgBuilderTest extends APITestSupport { + + private static final Log log = Logs.get(); + + @Before + public void init() { + log.info("====== JsonMsgBuilderTest ======"); + super.init(); + } + + @Test + public void testText() { + log.info("====== JsonMsgBuilder#text ======"); + TextMsg tm = new TextMsg(); + tm.setToUserName(openId); + tm.setContent("Hello world! 世界,你好!"); + String textJson = JsonMsgBuilder.create().text(tm).build(); + assertNotNull(textJson); + assertNotEquals(-1, textJson.indexOf("text")); + assertNotNull(Json.fromJson(textJson)); + log.info(textJson); + } + + @Test + public void testImage() { + log.info("====== JsonMsgBuilder#image ======"); + ImageMsg im = new ImageMsg(); + im.setToUserName(openId); + im.setMediaId(mediaId); + String imgJson = JsonMsgBuilder.create().image(im).build(); + assertNotNull(imgJson); + assertNotEquals(-1, imgJson.indexOf("image")); + assertNotNull(Json.fromJson(imgJson)); + log.info(imgJson); + } + + @Test + public void testVoice() { + log.info("====== JsonMsgBuilder#voice ======"); + VoiceMsg vom = new VoiceMsg(); + vom.setToUserName(openId); + vom.setMediaId(mediaId); + String voiceJson = JsonMsgBuilder.create().voice(vom).build(); + assertNotNull(voiceJson); + assertNotEquals(-1, voiceJson.indexOf("voice")); + assertNotNull(Json.fromJson(voiceJson)); + log.info(voiceJson); + } + + @Test + public void testVideo() { + log.info("====== JsonMsgBuilder#video ======"); + VideoMsg vim = new VideoMsg(); + vim.setToUserName(openId); + vim.setMediaId(mediaId); + vim.setThumbMediaId(mediaId); + vim.setTitle("Video message"); + vim.setDescription("Video message."); + String videoJson = JsonMsgBuilder.create().video(vim).build(); + assertNotNull(videoJson); + assertNotEquals(-1, videoJson.indexOf("video")); + assertNotNull(Json.fromJson(videoJson)); + log.info(videoJson); + } + + @Test + public void testMusic() { + log.info("====== JsonMsgBuilder#music ======"); + MusicMsg mm = new MusicMsg(); + mm.setToUserName(openId); + mm.setThumbMediaId(mediaId); + mm.setTitle("致爱 Your Song"); + mm.setDescription("Music message."); + mm.setMusicUrl("http://y.qq.com/#type=song&mid=002IVyIU4093Xr&play=0"); + mm.setHQMusicUrl("http://y.qq.com/#type=song&mid=002IVyIU4093Xr&play=0"); + String musicJson = JsonMsgBuilder.create().music(mm).build(); + assertNotNull(musicJson); + assertNotEquals(-1, musicJson.indexOf("music")); + assertNotNull(Json.fromJson(musicJson)); + log.info(musicJson); + } + + @Test + public void testNews() { + log.info("====== JsonMsgBuilder#news ======"); + NewsMsg nm = new NewsMsg(); + nm.setToUserName(openId); + Article art = new Article(); + art.setTitle("Wechat news message."); + art.setDescription("Wechat news message Description."); + art.setPicUrl("http://www.baidu.com"); + art.setUrl("http://www.baidu.com"); + nm.setArticles(Arrays.asList(art)); + String newsJson = JsonMsgBuilder.create().news(nm).build(); + assertNotNull(newsJson); + assertNotEquals(-1, newsJson.indexOf("articles")); + assertNotNull(Json.fromJson(newsJson)); + log.info(newsJson); + } + + @Test + public void testTemplate() { + log.info("====== JsonMsgBuilder#template ======"); + Template tmp = new Template(); + tmp.setColor("#ff0000"); + tmp.setName("title"); + tmp.setValue("Wechat template message."); + + String tmpJson = JsonMsgBuilder.create() + .template(openId, + tmplId, + "#ff0000", + "http://www.baidu.com", + tmp) + .build(); + assertNotNull(tmpJson); + assertNotEquals(-1, tmpJson.indexOf("data")); + assertNotNull(Json.fromJson(tmpJson)); + log.info(tmpJson); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/core/MessageHandlerTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/core/MessageHandlerTest.java new file mode 100644 index 0000000..1731a89 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/core/MessageHandlerTest.java @@ -0,0 +1,214 @@ +package io.github.elkan1788.mpsdk4j.core; + +import static org.junit.Assert.assertNotNull; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.junit.Before; +import org.junit.Test; +import org.nutz.lang.Lang; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +import io.github.elkan1788.mpsdk4j.util.StreamTool; +import io.github.elkan1788.mpsdk4j.vo.event.SendLocationInfoEvent; +import io.github.elkan1788.mpsdk4j.vo.event.SendPhotosEvent; +import io.github.elkan1788.mpsdk4j.vo.message.ImageMsg; +import io.github.elkan1788.mpsdk4j.vo.message.LinkMsg; +import io.github.elkan1788.mpsdk4j.vo.message.LocationMsg; +import io.github.elkan1788.mpsdk4j.vo.message.TextMsg; +import io.github.elkan1788.mpsdk4j.vo.message.VideoMsg; +import io.github.elkan1788.mpsdk4j.vo.message.VoiceMsg; + +/** + * 测试XML读取 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class MessageHandlerTest { + + private static final Log log = Logs.get(); + + private SAXParserFactory factory = SAXParserFactory.newInstance(); + private SAXParser xmlParser; + private MessageHandler msgHandler = new MessageHandler(); + + @Before + public void init() { + log.info("====== MessageHandlerTest ======"); + try { + xmlParser = factory.newSAXParser(); + } + catch (Exception e) { + throw Lang.wrapThrow(e); + } + } + + @Test + public void testTextMessage() throws Exception { + log.info("====== MessageHandler#text ======"); + String textXml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "6091046778677430\n" + + ""; + + xmlParser.parse(StreamTool.toStream(textXml), msgHandler); + TextMsg tm = new TextMsg(msgHandler.getValues()); + assertNotNull(tm); + log.info(tm); + } + + @Test + public void testImageMessage() throws Exception { + log.info("====== MessageHandler#image ======"); + String imgXml = "\n" + + "\n" + + "1418182361\n" + + "\n" + + "\n" + + "\n" + + "6091046778677460\n" + + ""; + + xmlParser.parse(StreamTool.toStream(imgXml), msgHandler); + ImageMsg im = new ImageMsg(msgHandler.getValues()); + assertNotNull(im); + log.info(im); + } + + @Test + public void testVoiceMessage() throws Exception { + log.info("====== MessageHandler#voice ======"); + String voiceXml = "\n" + + "\n" + + "1418182361\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "6091046778677460\n" + + ""; + xmlParser.parse(StreamTool.toStream(voiceXml), msgHandler); + VoiceMsg vm = new VoiceMsg(msgHandler.getValues()); + assertNotNull(vm); + log.info(vm); + } + + @Test + public void testVideoMessage() throws Exception { + log.info("====== MessageHandler#video ======"); + String videoXml = "\n" + + "\n" + + "1418182361\n" + + "\n" + + "\n" + + "\n" + + "6091046778677460\n" + + ""; + xmlParser.parse(StreamTool.toStream(videoXml), msgHandler); + VideoMsg vm = new VideoMsg(msgHandler.getValues()); + assertNotNull(vm); + log.info(vm); + } + + @Test + public void testShortVideoMessage() throws Exception { + log.info("====== MessageHandler#shortvideo ======"); + String videoXml = "\n" + + "\n" + + "1418182361\n" + + "\n" + + "\n" + + "\n" + + "6091046778677460\n" + + ""; + xmlParser.parse(StreamTool.toStream(videoXml), msgHandler); + VideoMsg vm = new VideoMsg(msgHandler.getValues()); + assertNotNull(vm); + log.info(vm); + } + + @Test + public void testLocationMessage() throws Exception { + log.info("====== MessageHandler#location ======"); + String videoXml = "\n" + + "\n" + + "1418182361\n" + + "\n" + + "23.134521\n" + + "113.358803\n" + + "20\n" + + "\n" + + "6091046778677460\n" + + ""; + xmlParser.parse(StreamTool.toStream(videoXml), msgHandler); + LocationMsg lm = new LocationMsg(msgHandler.getValues()); + assertNotNull(lm); + log.info(lm); + } + + @Test + public void testLinkMessage() throws Exception { + log.info("====== MessageHandler#link ======"); + String videoXml = "\n" + + "\n" + + "1418182361\n" + + "\n" + + "<![CDATA[Google]]>\n" + + "\n" + + "\n" + + "6091046778677460\n" + + ""; + xmlParser.parse(StreamTool.toStream(videoXml), msgHandler); + LinkMsg lm = new LinkMsg(msgHandler.getValues()); + assertNotNull(lm); + log.info(lm); + } + + @Test + public void testSendLocationMessage() throws Exception { + log.info("====== MessageHandler#sendlocation ======"); + String videoXml = "\n" + + "\n" + + "1418182361\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + xmlParser.parse(StreamTool.toStream(videoXml), msgHandler); + SendLocationInfoEvent slife = new SendLocationInfoEvent(msgHandler.getValues()); + assertNotNull(slife); + log.info(slife); + } + + @Test + public void testSendPicsMessage() throws Exception { + log.info("====== MessageHandler#sendpics ======"); + String videoXml = "\n" + + "\n" + + "1418182361\n" + + "\n" + + "\n" + + "\n" + + "2\n" + + "\n" + + "\n" + + "\n" + + ""; + xmlParser.parse(StreamTool.toStream(videoXml), msgHandler); + SendPhotosEvent spe = new SendPhotosEvent(msgHandler.getValues()); + assertNotNull(spe); + log.info(spe); + } +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/core/WechatKernelTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/core/WechatKernelTest.java new file mode 100644 index 0000000..25d80bc --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/core/WechatKernelTest.java @@ -0,0 +1,726 @@ +package io.github.elkan1788.mpsdk4j.core; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.nutz.lang.random.R; +import org.nutz.lang.random.StringGenerator; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +import io.github.elkan1788.mpsdk4j.TestSupport; +import io.github.elkan1788.mpsdk4j.util.StreamTool; +import io.github.elkan1788.mpsdk4j.vo.MPAccount; +import io.github.elkan1788.mpsdk4j.vo.message.BasicMsg; +import io.github.elkan1788.mpsdk4j.vo.message.MusicMsg; +import io.github.elkan1788.mpsdk4j.vo.message.VideoMsg; +import io.github.elkan1788.mpsdk4j.vo.message.VoiceMsg; +import io.github.elkan1788.mpsdk4j.vo.push.SentAllJobEvent; +import io.github.elkan1788.mpsdk4j.vo.push.SentTmlJobEvent; + +/** + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class WechatKernelTest extends TestSupport { + + private static final Log log = Logs.get(); + + private MPAccount mpAct; + private Map data; + + @Before + public void init() { + log.info("====== WechatKernelTest ======"); + mpAct = new MPAccount(); + mpAct.setMpId(_cr.get("mpId")); + mpAct.setAppId(_cr.get("appId")); + mpAct.setAppSecret(_cr.get("appSecret")); + mpAct.setToken(_cr.get("token")); + data = new HashMap(); + data.put("signature", new String[]{"af06ae6995cb1979e465d3b8015509ad61bb7204"}); + data.put("timestamp", new String[]{"1442726144"}); + data.put("nonce", new String[]{"1439307736"}); + data.put("echostr", new String[]{"1439307541"}); + } + + @Test + public void testNullParamsCheck() { + log.info("====== WechatKernel#check-null ======"); + data.clear(); + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String echo = wk.check(); + assertNotNull(echo); + assertTrue(echo.equals("error")); + } + + @Test + public void testLongParamsCheck() { + log.info("====== WechatKernel#check-toolong ======"); + StringGenerator sg = R.sg(129, 200); + data.put("signature", new String[]{sg.next()}); + data.put("timestamp", new String[]{sg.next()}); + data.put("nonce", new String[]{sg.next()}); + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String echo = wk.check(); + assertNotNull(echo); + assertTrue(echo.equals("error")); + } + + @Test + public void testLong1ParamsCheck() { + log.info("====== WechatKernel#check-toolong1 ======"); + StringGenerator sg = R.sg(129, 200); + data.put("signature", new String[]{sg.next()}); + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String echo = wk.check(); + assertNotNull(echo); + assertTrue(echo.equals("error")); + } + + @Test + public void testLong2ParamsCheck() { + log.info("====== WechatKernel#check-toolong2 ======"); + StringGenerator sg = R.sg(129, 200); + data.put("timestamp", new String[]{sg.next()}); + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String echo = wk.check(); + assertNotNull(echo); + assertTrue(echo.equals("error")); + } + + @Test + public void testLong3ParamsCheck() { + log.info("====== WechatKernel#check-toolong3 ======"); + StringGenerator sg = R.sg(129, 200); + data.put("nonce", new String[]{sg.next()}); + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String echo = wk.check(); + assertNotNull(echo); + assertTrue(echo.equals("error")); + } + + @Test + public void testNull1ParamsCheck() { + log.info("====== WechatKernel#check-null1 ======"); + data.put("signature", null); + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String echo = wk.check(); + assertNotNull(echo); + assertTrue(echo.equals("error")); + } + + @Test + public void testNull2ParamsCheck() { + log.info("====== WechatKernel#check-null2 ======"); + data.put("timestamp", null); + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String echo = wk.check(); + assertNotNull(echo); + assertTrue(echo.equals("error")); + } + + @Test + public void testNull3ParamsCheck() { + log.info("====== WechatKernel#check-null3 ======"); + data.put("nonce", null); + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String echo = wk.check(); + assertNotNull(echo); + assertTrue(echo.equals("error")); + } + + @Test + public void testCheck() { + log.info("====== WechatKernel#check ======"); + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String echo = wk.check(); + assertNotNull(echo); + assertTrue(echo.equals(data.get("echostr")[0])); + } + + @Test + public void testTextHandle() { + log.info("====== WechatKernel#handle-text ======"); + String textxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "6091046778677430\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(textxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testImageHandle() { + log.info("====== WechatKernel#handle-image ======"); + String imgxml = "\n" + + "\n" + + "1418182341\n" + + " \n" + + "\n" + + "\n" + + "6091046778677430\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(imgxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testVoiceHandle() { + log.info("====== WechatKernel#handle-voice ======"); + String voicexml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "1234567890123456\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(voicexml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + log.info("====== WechatKernel#handle-voice-voice ======"); + wk = new WechatKernel(mpAct, new VoiceHandler(), data); + respxml = wk.handle(StreamTool.toStream(voicexml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + log.info("====== WechatKernel#handle-voice-music ======"); + wk = new WechatKernel(mpAct, new MediaHandler(), data); + respxml = wk.handle(StreamTool.toStream(voicexml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testVideoHandle() { + log.info("====== WechatKernel#handle-video ======"); + String videoxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "1234567890123456\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(videoxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + log.info("====== WechatKernel#handle-video-video ======"); + wk = new WechatKernel(mpAct, new MediaHandler(), data); + respxml = wk.handle(StreamTool.toStream(videoxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testShortVideoHandle() { + log.info("====== WechatKernel#handle-shortvideo ======"); + String shortvxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "1234567890123456\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(shortvxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testLocationHandle() { + log.info("====== WechatKernel#handle-location ======"); + String locationxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "23.134521\n" + + "113.358803\n" + + "20\n" + + "\n" + + "1234567890123456\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(locationxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testLinkHandle() { + log.info("====== WechatKernel#handle-location ======"); + String linkxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "<![CDATA[公众平台官网链接]]>\n" + + "\n" + + "\n" + + "1234567890123456\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(linkxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testUnknowHandle() { + log.info("====== WechatKernel#handle-unknow ======"); + String unknowxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "1234567890123456\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(unknowxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testSubEventHandle() { + log.info("====== WechatKernel#handle-subevent ======"); + String subxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(subxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testUnSubEventHandle() { + log.info("====== WechatKernel#handle-subevent ======"); + String unsubxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(unsubxml)); + assertNotNull(respxml); + assertTrue(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testScanEventHandle() { + log.info("====== WechatKernel#handle-scanevent ======"); + String scanxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(); + wk.setMpAct(mpAct); + wk.setWechatHandler(new WechatDefHandler()); + wk.setParams(data); + String respxml = wk.handle(StreamTool.toStream(scanxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testLocationEventHandle() { + log.info("====== WechatKernel#handle-locationevent ======"); + String locationxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "23.137466\n" + + "113.352425\n" + + "119.385040\n" + + ""; + WechatKernel wk = new WechatKernel(); + wk.setMpAct(mpAct); + wk.setWechatHandler(new WechatDefHandler()); + wk.setParams(data); + String respxml = wk.handle(StreamTool.toStream(locationxml)); + assertNotNull(respxml); + assertTrue(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testClickEventHandle() { + log.info("====== WechatKernel#handle-clickevent ======"); + String clickxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(); + wk.setMpAct(mpAct); + wk.setWechatHandler(new WechatDefHandler()); + wk.setParams(data); + String respxml = wk.handle(StreamTool.toStream(clickxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testViewEventHandle() { + log.info("====== WechatKernel#handle-viewevent ======"); + String viewxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(); + wk.setMpAct(mpAct); + wk.setWechatHandler(new WechatDefHandler()); + wk.setParams(data); + String respxml = wk.handle(StreamTool.toStream(viewxml)); + assertNotNull(respxml); + assertTrue(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testScanPushEventHandle() { + log.info("====== WechatKernel#handle-scanpushevent ======"); + String spxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(spxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testScanWaitEventHandle() { + log.info("====== WechatKernel#handle-scanwaitmsgevent ======"); + String swxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(swxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testPicSysEventHandle() { + log.info("====== WechatKernel#handle-picsysevent ======"); + String psxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "1\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(psxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testPicPhotoAlbumEventHandle() { + log.info("====== WechatKernel#handle-picphotoalbumevent ======"); + String ppaxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "1\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(ppaxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testPicWeixinEventHandle() { + log.info("====== WechatKernel#handle-picweixinevent ======"); + String pwxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "1\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(pwxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testSelectLocationEventHandle() { + log.info("====== WechatKernel#handle-selectlocationevent ======"); + String slxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(slxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testSentTemplateFinshJobEventHandle() { + log.info("====== WechatKernel#handle-senttemplateevent ======"); + String tmlfjxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "200163836\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(tmlfjxml)); + assertNotNull(respxml); + assertTrue(respxml.equals("success")); + log.info(respxml); + wk = new WechatKernel(mpAct, new PushEventHandler(), data); + respxml = wk.handle(StreamTool.toStream(tmlfjxml)); + assertNotNull(respxml); + assertTrue(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testSentAllFinshJobEventHandle() { + log.info("====== WechatKernel#handle-sentallevent ======"); + String tmlfjxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "200163836\n" + + "\n" + + "100\n" + + "80\n" + + "75\n" + + "5\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(tmlfjxml)); + assertNotNull(respxml); + assertTrue(respxml.equals("success")); + log.info(respxml); + wk = new WechatKernel(mpAct, new PushEventHandler(), data); + respxml = wk.handle(StreamTool.toStream(tmlfjxml)); + assertNotNull(respxml); + assertTrue(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testNewEventHandle() { + log.info("====== WechatKernel#handle-viewevent ======"); + String newxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + "\n" + + ""; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(newxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test(expected = RuntimeException.class) + public void testErrorXmlHandle() { + log.info("====== WechatKernel#handle-viewevent ======"); + String newxml = "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "/xml>"; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(newxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test(expected = RuntimeException.class) + public void testDecryptMsgFailure() { + log.info("====== WechatKernel#encryptmsg ======"); + data.clear(); + data.put("nonce", new String[]{"xxxxxx"}); + data.put("timestamp", new String[]{"1409304348"}); + data.put("msg_signature", new String[]{"9f8883ff0676a51299747fc2d4b2e6a4be9207cb"}); + data.put("encrypt_type", new String[]{"aes"}); + mpAct.setAESKey("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG"); + mpAct.setAppId("wxb11529c136998cb6"); + mpAct.setToken("pamtest"); + String encryptxml = "1409304348"; + WechatKernel wk = new WechatKernel(mpAct, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(encryptxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + @Test + public void testDecryptMsg() { + log.info("====== WechatKernel#encryptmsg ======"); + data.clear(); + data.put("nonce", new String[]{"xxxxxx"}); + data.put("timestamp", new String[]{"1409304348"}); + data.put("msg_signature", new String[]{"92aa0aeee3943305c0127627dad968bd73e32428"}); + data.put("encrypt_type", new String[]{"aes"}); + MPAccount mpact = new MPAccount(); + mpact.setAESKey("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG"); + mpact.setAppId("wxb11529c136998cb6"); + mpact.setToken("pamtest"); + String encryptxml = "1409304348"; + WechatKernel wk = new WechatKernel(mpact, new WechatDefHandler(), data); + String respxml = wk.handle(StreamTool.toStream(encryptxml)); + assertNotNull(respxml); + assertFalse(respxml.equals("success")); + log.info(respxml); + } + + class VoiceHandler extends WechatDefHandler { + + @Override + public BasicMsg voice(VoiceMsg vm) { + VoiceMsg vim = new VoiceMsg(vm); + vim.setMediaId(vm.getMediaId()); + return vim; + } + + } + + class MediaHandler extends WechatDefHandler { + + @Override + public BasicMsg voice(VoiceMsg vm) { + MusicMsg mm = new MusicMsg(vm); + mm.setTitle("致爱 Your Song"); + mm.setThumbMediaId(vm.getMediaId()); + mm.setMusicUrl("http://y.qq.com/#type=song&mid=002IVyIU4093Xr&play=0"); + mm.setHQMusicUrl("http://y.qq.com/#type=song&mid=002IVyIU4093Xr&play=0"); + return mm; + } + + @Override + public BasicMsg video(VideoMsg vm) { + VideoMsg vdm = new VideoMsg(vm); + vdm.setTitle("习主席联合国峰会演讲"); + vdm.setDescription("习主席联合国峰会演讲"); + vdm.setMediaId("Hfdjlioieijl#22iojkljlkjlutfd"); + return super.video(vm); + } + + } + + class PushEventHandler extends WechatDefHandler { + + @Override + public void eSentTmplJobFinish(SentTmlJobEvent stje) { + log.info(stje); + } + + @Override + public void eSentAllJobFinish(SentAllJobEvent saje) { + log.info(saje); + } + + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/core/XmlMsgBuilderTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/core/XmlMsgBuilderTest.java new file mode 100644 index 0000000..3f2e9fe --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/core/XmlMsgBuilderTest.java @@ -0,0 +1,68 @@ +package io.github.elkan1788.mpsdk4j.core; + +import org.junit.Before; +import org.junit.Test; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +import io.github.elkan1788.mpsdk4j.TestSupport; +import io.github.elkan1788.mpsdk4j.vo.message.ImageMsg; +import io.github.elkan1788.mpsdk4j.vo.message.TextMsg; +import io.github.elkan1788.mpsdk4j.vo.message.VoiceMsg; + +/** + * XmlMsgBuilder 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class XmlMsgBuilderTest extends TestSupport { + + private static final Log log = Logs.get(); + + private String mpId; + private String openId; + private String mediaId; + + @Before + public void init() { + log.info("====== XmlMsgBuilderTest ======"); + mpId = _cr.get("mpId"); + openId = _cr.get("openId"); + mediaId = _cr.get("mediaId"); + } + + @Test + public void testText() { + log.info("====== XmlMsgBuilder#text ======"); + TextMsg tm = new TextMsg(); + tm.setFromUserName(mpId); + tm.setToUserName(openId); + tm.setContent("Hello world! 世界, 你好!"); + String txtXml = XmlMsgBuilder.create().text(tm).build(); + log.info(txtXml); + } + + @Test + public void testImage() { + log.info("====== XmlMsgBuilder#image ======"); + ImageMsg im = new ImageMsg(); + im.setFromUserName(mpId); + im.setToUserName(openId); + im.setMediaId(mediaId); + String imgXml = XmlMsgBuilder.create().image(im).build(); + log.info(imgXml); + } + + @Test + public void testVoice() { + log.info("====== XmlMsgBuilder#voice ======"); + VoiceMsg vm = new VoiceMsg(); + vm.setFromUserName(mpId); + vm.setToUserName(openId); + vm.setMediaId(mediaId); + String voiceXml = XmlMsgBuilder.create().voice(vm).build(); + log.info(voiceXml); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/mvc/HttpServletSupportTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/HttpServletSupportTest.java new file mode 100644 index 0000000..8493787 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/HttpServletSupportTest.java @@ -0,0 +1,79 @@ +package io.github.elkan1788.mpsdk4j.mvc; + +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.ServletException; +import javax.servlet.ServletInputStream; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.nutz.log.Log; +import org.nutz.log.Logs; +import org.xml.sax.SAXException; + +import io.github.elkan1788.mpsdk4j.util.StreamTool; +import mockit.Expectations; +import mockit.integration.junit4.JMockit; + +/** + * HttpServletSupport 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@RunWith(JMockit.class) +public class HttpServletSupportTest extends WebContainerMockit { + + private static final Log log = Logs.get(); + + @Override + @Before + public void init() { + log.info("====== HttpServletSupportTest ======"); + super.init(); + } + + @Test + public void testGetCheck() throws IOException, SAXException, ServletException { + log.info("====== HttpServletSupportTest-get ======"); + + ServletContainer sc = new ServletContainer(); + sc.doGet(req, resp); + } + + @Test + public void testInteract() throws IOException, SAXException, ServletException { + log.info("====== HttpServletSupportTest-post ======"); + new Expectations() { + { + // 模拟微信互动数据 + req.getInputStream(); + ServletInputStream sis = new ServletInputStream() { + + String textxml = "" + + "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "6091046778677430\n" + + ""; + + InputStream is = StreamTool.toStream(textxml); + + @Override + public int read() throws IOException { + return is.read(); + } + }; + returns(sis); + } + }; + + ServletContainer sc = new ServletContainer(); + sc.doPost(req, resp); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/mvc/MVCTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/MVCTest.java new file mode 100644 index 0000000..26b87a6 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/MVCTest.java @@ -0,0 +1,19 @@ +package io.github.elkan1788.mpsdk4j.mvc; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * mvc 包测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + HttpServletSupportTest.class, + WechatWebSupportTest.class +}) +public class MVCTest { + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/mvc/ServletContainer.java b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/ServletContainer.java new file mode 100644 index 0000000..aff1a63 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/ServletContainer.java @@ -0,0 +1,53 @@ +package io.github.elkan1788.mpsdk4j.mvc; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.github.elkan1788.mpsdk4j.core.WechatDefHandler; +import io.github.elkan1788.mpsdk4j.util.ConfigReader; +import io.github.elkan1788.mpsdk4j.vo.MPAccount; + +/** + * Servlet容器 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class ServletContainer extends HttpServletSupport { + + private static final long serialVersionUID = 8954466678965988236L; + + private static ConfigReader _cr; + + static { + _cr = new ConfigReader("/mpconf.properties"); + } + + @Override + public void init() throws ServletException { + MPAccount mpAct = new MPAccount(); + mpAct.setMpId(_cr.get("mpId")); + mpAct.setAppId(_cr.get("appId")); + mpAct.setAppSecret(_cr.get("appSecret")); + mpAct.setToken(_cr.get("token")); + _wk.setMpAct(mpAct); + _wk.setWechatHandler(new WechatDefHandler()); + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + init(); + super.doGet(req, resp); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + init(); + super.doPost(req, resp); + } +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/mvc/WebContainerMockit.java b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/WebContainerMockit.java new file mode 100644 index 0000000..8d73fed --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/WebContainerMockit.java @@ -0,0 +1,44 @@ +package io.github.elkan1788.mpsdk4j.mvc; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; + +import mockit.Expectations; +import mockit.Mocked; + +/** + * WEB容器模拟 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class WebContainerMockit { + + @Mocked + protected HttpServletRequest req; + + @Mocked + protected HttpServletResponse resp; + + @Before + public void init() { + new Expectations() { + { + // 模拟服务器检验参数 + req.getParameterMap(); + Map data = new HashMap(); + data.put("signature", new String[]{"8f89b331558f93dd1931f0c01a1dc121e1ef744a"}); + data.put("timestamp", new String[]{"1435267"}); + data.put("nonce", new String[]{"ZnFI4vNoiAqbmPyQ-bkOktOt9x"}); + data.put("echostr", new String[]{"123456"}); + returns(data); + + } + }; + } +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/mvc/WebSupportContainer.java b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/WebSupportContainer.java new file mode 100644 index 0000000..8ef267d --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/WebSupportContainer.java @@ -0,0 +1,40 @@ +package io.github.elkan1788.mpsdk4j.mvc; + +import java.io.IOException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import io.github.elkan1788.mpsdk4j.core.WechatDefHandler; +import io.github.elkan1788.mpsdk4j.util.ConfigReader; +import io.github.elkan1788.mpsdk4j.vo.MPAccount; + +/** + * WEB 容器 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class WebSupportContainer extends WechatWebSupport { + + private static ConfigReader _cr; + + static { + _cr = new ConfigReader("/mpconf.properties"); + } + + @Override + public void init() { + MPAccount mpAct = new MPAccount(); + mpAct.setMpId(_cr.get("mpId")); + mpAct.setAppId(_cr.get("appId")); + mpAct.setAppSecret(_cr.get("appSecret")); + mpAct.setToken(_cr.get("token")); + _wk.setMpAct(mpAct); + _wk.setWechatHandler(new WechatDefHandler()); + } + + public void wechat(HttpServletRequest req, HttpServletResponse resp) throws IOException { + this.interact(req, resp); + } +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/mvc/WechatWebSupportTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/WechatWebSupportTest.java new file mode 100644 index 0000000..332e271 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/mvc/WechatWebSupportTest.java @@ -0,0 +1,88 @@ +package io.github.elkan1788.mpsdk4j.mvc; + +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.ServletInputStream; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +import io.github.elkan1788.mpsdk4j.util.StreamTool; +import mockit.Expectations; +import mockit.integration.junit4.JMockit; + +/** + * WechatWebSupport 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@RunWith(JMockit.class) +public class WechatWebSupportTest extends WebContainerMockit { + + private static final Log log = Logs.get(); + + @Override + @Before + public void init() { + log.info("====== WechatWebSupportTest ======"); + super.init(); + } + + @Test + public void testCheck() throws IOException { + + log.info("====== WechatWebSupportTest-get ======"); + new Expectations() { + { + req.getMethod(); + returns("GET"); + } + }; + + WebSupportContainer wsct = new WebSupportContainer(); + wsct.wechat(req, resp); + } + + @Test + public void testInteract() throws IOException { + + log.info("====== WechatWebSupportTest-post ======"); + new Expectations() { + { + // 模拟微信互动数据 + req.getInputStream(); + ServletInputStream sis = new ServletInputStream() { + + String textxml = "" + + "\n" + + "\n" + + "1418182341\n" + + "\n" + + "\n" + + "6091046778677430\n" + + ""; + + InputStream is = StreamTool.toStream(textxml); + + @Override + public int read() throws IOException { + return is.read(); + } + }; + returns(sis); + + req.getMethod(); + returns("POST"); + } + }; + + WebSupportContainer wsct = new WebSupportContainer(); + wsct.wechat(req, resp); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/AESTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/AESTest.java new file mode 100644 index 0000000..ddf5273 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/AESTest.java @@ -0,0 +1,18 @@ +package io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * AES包测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + WXBizMsgCryptTest.class +}) +public class AESTest { + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/WXBizMsgCryptTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/WXBizMsgCryptTest.java new file mode 100644 index 0000000..1c0f6c9 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/repo/com/qq/weixin/mp/aes/WXBizMsgCryptTest.java @@ -0,0 +1,74 @@ +package io.github.elkan1788.mpsdk4j.repo.com.qq.weixin.mp.aes; + +import static org.junit.Assert.assertNotNull; +import io.github.elkan1788.mpsdk4j.util.StreamTool; + +import org.junit.Before; +import org.junit.Test; +import org.nutz.lang.Lang; +import org.nutz.log.Log; +import org.nutz.log.Logs; + +/** + * WXBizMsgCrypt 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class WXBizMsgCryptTest { + + private static final Log log = Logs.get(); + + private String encodingAesKey = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG"; + private String token = "pamtest"; + private String timeStamp = "1409304348"; + private String nonce = "xxxxxx"; + private String appId = "wxb11529c136998cb6"; + private String replyMsg = "1407743423"; + private String fromMsg = "1409304348"; + private String msgSignature = "9f8883ff0676a51299747fc2d4b2e6a4be9207cb"; + + private WXBizMsgCrypt pc; + + @Before + public void init() { + log.info("====== WXBizMsgCryptTest ======"); + try { + pc = new WXBizMsgCrypt(token, encodingAesKey, appId); + } + catch (AesException e) { + throw Lang.wrapThrow(e); + } + } + + @Test + public void testEncryptMsg() { + log.info("====== WXBizMsgCrypt#encryptMsg ======"); + try { + String encryptmsg = pc.encryptMsg(replyMsg, timeStamp, nonce); + assertNotNull(encryptmsg); + log.info(encryptmsg.replaceAll("\\n", "")); + } + catch (AesException e) { + throw Lang.wrapThrow(e); + } + } + + @Test + public void testDecryptMsg() { + log.info("====== WXBizMsgCrypt#decryptMsg ======"); + try { + String decryptmsg = pc.decryptMsg(msgSignature, + timeStamp, + nonce, + StreamTool.toStream(fromMsg)); + assertNotNull(decryptmsg); + log.info(decryptmsg); + log.info(pc.getFromAppid()); + } + catch (AesException e) { + throw Lang.wrapThrow(e); + } + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/util/ConfigReaderTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/util/ConfigReaderTest.java new file mode 100644 index 0000000..38a26ae --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/util/ConfigReaderTest.java @@ -0,0 +1,80 @@ +package io.github.elkan1788.mpsdk4j.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Collection; +import java.util.List; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * ConfigReader Testing + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class ConfigReaderTest { + + private static final int INTEGER = 1; + private static final boolean BOOL = true; + private static final long LONG = 100000000000000l; + + private ConfigReader cr; + + @Before + public void init() { + cr = new ConfigReader("/ErrorCode.properties"); + cr.put("int", String.valueOf(INTEGER)); + cr.put("bool", String.valueOf(BOOL)); + cr.put("long", String.valueOf(LONG)); + } + + @After + public void testClear() { + cr.clear(); + assertNull(cr.get("0")); + } + + @Test + public void testAll() { + List keys = cr.keys(); + Collection values = cr.values(); + assertEquals(keys.size(), values.size()); + } + + @Test + public void testGet() { + assertNotNull(cr.get("0")); + } + + @Test(expected = NullPointerException.class) + public void testNullFile() { + new ConfigReader("/test.properties"); + } + + @Test(expected = NullPointerException.class) + public void testNullKey() { + cr.get(null); + } + + @Test + public void testGetInt() { + assertEquals(cr.getInt("int"), INTEGER); + } + + @Test + public void testGetLong() { + assertEquals(cr.getLong("long"), LONG); + } + + @Test + public void testGetBoolean() { + assertTrue(cr.getBoolean("bool")); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/util/HttpToolTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/util/HttpToolTest.java new file mode 100644 index 0000000..205c459 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/util/HttpToolTest.java @@ -0,0 +1,111 @@ +package io.github.elkan1788.mpsdk4j.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.nutz.json.Json; +import org.nutz.lang.Files; +import org.nutz.lang.Lang; +import org.nutz.lang.Strings; + +import io.github.elkan1788.mpsdk4j.TestSupport; +import io.github.elkan1788.mpsdk4j.vo.ApiResult; + +/** + * HttpTool 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@SuppressWarnings("unchecked") +public class HttpToolTest extends TestSupport { + + private String openId; + private String appId; + private String appSecret; + private String accessToken; + private String mediaId; + + @Before + public void init() { + this.openId = _cr.get("openId"); + this.appId = _cr.get("appId"); + this.appSecret = _cr.get("appSecret"); + this.accessToken = _cr.get("accessToken"); + this.mediaId = _cr.get("mediaId"); + } + + // 注意access_token接口调用次数,建议跑一次就关闭 + @Ignore + // @Before + public void testGet() { + if (Strings.isBlank(accessToken)) { + String url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", + appId, + appSecret); + String content = HttpTool.get(url); + assertNotNull(content); + Map data = (Map) Json.fromJson(content); + accessToken = data.get("access_token"); + assertNotNull(data.get("expires_in")); + } + assertNotNull(accessToken); + } + + @Test + public void testPost() { + // 测试前请发先消息给公众号 + String url = String.format("https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s", + accessToken); + String body = "{\"touser\":\"" + + openId + + "\",\"msgtype\":\"text\",\"text\":{\"content\":\"Hello World!\r\n世界,你好!\"}}"; + String content = HttpTool.post(url, body); + assertNotNull(content); + assertEquals("{\"errcode\":0,\"errmsg\":\"ok\"}", content); + + } + + @Test + public void testUpload() { + String url = String.format("https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s", + accessToken, + "image"); + File file = new File(this.getClass().getResource("/mpsdk4j-logo.png").getFile()); + String content = HttpTool.upload(url, file); + assertNotNull(content); + Map nm = (Map) Json.fromJson(content); + assertNotNull(nm.get("created_at")); + mediaId = nm.get("media_id"); + System.out.println(mediaId); + } + + // @Ignore + @Test + public void testDownload() { + String url = String.format("https://api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s", + accessToken, + mediaId); + Object tmp = HttpTool.download(url); + try { + if (tmp instanceof File) { + Files.copyFile((File) tmp, new File("D:/tmp/mpsdk4j-logo.png")); + } + else { + ApiResult ar = ApiResult.create((String) tmp); + System.out.println(ar); + } + } + catch (IOException e) { + throw Lang.wrapThrow(e); + } + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/util/UtilTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/util/UtilTest.java new file mode 100644 index 0000000..a1b1c86 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/util/UtilTest.java @@ -0,0 +1,17 @@ +package io.github.elkan1788.mpsdk4j.util; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * Util 包测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ConfigReaderTest.class, + HttpToolTest.class +}) +public class UtilTest {} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/vo/ApiResultTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/vo/ApiResultTest.java new file mode 100644 index 0000000..37c309d --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/vo/ApiResultTest.java @@ -0,0 +1,83 @@ +package io.github.elkan1788.mpsdk4j.vo; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * ApiResult 测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +public class ApiResultTest { + + private String sucJson = "{'errcode':0,'errmsg':'Success.'}"; + private String unKnowErrJson = "{'errcode':9999,'errmsg':'Unkonw error.'}"; + private String errJson = "{'errcode':-1,'errmsg':'System busy.'}"; + private String atErrJson1 = "{'errcode':40001,'errmsg':'invalid appsecret'}"; + private String atErrJson2 = "{'errcode':42001,'errmsg':'access_token over time'}"; + private String atErrJson3 = "{'errcode':42002,'errmsg':'refresh token over time'}"; + private String atErrJson4 = "{'errcode':40014,'errmsg':'invalid access_token'}"; + + @Test + public void testGetJson() { + ApiResult ar = ApiResult.create(sucJson); + assertEquals(sucJson, ar.getJson()); + } + + @Test + public void testGetErrCode() { + ApiResult ar = ApiResult.create(errJson); + assertEquals(-1, ar.getErrCode().intValue()); + } + + @Test + public void testGetErrMsg() { + ApiResult ar = ApiResult.create(errJson); + assertEquals("System busy.", ar.getErrMsg()); + } + + @Test + public void testGetErrCNMsg() { + ApiResult ar = ApiResult.create(sucJson); + assertEquals("请求成功.", ar.getErrCNMsg()); + } + + @Test + public void testIsSuccess() { + ApiResult ar = ApiResult.create(errJson); + assertTrue(!ar.isSuccess()); + } + + @Test + public void testIsAccessTokenInvalid() { + ApiResult ar = ApiResult.create(atErrJson1); + assertFalse(ar.isSuccess()); + ar = ApiResult.create(atErrJson2); + assertFalse(ar.isSuccess()); + ar = ApiResult.create(atErrJson3); + assertFalse(ar.isSuccess()); + ar = ApiResult.create(atErrJson4); + assertFalse(ar.isSuccess()); + } + + @Test + public void testUnknowError() { + ApiResult ar = ApiResult.create(unKnowErrJson); + assertEquals(unKnowErrJson, ar.getJson()); + assertEquals("未知错误!", ar.getErrCNMsg()); + } + + @Test + public void testSucWithoutErrCode() { + String json = "{'access_token':'ACCESS_TOKEN','expires_in':7200}"; + ApiResult ar = ApiResult.create(json); + assertEquals(json, ar.getJson()); + assertEquals("Unknow Error!", ar.getErrMsg()); + assertTrue(ar.isSuccess()); + } + +} diff --git a/src/test/java/io/github/elkan1788/mpsdk4j/vo/VOTest.java b/src/test/java/io/github/elkan1788/mpsdk4j/vo/VOTest.java new file mode 100644 index 0000000..0c05639 --- /dev/null +++ b/src/test/java/io/github/elkan1788/mpsdk4j/vo/VOTest.java @@ -0,0 +1,18 @@ +package io.github.elkan1788.mpsdk4j.vo; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * VO 包测试 + * + * @author 凡梦星尘(elkan1788@gmail.com) + * @since 2.0 + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + ApiResultTest.class +}) +public class VOTest { + +} diff --git a/src/test/java/org/elkan1788/osc/testunit/TestSupport.java b/src/test/java/org/elkan1788/osc/testunit/TestSupport.java deleted file mode 100644 index 39481b7..0000000 --- a/src/test/java/org/elkan1788/osc/testunit/TestSupport.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.elkan1788.osc.testunit; - -import org.elkan1788.osc.weixin.mp.vo.MPAct; -import org.junit.Before; - -import java.util.Properties; - -/** - * 测试父类 - * - * @author 凡梦星尘(elkan1788@gmail.com) - * @since 2014/11/12 - * @version 1.0.0 - */ -public class TestSupport { - - protected String mpId; - protected String appId; - protected String appSecret; - protected String token; - protected String openId; - protected String aesKey; - - protected String templateId; - protected String mediaId; - protected String accessToken; - - protected String msgSing; - protected String timestamp; - protected String echostr; - protected String nonce; - - protected MPAct mpAct; - - @Before - public void init() throws Exception { - Properties p = new Properties(); - p.load(getClass().getResourceAsStream("/test-cfg.properties")); - - mpId = p.getProperty("mpId"); - appId = p.getProperty("appId"); - appSecret = p.getProperty("appSecret"); - token = p.getProperty("token"); - openId = p.getProperty("openId"); - aesKey = p.getProperty("aesKey"); - - templateId = p.getProperty("templateId"); - mediaId = p.getProperty("mediaId"); - accessToken = p.getProperty("accessToken", "NOT"); - - msgSing = p.getProperty("msgSing"); - timestamp = p.getProperty("timestamp"); - echostr = p.getProperty("echostr"); - nonce = p.getProperty("nonce"); - - mpAct = new MPAct(); - mpAct.setMpId(mpId); - mpAct.setAppId(appId); - mpAct.setAppSecret(appSecret); - mpAct.setToken(token); - mpAct.setAESKey(aesKey); - if (!accessToken.equals("NOT")||!accessToken.isEmpty()) { - mpAct.setAccessToken(accessToken); - mpAct.setExpiresIn(7000 * 1000 + System.currentTimeMillis()); - } - } -} diff --git a/src/test/java/org/elkan1788/osc/weixin/mp/core/WxApiImplTest.java b/src/test/java/org/elkan1788/osc/weixin/mp/core/WxApiImplTest.java deleted file mode 100644 index f58d272..0000000 --- a/src/test/java/org/elkan1788/osc/weixin/mp/core/WxApiImplTest.java +++ /dev/null @@ -1,231 +0,0 @@ -package org.elkan1788.osc.weixin.mp.core; - -import org.elkan1788.osc.testunit.TestSupport; -import org.elkan1788.osc.weixin.mp.util.SimpleHttpReq; -import org.elkan1788.osc.weixin.mp.vo.*; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.util.Arrays; -import java.util.List; - -/** - * 微信API接口测试 - * - * @author 凡梦星尘(elkan1788@gmail.com) - * @since 2014/11/12 - */ -public class WxApiImplTest extends TestSupport { - - private WxApi wxApi; - - @Before - public void init() throws Exception { - super.init(); - - wxApi = new WxApiImpl(mpAct); - if ("NOT".equals(accessToken) - || "".equals(accessToken) - || accessToken.isEmpty()) { - mpAct.setToken(wxApi.getAccessToken()); - mpAct.setExpiresIn(7000 * 1000 + System.currentTimeMillis()); - } - System.out.println("ACCESS_TOKEN: " + mpAct.getAccessToken()); - } - - @Test - public void testGetAccessToken() throws Exception { - String token = wxApi.getAccessToken(); - System.out.println(token); - } - - @Test - public void testGetServerIp() throws Exception { - List ips = wxApi.getServerIp(); - for (String ip : ips) { - System.out.println(ip); - } - } - - @Test - public void testGetMenu() throws Exception { - List menus = wxApi.getMenu(); - System.out.println("拉取到的菜单: " + menus); - } - - @Test - public void testCreateMenu() throws Exception { - // 点击或视图菜单 - Menu m1 = new Menu("基本"); - Menu csdn = new Menu("CSDN", Menu.VIEW, "http://www.csdn.net"); - Menu osc = new Menu("OSC", Menu.VIEW, "http://www.oschina.net"); - Menu click = new Menu("点击我", Menu.CLICK, "Thanks you click me!"); - m1.setSubButtons(Arrays.asList(csdn, osc, click)); - // 发送相片或位置 - Menu m2 = new Menu("发送"); - Menu sysphoto = new Menu("拍照", Menu.PIC_SYSPHOTO, "pic_sysphoto"); - Menu album = new Menu("拍照或相册", Menu.PIC_PHOTO_OR_ALBUM, "pic_photo_or_album"); - Menu weixin = new Menu("微信相册", Menu.PIC_WEIXIN, "pic_weixin"); - Menu location = new Menu("位置", Menu.LOCATION_SELECT, "location_select"); - m2.setSubButtons(Arrays.asList(sysphoto, album, weixin, location)); - // 扫码菜单 - Menu m3 = new Menu("扫码"); - Menu push = new Menu("推送", Menu.SCANCODE_PUSH, "scancode_push"); - Menu wait = new Menu("等待", Menu.SCANCODE_WAITMSG, "scancode_waitmsg"); - m3.setSubButtons(Arrays.asList(push, wait)); - - boolean flag = wxApi.createMenu(m1, m2, m3); - System.out.println("创建菜单: " + flag); - } - - @Test - public void testDeleteMenu() throws Exception { - boolean flag = wxApi.deleteMenu(); - System.out.println("删除菜单: " + flag); - } - - @Test - public void testCreatGroup() throws Exception { - int groupId = wxApi.creatGroup("公司员工"); - System.out.println("创建分组: " + groupId); - } - - @Test - public void testGetGroups() throws Exception { - List groups = wxApi.getGroups(); - System.out.println("拉取到的分组: " + groups); - } - - @Test - public void testRenGroup() throws Exception { - boolean flag = wxApi.renGroup(104, "内部测试人员"); - System.out.println("修改分组: " + flag); - } - - @Test - public void testGetGroupId() throws Exception { - int groupId = wxApi.getGroupId(openId); - System.out.println("拉取到用户分组: " + groupId); - } - - @Test - public void testMove2Group() throws Exception { - boolean flag = wxApi.move2Group("oMIXfjjEB8ifmUpjauItMXpGAik0", 104); - System.out.println("移动用户分组: " + flag); - } - - @Test - public void testGetFollowerList() throws Exception { - FollowList follows = wxApi.getFollowerList(""); - System.out.println("拉取用户关注列表: " + follows); - } - - @Test - public void testGetFollower() throws Exception { - Follower follower = wxApi.getFollower(openId, "zh_CN"); - System.out.println("拉取用户的信息: " + follower); - } - - @Test - public void testSendCustomerMsg() throws Exception { - // 文本客服消息 - OutPutMsg msg = new OutPutMsg(); - msg.setToUserName(openId); - msg.setMsgType(WxApi.TEXT); - msg.setContent("感谢你持续关注我们的公众号[微笑]."); - boolean flag = wxApi.sendCustomerMsg(msg); - System.out.println("发送文本客服消息: " + flag); - // 图像客服消息 - /*msg.setMsgType(WxApi.IMAGE); - msg.setMediaId(mediaId); - flag = wxApi.sendCustomerMsg(msg); - System.out.println("发送图像客服消息: " + flag);*/ - } - - @Test - public void testSendTemplateMsg() throws Exception { - Template first = new Template("first", "#fffff", "faithzhang回复了您的评论"); - Template keyword = new Template("keyword", "#cccccc", "请问腾讯的妈妈们都几点下班?"); - Template time = new Template("time", "#000000", "2014/11/11 11:11:11"); - boolean flag = wxApi.sendTemplateMsg(openId, templateId, "#FF0000", "", first, keyword, time); - System.out.println("发送模板消息: " + flag); - } - - @Test - public void testUpMedia() throws Exception { - File image = new File("D:/upload.jpg"); - String mediaId = wxApi.upMedia("image", image); - System.out.println("上传多媒体文件: " + mediaId); - } - - @Test - public void testDlMedia() throws Exception { - File image = new File("D:/dl/" + mediaId + ".jpg"); - wxApi.dlMedia(mediaId, image); - } - - @Test - public void testUpNews() throws Exception { - File image1 = new File("D:/art1.jpg"); - String mediaId1 = wxApi.upMedia(WxApi.IMAGE, image1); - File image2 = new File("D:/art2.jpg"); - String mediaId2 = wxApi.upMedia(WxApi.IMAGE, image2); - Article2 art1 = new Article2(); - art1.setTitle("不差钱!玩酷4G上线送话费啦!"); - art1.setDigest("不差钱!玩酷4G上线送话费啦!!"); - art1.setAuthor("凡梦星尘"); - art1.setContent("


    就是那么有钱!就是那么任性!


    这个季节,谁最风靡,是的,是我们玩酷4G!

    这个年龄段,谁能带领互联网潮流,是的,是我们玩酷4G!

    玩酷4G新上线啦,这里有新鲜、时尚的IT资讯,新潮、免费的产品体验,我们的福利多多!就是那么有钱,就是那么任性,就是那么时尚时尚最时尚!


    玩酷4G公共账号上线第一波活动!咱们年轻人!不差钱!

    活动一关注玩酷4G公共账号,就有机会获赠电信充值卡

    1、小伙伴们只要关注玩酷4G,就自动拥有抽奖资格了。

    2、等着好运降临!

    3、奖品:11888话费充值卡。每期活动有:100元2名50元4名10元40名! 11888话费卡可以充值话费、宽带费、翼支付,绝对的生活好帮手!

    4、活动从11月21日起正式开启!每周三公布获奖的小伙伴名单!共持续四期!

    5、获奖查询可以点击“中奖名单”,小伙伴们记得保持关注哦!


    活动二关注玩酷4G公共账号,就有机会免费体验最新的互联网单品!华为荣耀立方智能路由器Bong手环天翼手机交通卡,一网打尽!

    1、关注玩酷4G账号,在“玩酷爆点”中点击你喜欢的互联网单品按要求报名参加体验活动。

    2、首批互联网单品有

    超越小米手环的手环Bong!11月21日起,每周四公布获得Bong的小伙伴,连续三期,每期1名。

    让生活无比简单的翼支付手机交通卡!没有名额限制。只要你报名,就让你体验!11月29日将公布全部体验成功的小伙伴名单。

    新时代的智能路由器华为荣耀立方!价值800!畅想精彩互联家庭生活!12月6日公布,只有一台,不要错过哦!



    "); - art1.setMediaId(mediaId1); - art1.setShowCover(1); - art1.setSourceUrl("http://mp.weixin.qq.com/s?__biz=MzA3MTUyMTMyNg==&mid=201733564&idx=1&sn=ad92ead6499c322b4273cfb39708d40a&key=475e040e205b91d666e07d715eedc09b47d3c4631448d3cf4c229fec511f83e55cebe78da209f0aa51fcd1d8a82302e2&ascene=1&uin=MTU1ODU2MzgwMg%3D%3D&devicetype=webwx&version=70000001&pass_ticket=JG3EHTQ5Idcfm%2Bqt%2BhbFaY8U%2BKx3wCm%2FS6JZomsTeS10EjFBAsYWqTWOctZiAl5C"); - - Article2 art2 = new Article2(); - art2.setTitle("【实用攻略】火车票预售期将延长至60天,买票攻略get!火速收藏~ "); - art2.setDigest("【实用攻略】火车票预售期将延长至60天,买票攻略get!火速收藏~ "); - art2.setAuthor("凡梦星尘"); - art2.setContent("自2014年12月1日起,中国铁路总公司将用一周时间,逐步将铁路互联网售票、电话订票的预售期,由目前的20天延长至60天。具体实施方法为:"); - art2.setMediaId(mediaId2); - art2.setShowCover(1); - art2.setSourceUrl("http://mp.weixin.qq.com/s?__biz=MzA4OTEwNDkyMw==&mid=201489837&idx=2&sn=a86b67dd34894ee1f124b2ddcd02c35b#rd"); - - String[] results = wxApi.upNews(art1, art2); - System.out.println("msgtype:"+results[0]); - System.out.println("mediaid:"+results[1]);//q2t8zU4BGSH58YsBkL_WCdjdvVDZG1oyxe3NRgioLQp0gGjolztVjP6B4mugLfhE - System.out.println("createat:"+results[2]); - - } - - @Test - public void testUpVideo() throws Exception { - File mp4 = new File("D:/kajiaren_clip.mp4"); - String mediaId = "KIV7QppP1LEwsXcUiSHx5BZEV4sIW53a_iX7uHWtU8QyG4maRvPiodiR_6t4JkX3"; //wxApi.upMedia(WxApi.VIDEO, mp4); - System.out.println(mediaId);//KIV7QppP1LEwsXcUiSHx5BZEV4sIW53a_iX7uHWtU8QyG4maRvPiodiR_6t4JkX3 - String[] results = wxApi.upVideo(mediaId,"歌曲《客家人系有料》","歌曲《客家人系有料》"); - System.out.println("msgtype:"+results[0]); - System.out.println("mediaid:"+results[1]);//-h6ETcIv_vOeS_X3Wjf2VlSuwWiPfZSJBelgKlNffqDB8pgg7QWWPG986vOCmG3G - System.out.println("createat:"+results[2]); - } - - @Test - public void testSendAll() throws Exception { - OutPutMsg outmsg = new OutPutMsg(); - outmsg.setToUsers(Arrays.asList(openId)); - outmsg.setMsgType(WxApi.MPNEWS); - outmsg.setMediaId("-ClbTNHmkAYlBmuY0lK-_GMipA7rDQvlekihc8u6oCr3AcQkemGcI1Nxj8Xtma_9"); - String msgid = wxApi.sendAll(outmsg); - System.out.println("news msgid:"+msgid);//2348282442 - - /* outmsg.setMediaId("-h6ETcIv_vOeS_X3Wjf2VlSuwWiPfZSJBelgKlNffqDB8pgg7QWWPG986vOCmG3G"); - outmsg.setMsgType(WxApi.MPVIDEO); - outmsg.setGroupId("104"); - String msgid = wxApi.sendAll(outmsg); - System.out.println("video msgid:"+msgid);*///2348282861 - } - - @Test - public void testDelSendAll() throws Exception { - boolean flag = wxApi.delSendAll("2348282861"); - System.out.println("delsendall:"+flag); - } -} \ No newline at end of file diff --git a/src/test/java/org/elkan1788/osc/weixin/mp/core/WxBaseTest.java b/src/test/java/org/elkan1788/osc/weixin/mp/core/WxBaseTest.java deleted file mode 100644 index 674634b..0000000 --- a/src/test/java/org/elkan1788/osc/weixin/mp/core/WxBaseTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.elkan1788.osc.weixin.mp.core; - -import org.elkan1788.osc.testunit.TestSupport; -import org.elkan1788.osc.weixin.mp.util.StreamTool; -import org.elkan1788.osc.weixin.mp.util.XmlMsgBuilder; -import org.junit.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.core.Is.is; - - -/** - * 微信公众平台普通消息互动测试 - * - * @author 凡梦星尘(elkan1788@gmail.com) - * @version 1.0.3 - * @since 2015/1/11 - */ -public class WxBaseTest extends TestSupport { - - private WxBase wxBase = new WxBase(); - private WxHandler handler = new WxDefaultHandler(); - - @Override - public void init() throws Exception { - super.init(); - this.wxBase.setMpAct(mpAct); - this.wxBase.setWxHandler(handler); - this.wxBase.setMsgSignature(msgSing); - this.wxBase.setTimeStamp(timestamp); - this.wxBase.setNonce(nonce); - this.wxBase.setMpAct(mpAct); - this.wxBase.setAesEncrypt(true); - } - - - private String textXml = "\n" + - "\n" + - "1418182341\n" + - "\n" + - "\n" + - "6091046778677430\n" + - ""; - - private String enrcpXml = "\n" + - " \n" + - " \n" + - ""; - - private String pushXml = "\n" + - "" + this.mpId + "\n" + - "1413192605\n" + - "component_verify_ticket\n" + - "\n" + - ""; - - /** - * 测试微信验证URL方法 - */ - @Test - public void testCheck() throws Exception { - String echostr = this.wxBase.check(); - assertThat(echostr, is(this.wxBase.getEchostr())); - } - - /** - * 测试微信普通互动与事件消息 - */ - @Test - public void testHandler() throws Exception { - if (this.wxBase.isAesEncrypt()) { - // 加密消息 - this.wxBase.setWxInMsg(StreamTool.toStream(this.enrcpXml)); - } else { - this.wxBase.setWxInMsg(StreamTool.toStream(this.textXml)); - } - String reply_msg = this.wxBase.handler(); - String xml_msg = XmlMsgBuilder.create(). - text(this.wxBase.getOutPutMsg()).build(); - assertThat(reply_msg, is(xml_msg)); - } - - /** - * 测试微信开放平台推送事件消息 - */ - @Test - public void testHandlerPush() throws Exception { - this.wxBase.setWxInMsg(StreamTool.toStream(this.pushXml)); - String reply_msg = this.wxBase.handlerPush(); - assertThat("success", is(reply_msg)); - } -} \ No newline at end of file diff --git a/src/test/java/org/elkan1788/osc/weixin/mp/util/SimpleHttpReqTest.java b/src/test/java/org/elkan1788/osc/weixin/mp/util/SimpleHttpReqTest.java deleted file mode 100644 index f011889..0000000 --- a/src/test/java/org/elkan1788/osc/weixin/mp/util/SimpleHttpReqTest.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.elkan1788.osc.weixin.mp.util; - -import com.alibaba.fastjson.JSON; -import org.elkan1788.osc.testunit.TestSupport; -import org.elkan1788.osc.weixin.mp.commons.WxApiUrl; -import org.junit.Before; -import org.junit.Test; - -import java.io.File; -import java.util.HashMap; -import java.util.Map; - -/** - * HTTP请求测试 - * - * @author 凡梦星尘(elkan1788@gmail.com) - * @since 2014/11/12 - */ -public class SimpleHttpReqTest extends TestSupport { - - private String mediaType = "image"; - - @Before - public void init() throws Exception { - super.init(); - } - - @Test - public void testGet() throws Exception { - String url = String.format(WxApiUrl.ACCESS_TOKEN_API, appId, appSecret); - System.out.println(url); - String content = SimpleHttpReq.get(url); - System.out.println(content); - } - - @Test - public void testPost() throws Exception { - Map msg = new HashMap<>(); - msg.put("touser", openId); - msg.put("msgtype", mediaType); - Map image = new HashMap<>(); - image.put("media_id", mediaId); - msg.put("image", image); - String url = String.format(WxApiUrl.CUSTOM_MESSAGE_API, accessToken); - System.out.println(url); - System.out.println(JSON.toJSONString(msg)); - String content = SimpleHttpReq.post(url, SimpleHttpReq.APPLICATION_JSON, JSON.toJSONString(msg)); - System.out.println(content); - } - - @Test - public void testUpload() throws Exception { - File f = new File("D:/upload.jpg"); - String url = String.format(WxApiUrl.MEDIA_UP_API, mediaType, accessToken); - System.out.println(url); - String content = SimpleHttpReq.upload(url, f); - System.out.println(content); - } - - @Test - public void testDownload() throws Exception { - File f = new File("D:/download.jpg"); - String url = String.format(WxApiUrl.MEDIA_DL_API, accessToken, mediaId); - System.out.println(url); - SimpleHttpReq.download(url, f); - } -} \ No newline at end of file diff --git a/src/test/java/org/elkan1788/osc/weixin/mp/util/StreamUtilTest.java b/src/test/java/org/elkan1788/osc/weixin/mp/util/StreamUtilTest.java deleted file mode 100644 index 23258e8..0000000 --- a/src/test/java/org/elkan1788/osc/weixin/mp/util/StreamUtilTest.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.elkan1788.osc.weixin.mp.util; - -import org.junit.Test; - -import java.io.InputStream; - -public class StreamUtilTest { - - @Test - public void testStr2Stream() throws Exception { - - } - - @Test - public void testStream2String() throws Exception { - InputStream is = this.getClass().getResourceAsStream("/mysqlnginx.txt"); - long t1 = System.currentTimeMillis(); - String str = StreamTool.toString(is); - long t2 = System.currentTimeMillis(); - System.out.println(str); - System.out.println((t2-t1)); - } -} \ No newline at end of file diff --git a/src/test/java/org/elkan1788/osc/weixin/mp/util/XmlMsgBuilderTest.java b/src/test/java/org/elkan1788/osc/weixin/mp/util/XmlMsgBuilderTest.java deleted file mode 100644 index 07545af..0000000 --- a/src/test/java/org/elkan1788/osc/weixin/mp/util/XmlMsgBuilderTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.elkan1788.osc.weixin.mp.util; - -import org.elkan1788.osc.testunit.TestSupport; -import org.elkan1788.osc.weixin.mp.core.WxApi; -import org.elkan1788.osc.weixin.mp.vo.Article; -import org.elkan1788.osc.weixin.mp.vo.OutPutMsg; -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -public class XmlMsgBuilderTest extends TestSupport { - - private OutPutMsg msg; - - @Before - public void init() { - msg = new OutPutMsg(openId, mpId, WxApi.TEXT); - List
    articles = new ArrayList<>(); - for (int i=1; i<11; i++) { - Article a = new Article(); - a.setTitle(getRandomStr(new Random().nextInt(100)+20)); - a.setDescription(getRandomStr(new Random().nextInt(200) + 120)); - a.setPicUrl(getRandomStr(new Random().nextInt(60) + 20)); - a.setUrl(getRandomStr(new Random().nextInt(80) + 20)); - articles.add(a); - } - msg.setArticles(articles); - } - - private String getRandomStr(int length) { - String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - Random random = new Random(); - StringBuffer sb = new StringBuffer(); - for (int i = 0; i < length; i++) { - int number = random.nextInt(base.length()); - sb.append(base.charAt(number)); - } - return sb.toString(); - } - - @Test - public void testText() throws Exception { - msg.setContent(getRandomStr(20)); - long t1 = System.currentTimeMillis(); - String xml = XmlMsgBuilder.create().text(msg).build(); - long t2 = System.currentTimeMillis(); - System.out.println(xml); - System.out.println((t2-t1)); - } - - @Test - public void testImage() throws Exception { - - } - - @Test - public void testNews() throws Exception { - long t = 0l; - String xml = ""; - for(int i=0; i<101; i++) { - long t1 = System.currentTimeMillis(); - xml = XmlMsgBuilder.create().news(msg).build(); - long t2 = System.currentTimeMillis(); - t+= (t2 - t1); - } - System.out.println(((double)t / 101.0)); - t = 0l; - for(int i=0; i<101; i++) { - long t1 = System.currentTimeMillis(); - XmlMsgBuilder xb = new XmlMsgBuilder(); - xb.news(msg); - xml = xb.build(); - long t2 = System.currentTimeMillis(); - t+= (t2 - t1); - } - System.out.println(((double)t / 101.0)); - System.out.println(xml); - } -} \ No newline at end of file diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties new file mode 100644 index 0000000..3181327 --- /dev/null +++ b/src/test/resources/log4j.properties @@ -0,0 +1,5 @@ +log4j.rootLogger=DEBUG,STDOUT +log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender +log4j.appender.STDOUT.Target=System.out +log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout +log4j.appender.STDOUT.layout.ConversionPattern=%d{ABSOLUTE} %3p %c{1}:%L - %m%n \ No newline at end of file diff --git a/src/test/resources/mpconf.properties b/src/test/resources/mpconf.properties new file mode 100644 index 0000000..9c47596 --- /dev/null +++ b/src/test/resources/mpconf.properties @@ -0,0 +1,15 @@ +## wechat 配置文件 +mpId= +appId= +appSecret= +token= +accessToken= +jsTicket= + +## 测试数据 +openId= +openId2= +groupId= +mediaId= +ticket= +tmplId= \ No newline at end of file diff --git a/src/test/resources/mpsdk4j-logo.png b/src/test/resources/mpsdk4j-logo.png new file mode 100644 index 0000000..4cea696 Binary files /dev/null and b/src/test/resources/mpsdk4j-logo.png differ