| @@ -26,6 +26,7 @@ test-config.xml | |||
| /gradle/ | |||
| *.bat | |||
| /gradlew | |||
| **/build/ | |||
| # OSX | |||
| # Icon must end with two \r | |||
| @@ -1,30 +1,33 @@ | |||
| # Weixin Java Tools 微信公众号/企业号开发Java SDK | |||
| ## [](https://github.com/ellerbrock/open-source-badge/)  [](https://travis-ci.org/wechat-group/weixin-java-tools) | |||
| Weixin Java Tools 微信公众号/企业号开发Java SDK | |||
| ===================================== | |||
| [](https://maven-badges.herokuapp.com/maven-central/com.github.binarywang/weixin-java-parent) | |||
| [](https://travis-ci.org/wechat-group/weixin-java-tools) | |||
| ### 注意: | |||
| 1. ***本项目Fork自chanjarster/weixin-java-tools,但由于原项目已停止维护,故单独维护和发布,且发布到maven上的groupId也会不同,详细信息见下文。*** | |||
| 1. ***自2.0.0版本以来,公众号的接口调整比较大,主要是为了解决主接口类过于庞大不方便管理的问题,将接口实现代码按模块进行拆分。*** | |||
| 1. 本SDK要求的最低JDK版本是7,为满足少量还在使用JDK6的用户的需求,特意抽出独立的代码分支项目,请参考 https://github.com/binarywang/weixin-java-tools-for-jdk6 ,其他更早的JDK版本则需要自己改造实现; | |||
| 1. 最新更新:2016-09-30 发布2.2.0正式版! | |||
| 1. 最新更新:2016-10-31 发布2.3.0正式版! | |||
| =========== | |||
| ## 开发交流方式: | |||
| 1. ***QQ群入群须知:为保证入群成员质量,请申请入群前,先Star本项目,然后在申请入群时,输入您的Github帐号ID,以便管理员核对,ID即你的github主页地址https://github.com/XXXX 中最后的部分XXXX的内容,或者在github网页右上角点击头像后查看Signed in as后方内容。*** | |||
| ## 开发交流方式及注意事项: | |||
| 1. QQ群:343954419(推荐点击按钮入群: [](http://shang.qq.com/wpa/qunwpa?idkey=731dc3e7ea31ebe25376cc1a791445468612c63fd0e9e05399b088ec81fd9e15) 或 [](http://jq.qq.com/?_wv=1027&k=40lRskK),如果无反应,可以自行搜索群号进行添加 ) | |||
| 1. 由于群容量有限即将爆满,现开启付费入群模式,并不定期清理长时间不活跃人士; | |||
| 1. 微信群: 因微信群已达到100人限制,故如有想加入微信群的,请入QQ群后联系管理员,提供微信号以便邀请加入; | |||
| 1. 新手提问前,请先阅读此文章:http://t.cn/RV93MRB | |||
| 1. 寻求帮助时需贴代码或大长串异常信息的,请利用http://paste.ubuntu.com | |||
| 1. QQ群:343954419 [](http://shang.qq.com/wpa/qunwpa?idkey=078f7a153d243853e24cf2b542e7a6ccbf2a592bc138080f84d11297f736ec46) | |||
| 1. 微信群: 因微信群已达到100人限制,故如有想加入微信群的,请入QQ群后联系管理员,提供微信号以便邀请加入; | |||
| 1. 有功能需求或由于微信官方接口调整导致的代码问题,可以直接提出issue,便于讨论追踪问题; | |||
| 1. 详细开发文档请看 [Wiki](https://github.com/wechat-group/weixin-java-tools/wiki)。 | |||
| 1. 详细开发文档请看 [Wiki](https://github.com/wechat-group/weixin-java-tools/wiki),部分文档可能未能及时更新,如有发现,可以及时上报或者自行修改。 | |||
| 1. 微信公众号官方文档入口地址:http://mp.weixin.qq.com/wiki (注意,从网上搜到的文档有的虽然地址前面跟这个一样,但明显左侧菜单不一致,是旧的文档,注意不要看错文档)。 | |||
| 1. 各个模块的Javadoc可以在线查看:[weixin-java-mp](https://binarywang.github.io/weixin-java-mp-javadoc/)、[weixin-java-common](https://binarywang.github.io/weixin-java-common-javadoc/)、[weixin-java-cp](https://binarywang.github.io/weixin-java-cp-javadoc/) | |||
| =========== | |||
| ## 版本说明 | |||
| 1. 本项目定为每月发布一次正式版,版本号格式为X.X.0(如2.1.0,2.2.0等),月初或月底发布新版本,遇到重大问题需修复会及时提交新版本,欢迎大家随时提交Pull Request; | |||
| 1. BUG修复和新特性一般会先发布成小版本作为临时版本(如2.0.1,2.0.2等,即尾号不为0,以区别于正式版); | |||
| 1. 目前最新版本号为  ,也可以通过访问链接 [【公众号】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-mp%22) 、[【企业号】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-cp%22) | |||
| 1. 目前最新版本号为 [](https://maven-badges.herokuapp.com/maven-central/com.github.binarywang/weixin-java-parent) ,也可以通过访问链接 [【公众号】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-mp%22) 、[【企业号】](http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.github.binarywang%22%20AND%20a%3A%22weixin-java-cp%22) | |||
| 分别查看所有最新的版本。 | |||
| ## Maven & Gradle | |||
| @@ -36,12 +39,12 @@ maven: | |||
| <dependency> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-mp</artifactId> | |||
| <version>2.2.0</version> | |||
| <version>2.3.0</version> | |||
| </dependency> | |||
| ``` | |||
| gradle: | |||
| ```groovy | |||
| compile 'com.github.binarywang:weixin-java-mp:2.2.0' | |||
| compile 'com.github.binarywang:weixin-java-mp:2.3.0' | |||
| ``` | |||
| * 企业号: | |||
| @@ -51,12 +54,12 @@ maven: | |||
| <dependency> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-cp</artifactId> | |||
| <version>2.2.0</version> | |||
| <version>2.3.0</version> | |||
| </dependency> | |||
| ``` | |||
| gradle: | |||
| ```groovy | |||
| compile 'com.github.binarywang:weixin-java-cp:2.2.0' | |||
| compile 'com.github.binarywang:weixin-java-cp:2.3.0' | |||
| ``` | |||
| =========== | |||
| @@ -69,15 +72,42 @@ compile 'com.github.binarywang:weixin-java-cp:2.2.0' | |||
| * https://git.coding.net/binarywang/weixin-java-tools.git | |||
| =========== | |||
| ## 目前可参考的Demo项目有两个(目前都是公众号的,风格不同,欢迎提供更多的demo供新手参考): | |||
| 1. https://github.com/wechat-group/weixin-mp-demo | |||
| ## 可参考的Demo项目 | |||
| #### 目前都是公众号的,风格不同,欢迎提供更多的demo供新手参考: | |||
| 1. https://github.com/wechat-group/weixin-mp-demo | |||
| 1. https://github.com/wechat-group/weixin-mp-multi-demo (支持多公众号) | |||
| 1. https://github.com/wechat-group/weixin-java-tools-springmvc | |||
| 1. https://github.com/wechat-group/weixin-mp-demo-springboot | |||
| =========== | |||
| ## 关于代码贡献 | |||
| 1. 非常欢迎和感谢对本项目发起Pull Request的同学,本项目代码风格为使用2个空格代表一个Tab,因此在提交代码时请注意一下,否则很容易在IDE格式化代码后与原代码产生大量diff,这样会给其他人阅读代码带来极大的困扰。 | |||
| 1. 为了便于设置,本项目引入editorconfig插件,请使用eclipse的同学在贡献代码前安装相关插件,IntelliJ IDEA则自带支持,无需额外安装插件。 | |||
| 1. 本项目可以采用两种方式接受代码贡献: | |||
| * 第一种就是基于[Git Flow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow)开发流程,因此在发起Pull Request的时候请选择develop分支,详细步骤参考后文。 | |||
| * 另外一种贡献代码的方式就是加入SDK Developers开发组,前提是对自己的代码足够自信就可以申请加入,加入之后可以随时直接提交代码,但要注意对所做的修改或新增的代码进行单元测试,保证提交代码没有明显问题,具体加入方式,请咨询QQ群管理员[](http://wpa.qq.com/msgrd?v=3&uin=1211415707&site=qq&menu=yes)。 | |||
| 1. 第一种就是基于[Git Flow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow)开发流程,因此在发起Pull Request的时候请选择develop分支。 | |||
| 1. 另外一种贡献代码的方式就是加入SDK Developers开发组,前提是对自己的代码足够自信就可以申请加入,加入之后可以随时直接提交代码,但要注意对所做的修改或新增的代码进行单元测试,保证提交代码没有明显问题,具体加入方式,请咨询管理员。 | |||
| ## PR方式贡献代码步骤 | |||
| * 在 GitHub 上 `fork` 到自己的仓库,如 `my_user/weixin-java-tools`,然后 `clone` 到本地,并设置用户信息。 | |||
| ``` | |||
| $ git clone git@github.com:my_user/weixin-java-tools.git | |||
| $ cd weixin-java-tools | |||
| $ git config user.name "yourname" | |||
| $ git config user.email "your email" | |||
| ``` | |||
| * 修改代码后提交,并推送到自己的仓库。 | |||
| ``` | |||
| $ #do some change on the content | |||
| $ git commit -am "Fix issue #1: change something" | |||
| $ git push | |||
| ``` | |||
| * 在 GitHub 网站上提交 Pull Request。 | |||
| * 定期使用项目仓库内容更新自己仓库内容。 | |||
| ``` | |||
| $ git remote add upstream https://github.com/wechat-group/weixin-java-tools | |||
| $ git fetch upstream | |||
| $ git checkout develop | |||
| $ git rebase upstream/develop | |||
| $ git push -f origin develop | |||
| ``` | |||
| @@ -2,7 +2,7 @@ allprojects { | |||
| apply plugin: 'maven' | |||
| group = 'com.github.binarywang' | |||
| version = '2.2.0' | |||
| version = '2.4.0-SNAPSHOT' | |||
| } | |||
| subprojects { | |||
| @@ -21,10 +21,9 @@ subprojects { | |||
| dependencies { | |||
| compile group: 'org.slf4j', name: 'slf4j-api', version:'1.7.10' | |||
| compile group: 'org.apache.httpcomponents', name: 'httpmime', version:'4.5' | |||
| compile group: 'org.jodd', name: 'jodd-http', version:'3.6.7' | |||
| compile group: 'org.apache.httpcomponents', name: 'httpclient', version:'4.5' | |||
| compile group: 'com.google.code.gson', name: 'gson', version:'2.7' | |||
| compile group: 'com.google.guava', name: 'guava', version:'19.0' | |||
| compile group: 'org.jooq', name: 'joor', version:'0.9.6' | |||
| compile group: 'commons-codec', name: 'commons-codec', version:'1.10' | |||
| compile group: 'commons-io', name: 'commons-io', version:'2.5' | |||
| compile group: 'org.apache.commons', name: 'commons-lang3', version:'3.4' | |||
| @@ -5,11 +5,11 @@ | |||
| <modelVersion>4.0.0</modelVersion> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>2.3.0-SNAPSHOT</version> | |||
| <version>2.4.0-SNAPSHOT</version> | |||
| <packaging>pom</packaging> | |||
| <name>WeiXin Java Tools - Parent</name> | |||
| <description>微信公众号、企业号上级POM</description> | |||
| <url>https://github.com/binarywang/weixin-java-tools</url> | |||
| <url>https://github.com/wechat-group/weixin-java-tools</url> | |||
| <licenses> | |||
| <license> | |||
| @@ -22,17 +22,44 @@ | |||
| <developer> | |||
| <name>Daniel Qian</name> | |||
| <email>chanjarster@gmail.com</email> | |||
| <url>https://github.com/chanjarster</url> | |||
| </developer> | |||
| <developer> | |||
| <name>Binary Wang</name> | |||
| <email>binarywang@gmail.com</email> | |||
| <url>https://github.com/binarywang</url> | |||
| </developer> | |||
| <developer> | |||
| <name>gaigeshen</name> | |||
| <email>gaigeshen@qq.com</email> | |||
| <url>https://github.com/gaigeshen</url> | |||
| </developer> | |||
| <developer> | |||
| <name>Liu Mingbo</name> | |||
| <email>liumingbo2008@gmail.com</email> | |||
| <url>https://github.com/FirenzesEagle</url> | |||
| </developer> | |||
| <developer> | |||
| <name>kakotor</name> | |||
| <email>kakotor@gmail.com</email> | |||
| <url>https://github.com/kakotor</url> | |||
| </developer> | |||
| <developer> | |||
| <name>xiong</name> | |||
| <email>zhaoxiong.tan@gmail.com</email> | |||
| <url>https://github.com/ZhaoxiongTan</url> | |||
| </developer> | |||
| <developer> | |||
| <name>LiuJunGuang</name> | |||
| <email>aimilin@yeah.net</email> | |||
| <url>https://github.com/aimilin6688</url> | |||
| </developer> | |||
| </developers> | |||
| <scm> | |||
| <connection>scm:git:https://github.com/binarywang/weixin-java-tools.git</connection> | |||
| <developerConnection>scm:git:git@github.com:binarywang/weixin-java-tools.git</developerConnection> | |||
| <url>https://github.com/binarywang/weixin-java-tools</url> | |||
| <connection>scm:git:https://github.com/wechat-group/weixin-java-tools.git</connection> | |||
| <developerConnection>scm:git:git@github.com:wechat-group/weixin-java-tools.git</developerConnection> | |||
| <url>https://github.com/wechat-group/weixin-java-tools</url> | |||
| </scm> | |||
| <modules> | |||
| @@ -43,22 +70,22 @@ | |||
| </modules> | |||
| <properties> | |||
| <maven.compiler.source>1.7</maven.compiler.source> | |||
| <maven.compiler.target>1.7</maven.compiler.target> | |||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||
| <downloadJavadocs>true</downloadJavadocs> | |||
| <downloadSources>true</downloadSources> | |||
| <httpclient.version>4.5</httpclient.version> | |||
| <slf4j.version>1.7.10</slf4j.version> | |||
| <logback.version>1.1.2</logback.version> | |||
| <jodd-http.version>3.6.7</jodd-http.version> | |||
| <jedis.version>2.9.0</jedis.version> | |||
| <gson.version>2.7</gson.version> | |||
| <guava.version>19.0</guava.version> | |||
| <joor.version>0.9.6</joor.version> | |||
| <commons-lang3.version>3.4</commons-lang3.version> | |||
| <commons-lang3.version>3.5</commons-lang3.version> | |||
| <commons-io.version>2.5</commons-io.version> | |||
| <commons-codec.version>1.10</commons-codec.version> | |||
| <jetty.version>9.3.0.M0</jetty.version> | |||
| <jetty-new.version>9.3.10.v20160621</jetty-new.version> | |||
| <jetty.version>9.3.0.RC0</jetty.version> | |||
| </properties> | |||
| <dependencies> | |||
| @@ -75,13 +102,13 @@ | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.apache.httpcomponents</groupId> | |||
| <artifactId>httpmime</artifactId> | |||
| <artifactId>httpclient</artifactId> | |||
| <version>${httpclient.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.jodd</groupId> | |||
| <artifactId>jodd-http</artifactId> | |||
| <version>${jodd-http.version}</version> | |||
| <groupId>org.apache.httpcomponents</groupId> | |||
| <artifactId>httpmime</artifactId> | |||
| <version>${httpclient.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>com.google.code.gson</groupId> | |||
| @@ -113,10 +140,6 @@ | |||
| <groupId>com.google.guava</groupId> | |||
| <artifactId>guava</artifactId> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.jooq</groupId> | |||
| <artifactId>joor</artifactId> | |||
| </dependency> | |||
| </dependencies> | |||
| <dependencyManagement> | |||
| @@ -162,11 +185,6 @@ | |||
| <artifactId>guava</artifactId> | |||
| <version>${guava.version}</version> | |||
| </dependency> | |||
| <dependency> | |||
| <groupId>org.jooq</groupId> | |||
| <artifactId>joor</artifactId> | |||
| <version>${joor.version}</version> | |||
| </dependency> | |||
| </dependencies> | |||
| </dependencyManagement> | |||
| @@ -286,10 +304,8 @@ | |||
| <plugin> | |||
| <groupId>org.apache.maven.plugins</groupId> | |||
| <artifactId>maven-compiler-plugin</artifactId> | |||
| <version>2.3.2</version> | |||
| <version>3.6.0</version> | |||
| <configuration> | |||
| <source>1.7</source> | |||
| <target>1.7</target> | |||
| <encoding>UTF-8</encoding> | |||
| </configuration> | |||
| </plugin> | |||
| @@ -6,7 +6,7 @@ dependencies { | |||
| testCompile group: 'org.testng', name: 'testng', version:'6.8.7' | |||
| testCompile group: 'org.mockito', name: 'mockito-all', version:'1.9.5' | |||
| testCompile group: 'com.google.inject', name: 'guice', version:'3.0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.M0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.M0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.RC0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.RC0' | |||
| } | |||
| test.useTestNG() | |||
| @@ -6,7 +6,7 @@ | |||
| <parent> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>2.3.0-SNAPSHOT</version> | |||
| <version>2.4.0-SNAPSHOT</version> | |||
| </parent> | |||
| <artifactId>weixin-java-common</artifactId> | |||
| @@ -23,14 +23,15 @@ public class WxConsts { | |||
| /////////////////////// | |||
| // 主动发送消息(即客服消息)的消息类型 | |||
| /////////////////////// | |||
| public static final String CUSTOM_MSG_TEXT = "text"; | |||
| public static final String CUSTOM_MSG_IMAGE = "image"; | |||
| public static final String CUSTOM_MSG_VOICE = "voice"; | |||
| public static final String CUSTOM_MSG_VIDEO = "video"; | |||
| public static final String CUSTOM_MSG_MUSIC = "music"; | |||
| public static final String CUSTOM_MSG_NEWS = "news"; | |||
| public static final String CUSTOM_MSG_FILE = "file"; | |||
| public static final String CUSTOM_MSG_WXCARD = "wxcard"; | |||
| public static final String CUSTOM_MSG_TEXT = "text";//文本消息 | |||
| public static final String CUSTOM_MSG_IMAGE = "image";//图片消息 | |||
| public static final String CUSTOM_MSG_VOICE = "voice";//语音消息 | |||
| public static final String CUSTOM_MSG_VIDEO = "video";//视频消息 | |||
| public static final String CUSTOM_MSG_MUSIC = "music";//音乐消息 | |||
| public static final String CUSTOM_MSG_NEWS = "news";//图文消息(点击跳转到外链) | |||
| public static final String CUSTOM_MSG_MPNEWS = "mpnews";//图文消息(点击跳转到图文消息页面) | |||
| public static final String CUSTOM_MSG_FILE = "file";//发送文件(CP专用) | |||
| public static final String CUSTOM_MSG_WXCARD = "wxcard";//卡券消息 | |||
| public static final String CUSTOM_MSG_TRANSFER_CUSTOMER_SERVICE = "transfer_customer_service"; | |||
| public static final String CUSTOM_MSG_SAFE_NO = "0"; | |||
| public static final String CUSTOM_MSG_SAFE_YES = "1"; | |||
| @@ -193,7 +194,7 @@ public class WxConsts { | |||
| * 弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息 | |||
| */ | |||
| public static final String OAUTH2_SCOPE_USER_INFO = "snsapi_userinfo"; | |||
| /** | |||
| * 网页应用登录授权作用域 snsapi_login | |||
| */ | |||
| @@ -1,9 +1,8 @@ | |||
| package me.chanjar.weixin.common.bean; | |||
| import java.io.Serializable; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import java.io.Serializable; | |||
| /** | |||
| * 卡券Api签名 | |||
| @@ -35,7 +34,7 @@ public class WxCardApiSignature implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public String getAppId() { | |||
| @@ -1,11 +1,10 @@ | |||
| package me.chanjar.weixin.common.bean.menu; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| public class WxMenuButton { | |||
| private String type; | |||
| @@ -18,10 +17,9 @@ public class WxMenuButton { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, | |||
| ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public String getType() { | |||
| return this.type; | |||
| } | |||
| @@ -69,4 +67,4 @@ public class WxMenuButton { | |||
| public void setMediaId(String mediaId) { | |||
| this.mediaId = mediaId; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,7 +1,6 @@ | |||
| package me.chanjar.weixin.common.bean.menu; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| public class WxMenuRule { | |||
| private String tagId; | |||
| @@ -70,6 +69,6 @@ public class WxMenuRule { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| } | |||
| @@ -0,0 +1,88 @@ | |||
| package me.chanjar.weixin.common.util; | |||
| import com.google.common.collect.Lists; | |||
| import com.google.common.collect.Maps; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import me.chanjar.weixin.common.annotation.Required; | |||
| import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import java.lang.reflect.Field; | |||
| import java.util.ArrayList; | |||
| import java.util.Arrays; | |||
| import java.util.List; | |||
| import java.util.Map; | |||
| /** | |||
| * <pre> | |||
| * bean操作的一些工具类 | |||
| * Created by Binary Wang on 2016-10-21. | |||
| * @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||
| * </pre> | |||
| */ | |||
| public class BeanUtils { | |||
| /** | |||
| * 检查bean里标记为@Required的field是否为空,为空则抛异常 | |||
| * @param bean 要检查的bean对象 | |||
| * @throws WxErrorException | |||
| */ | |||
| public static void checkRequiredFields(Object bean) throws WxErrorException { | |||
| List<String> nullFields = Lists.newArrayList(); | |||
| List<Field> fields = new ArrayList<>( Arrays.asList(bean.getClass().getDeclaredFields())); | |||
| fields.addAll(Arrays.asList(bean.getClass().getSuperclass().getDeclaredFields())); | |||
| for (Field field : fields) { | |||
| try { | |||
| boolean isAccessible = field.isAccessible(); | |||
| field.setAccessible(true); | |||
| if (field.isAnnotationPresent(Required.class) | |||
| && field.get(bean) == null) { | |||
| nullFields.add(field.getName()); | |||
| } | |||
| field.setAccessible(isAccessible); | |||
| } catch (SecurityException | IllegalArgumentException | |||
| | IllegalAccessException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| if (!nullFields.isEmpty()) { | |||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg("必填字段 " + nullFields + " 必须提供值").build()); | |||
| } | |||
| } | |||
| /** | |||
| * 将bean按照@XStreamAlias标识的字符串内容生成以之为key的map对象 | |||
| * @param bean 包含@XStreamAlias的xml bean对象 | |||
| * @return map对象 | |||
| */ | |||
| public static Map<String, String> xmlBean2Map(Object bean) { | |||
| Map<String, String> result = Maps.newHashMap(); | |||
| List<Field> fields = new ArrayList<>( Arrays.asList(bean.getClass().getDeclaredFields())); | |||
| fields.addAll(Arrays.asList(bean.getClass().getSuperclass().getDeclaredFields())); | |||
| for (Field field : fields) { | |||
| try { | |||
| boolean isAccessible = field.isAccessible(); | |||
| field.setAccessible(true); | |||
| if (field.get(bean) == null) { | |||
| field.setAccessible(isAccessible); | |||
| continue; | |||
| } | |||
| if (field.isAnnotationPresent(XStreamAlias.class)) { | |||
| result.put(field.getAnnotation(XStreamAlias.class).value(), | |||
| field.get(bean).toString()); | |||
| } | |||
| field.setAccessible(isAccessible); | |||
| } catch (SecurityException | IllegalArgumentException | |||
| | IllegalAccessException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| return result; | |||
| } | |||
| } | |||
| @@ -1,98 +0,0 @@ | |||
| package me.chanjar.weixin.common.util; | |||
| /** | |||
| * copy from apache-commons-lang3 | |||
| */ | |||
| public class StringUtils { | |||
| /** | |||
| * <p>Checks if a CharSequence is whitespace, empty ("") or null.</p> | |||
| * <p> | |||
| * <pre> | |||
| * StringUtils.isBlank(null) = true | |||
| * StringUtils.isBlank("") = true | |||
| * StringUtils.isBlank(" ") = true | |||
| * StringUtils.isBlank("bob") = false | |||
| * StringUtils.isBlank(" bob ") = false | |||
| * </pre> | |||
| * | |||
| * @param cs the CharSequence to check, may be null | |||
| * @return {@code true} if the CharSequence is null, empty or whitespace | |||
| * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence) | |||
| */ | |||
| public static boolean isBlank(CharSequence cs) { | |||
| int strLen; | |||
| if (cs == null || (strLen = cs.length()) == 0) { | |||
| return true; | |||
| } | |||
| for (int i = 0; i < strLen; i++) { | |||
| if (Character.isWhitespace(cs.charAt(i)) == false) { | |||
| return false; | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| /** | |||
| * <p>Checks if a CharSequence is not empty (""), not null and not whitespace only.</p> | |||
| * <p> | |||
| * <pre> | |||
| * StringUtils.isNotBlank(null) = false | |||
| * StringUtils.isNotBlank("") = false | |||
| * StringUtils.isNotBlank(" ") = false | |||
| * StringUtils.isNotBlank("bob") = true | |||
| * StringUtils.isNotBlank(" bob ") = true | |||
| * </pre> | |||
| * | |||
| * @param cs the CharSequence to check, may be null | |||
| * @return {@code true} if the CharSequence is | |||
| * not empty and not null and not whitespace | |||
| * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) | |||
| */ | |||
| public static boolean isNotBlank(CharSequence cs) { | |||
| return !StringUtils.isBlank(cs); | |||
| } | |||
| /** | |||
| * <p>Checks if a CharSequence is empty ("") or null.</p> | |||
| * <p> | |||
| * <pre> | |||
| * StringUtils.isEmpty(null) = true | |||
| * StringUtils.isEmpty("") = true | |||
| * StringUtils.isEmpty(" ") = false | |||
| * StringUtils.isEmpty("bob") = false | |||
| * StringUtils.isEmpty(" bob ") = false | |||
| * </pre> | |||
| * <p> | |||
| * <p>NOTE: This method changed in Lang version 2.0. | |||
| * It no longer trims the CharSequence. | |||
| * That functionality is available in isBlank().</p> | |||
| * | |||
| * @param cs the CharSequence to check, may be null | |||
| * @return {@code true} if the CharSequence is empty or null | |||
| * @since 3.0 Changed signature from isEmpty(String) to isEmpty(CharSequence) | |||
| */ | |||
| public static boolean isEmpty(CharSequence cs) { | |||
| return cs == null || cs.length() == 0; | |||
| } | |||
| /** | |||
| * <p>Checks if a CharSequence is not empty ("") and not null.</p> | |||
| * <p> | |||
| * <pre> | |||
| * StringUtils.isNotEmpty(null) = false | |||
| * StringUtils.isNotEmpty("") = false | |||
| * StringUtils.isNotEmpty(" ") = true | |||
| * StringUtils.isNotEmpty("bob") = true | |||
| * StringUtils.isNotEmpty(" bob ") = true | |||
| * </pre> | |||
| * | |||
| * @param cs the CharSequence to check, may be null | |||
| * @return {@code true} if the CharSequence is not empty and not null | |||
| * @since 3.0 Changed signature from isNotEmpty(String) to isNotEmpty(CharSequence) | |||
| */ | |||
| public static boolean isNotEmpty(CharSequence cs) { | |||
| return !StringUtils.isEmpty(cs); | |||
| } | |||
| } | |||
| @@ -0,0 +1,61 @@ | |||
| package me.chanjar.weixin.common.util; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| /** | |||
| * <pre> | |||
| * 自定义的ToString方法,用于产生去掉空值属性的字符串 | |||
| * Created by Binary Wang on 2016-10-27. | |||
| * @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||
| * </pre> | |||
| */ | |||
| public class ToStringUtils { | |||
| public static final ToStringStyle THE_STYLE = new SimpleMultiLineToStringStyle(); | |||
| private static class SimpleMultiLineToStringStyle extends ToStringStyle { | |||
| private static final long serialVersionUID = 4645306494220335355L; | |||
| private static final String LINE_SEPARATOR = "\n"; | |||
| private static final String NULL_TEXT = "<null>"; | |||
| public SimpleMultiLineToStringStyle() { | |||
| super(); | |||
| this.setContentStart("["); | |||
| this.setFieldSeparator(LINE_SEPARATOR + " "); | |||
| this.setFieldSeparatorAtStart(true); | |||
| this.setContentEnd(LINE_SEPARATOR + "]"); | |||
| this.setNullText(NULL_TEXT); | |||
| this.setUseShortClassName(true); | |||
| this.setUseIdentityHashCode(false); | |||
| } | |||
| } | |||
| /** | |||
| * 用于产生去掉空值属性并以换行符分割各属性键值的toString字符串 | |||
| * @param obj | |||
| */ | |||
| public static String toSimpleString(Object obj) { | |||
| String toStringResult = ToStringBuilder.reflectionToString(obj, THE_STYLE); | |||
| String[] split = toStringResult.split(SimpleMultiLineToStringStyle.LINE_SEPARATOR); | |||
| StringBuilder result = new StringBuilder(); | |||
| for (String string : split) { | |||
| if (string.endsWith(SimpleMultiLineToStringStyle.NULL_TEXT)) { | |||
| continue; | |||
| } | |||
| result.append(string + SimpleMultiLineToStringStyle.LINE_SEPARATOR); | |||
| } | |||
| if (result.length() == 0) { | |||
| return ""; | |||
| } | |||
| //如果没有非空的属性,就输出 <all null properties> | |||
| if (StringUtils.countMatches(result, SimpleMultiLineToStringStyle.LINE_SEPARATOR) == 2) { | |||
| return result.toString().split(SimpleMultiLineToStringStyle.LINE_SEPARATOR)[0] | |||
| + "<all null values>]"; | |||
| } | |||
| return result.deleteCharAt(result.length() - 1).toString(); | |||
| } | |||
| } | |||
| @@ -1,300 +1,300 @@ | |||
| /** | |||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||
| * | |||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||
| * <p> | |||
| * 针对org.apache.commons.codec.binary.Base64, | |||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||
| */ | |||
| // ------------------------------------------------------------------------ | |||
| /** | |||
| * 针对org.apache.commons.codec.binary.Base64, | |||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||
| */ | |||
| package me.chanjar.weixin.common.util.crypto; | |||
| import java.io.StringReader; | |||
| 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 javax.xml.parsers.DocumentBuilder; | |||
| import javax.xml.parsers.DocumentBuilderFactory; | |||
| import javax.xml.parsers.ParserConfigurationException; | |||
| import org.apache.commons.codec.binary.Base64; | |||
| import org.w3c.dom.Document; | |||
| import org.w3c.dom.Element; | |||
| import org.xml.sax.InputSource; | |||
| public class WxCryptUtil { | |||
| private static final Base64 base64 = new Base64(); | |||
| private static final Charset CHARSET = Charset.forName("utf-8"); | |||
| private static final ThreadLocal<DocumentBuilder> builderLocal = new ThreadLocal<DocumentBuilder>() { | |||
| @Override | |||
| protected DocumentBuilder initialValue() { | |||
| try { | |||
| return DocumentBuilderFactory.newInstance().newDocumentBuilder(); | |||
| } catch (ParserConfigurationException exc) { | |||
| throw new IllegalArgumentException(exc); | |||
| } | |||
| } | |||
| }; | |||
| protected byte[] aesKey; | |||
| protected String token; | |||
| protected String appidOrCorpid; | |||
| public WxCryptUtil() { | |||
| super(); | |||
| } | |||
| /** | |||
| * 构造函数 | |||
| * | |||
| * @param token 公众平台上,开发者设置的token | |||
| * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey | |||
| * @param appidOrCorpid 公众平台appid/corpid | |||
| */ | |||
| public WxCryptUtil(String token, String encodingAesKey, | |||
| String appidOrCorpid) { | |||
| this.token = token; | |||
| this.appidOrCorpid = appidOrCorpid; | |||
| this.aesKey = Base64.decodeBase64(encodingAesKey + "="); | |||
| } | |||
| static String extractEncryptPart(String xml) { | |||
| try { | |||
| DocumentBuilder db = builderLocal.get(); | |||
| Document document = db.parse(new InputSource(new StringReader(xml))); | |||
| Element root = document.getDocumentElement(); | |||
| return root.getElementsByTagName("Encrypt").item(0).getTextContent(); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| /** | |||
| * 将公众平台回复用户的消息加密打包. | |||
| * <ol> | |||
| * <li>对要发送的消息进行AES-CBC加密</li> | |||
| * <li>生成安全签名</li> | |||
| * <li>将消息密文和安全签名打包成xml格式</li> | |||
| * </ol> | |||
| * | |||
| * @param plainText 公众平台待回复用户的消息,xml格式的字符串 | |||
| * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串 | |||
| */ | |||
| public String encrypt(String plainText) { | |||
| // 加密 | |||
| String encryptedXml = encrypt(genRandomStr(), plainText); | |||
| // 生成安全签名 | |||
| String timeStamp = Long.toString(System.currentTimeMillis() / 1000l); | |||
| String nonce = genRandomStr(); | |||
| String signature = SHA1.gen(this.token, timeStamp, nonce, encryptedXml); | |||
| String result = generateXml(encryptedXml, signature, timeStamp, nonce); | |||
| return result; | |||
| } | |||
| /** | |||
| * 对明文进行加密. | |||
| * | |||
| * @param plainText 需要加密的明文 | |||
| * @return 加密后base64编码的字符串 | |||
| */ | |||
| protected String encrypt(String randomStr, String plainText) { | |||
| ByteGroup byteCollector = new ByteGroup(); | |||
| byte[] randomStringBytes = randomStr.getBytes(CHARSET); | |||
| byte[] plainTextBytes = plainText.getBytes(CHARSET); | |||
| byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder( | |||
| plainTextBytes.length); | |||
| byte[] appIdBytes = this.appidOrCorpid.getBytes(CHARSET); | |||
| // randomStr + networkBytesOrder + text + appid | |||
| byteCollector.addBytes(randomStringBytes); | |||
| byteCollector.addBytes(bytesOfSizeInNetworkOrder); | |||
| byteCollector.addBytes(plainTextBytes); | |||
| byteCollector.addBytes(appIdBytes); | |||
| // ... + pad: 使用自定义的填充方式对明文进行补位填充 | |||
| byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); | |||
| byteCollector.addBytes(padBytes); | |||
| // 获得最终的字节流, 未加密 | |||
| byte[] unencrypted = byteCollector.toBytes(); | |||
| try { | |||
| // 设置加密模式为AES的CBC模式 | |||
| Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | |||
| SecretKeySpec keySpec = new SecretKeySpec(this.aesKey, "AES"); | |||
| IvParameterSpec iv = new IvParameterSpec(this.aesKey, 0, 16); | |||
| cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); | |||
| // 加密 | |||
| byte[] encrypted = cipher.doFinal(unencrypted); | |||
| // 使用BASE64对加密后的字符串进行编码 | |||
| String base64Encrypted = base64.encodeToString(encrypted); | |||
| return base64Encrypted; | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| /** | |||
| * 检验消息的真实性,并且获取解密后的明文. | |||
| * <ol> | |||
| * <li>利用收到的密文生成安全签名,进行签名验证</li> | |||
| * <li>若验证通过,则提取xml中的加密消息</li> | |||
| * <li>对消息进行解密</li> | |||
| * </ol> | |||
| * | |||
| * @param msgSignature 签名串,对应URL参数的msg_signature | |||
| * @param timeStamp 时间戳,对应URL参数的timestamp | |||
| * @param nonce 随机串,对应URL参数的nonce | |||
| * @param encryptedXml 密文,对应POST请求的数据 | |||
| * @return 解密后的原文 | |||
| */ | |||
| public String decrypt(String msgSignature, String timeStamp, String nonce, | |||
| String encryptedXml) { | |||
| // 密钥,公众账号的app corpSecret | |||
| // 提取密文 | |||
| String cipherText = extractEncryptPart(encryptedXml); | |||
| // 验证安全签名 | |||
| String signature = SHA1.gen(this.token, timeStamp, nonce, cipherText); | |||
| if (!signature.equals(msgSignature)) { | |||
| throw new RuntimeException("加密消息签名校验失败"); | |||
| } | |||
| // 解密 | |||
| String result = decrypt(cipherText); | |||
| return result; | |||
| } | |||
| /** | |||
| * 对密文进行解密. | |||
| * | |||
| * @param cipherText 需要解密的密文 | |||
| * @return 解密得到的明文 | |||
| */ | |||
| public String decrypt(String cipherText) { | |||
| byte[] original; | |||
| try { | |||
| // 设置解密模式为AES的CBC模式 | |||
| Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | |||
| SecretKeySpec key_spec = new SecretKeySpec(this.aesKey, "AES"); | |||
| IvParameterSpec iv = new IvParameterSpec( | |||
| Arrays.copyOfRange(this.aesKey, 0, 16)); | |||
| cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); | |||
| // 使用BASE64对密文进行解码 | |||
| byte[] encrypted = Base64.decodeBase64(cipherText); | |||
| // 解密 | |||
| original = cipher.doFinal(encrypted); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| String xmlContent, from_appid; | |||
| try { | |||
| // 去除补位字符 | |||
| byte[] bytes = PKCS7Encoder.decode(original); | |||
| // 分离16位随机字符串,网络字节序和AppId | |||
| byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); | |||
| int xmlLength = bytesNetworkOrder2Number(networkOrder); | |||
| xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), | |||
| CHARSET); | |||
| from_appid = new String( | |||
| Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| // appid不相同的情况 | |||
| if (!from_appid.equals(this.appidOrCorpid)) { | |||
| throw new RuntimeException("AppID不正确"); | |||
| } | |||
| return xmlContent; | |||
| } | |||
| /** | |||
| * 将一个数字转换成生成4个字节的网络字节序bytes数组 | |||
| * | |||
| * @param number | |||
| */ | |||
| private static byte[] number2BytesInNetworkOrder(int number) { | |||
| byte[] orderBytes = new byte[4]; | |||
| orderBytes[3] = (byte) (number & 0xFF); | |||
| orderBytes[2] = (byte) (number >> 8 & 0xFF); | |||
| orderBytes[1] = (byte) (number >> 16 & 0xFF); | |||
| orderBytes[0] = (byte) (number >> 24 & 0xFF); | |||
| return orderBytes; | |||
| } | |||
| /** | |||
| * 4个字节的网络字节序bytes数组还原成一个数字 | |||
| * | |||
| * @param bytesInNetworkOrder | |||
| */ | |||
| private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) { | |||
| int sourceNumber = 0; | |||
| for (int i = 0; i < 4; i++) { | |||
| sourceNumber <<= 8; | |||
| sourceNumber |= bytesInNetworkOrder[i] & 0xff; | |||
| } | |||
| return sourceNumber; | |||
| } | |||
| /** | |||
| * 随机生成16位字符串 | |||
| */ | |||
| private static String genRandomStr() { | |||
| String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | |||
| Random random = new Random(); | |||
| StringBuffer sb = new StringBuffer(); | |||
| for (int i = 0; i < 16; i++) { | |||
| int number = random.nextInt(base.length()); | |||
| sb.append(base.charAt(number)); | |||
| } | |||
| return sb.toString(); | |||
| } | |||
| /** | |||
| * 生成xml消息 | |||
| * | |||
| * @param encrypt 加密后的消息密文 | |||
| * @param signature 安全签名 | |||
| * @param timestamp 时间戳 | |||
| * @param nonce 随机字符串 | |||
| * @return 生成的xml字符串 | |||
| */ | |||
| private static String generateXml(String encrypt, String signature, | |||
| String timestamp, String nonce) { | |||
| String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n" | |||
| + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" | |||
| + "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" | |||
| + "</xml>"; | |||
| return String.format(format, encrypt, signature, timestamp, nonce); | |||
| } | |||
| } | |||
| /** | |||
| * 对公众平台发送给公众账号的消息加解密示例代码. | |||
| * | |||
| * @copyright Copyright (c) 1998-2014 Tencent Inc. | |||
| * <p> | |||
| * 针对org.apache.commons.codec.binary.Base64, | |||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||
| */ | |||
| // ------------------------------------------------------------------------ | |||
| /** | |||
| * 针对org.apache.commons.codec.binary.Base64, | |||
| * 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本) | |||
| * 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi | |||
| */ | |||
| package me.chanjar.weixin.common.util.crypto; | |||
| import java.io.StringReader; | |||
| 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 javax.xml.parsers.DocumentBuilder; | |||
| import javax.xml.parsers.DocumentBuilderFactory; | |||
| import javax.xml.parsers.ParserConfigurationException; | |||
| import org.apache.commons.codec.binary.Base64; | |||
| import org.w3c.dom.Document; | |||
| import org.w3c.dom.Element; | |||
| import org.xml.sax.InputSource; | |||
| public class WxCryptUtil { | |||
| private static final Base64 base64 = new Base64(); | |||
| private static final Charset CHARSET = Charset.forName("utf-8"); | |||
| private static final ThreadLocal<DocumentBuilder> builderLocal = new ThreadLocal<DocumentBuilder>() { | |||
| @Override | |||
| protected DocumentBuilder initialValue() { | |||
| try { | |||
| return DocumentBuilderFactory.newInstance().newDocumentBuilder(); | |||
| } catch (ParserConfigurationException exc) { | |||
| throw new IllegalArgumentException(exc); | |||
| } | |||
| } | |||
| }; | |||
| protected byte[] aesKey; | |||
| protected String token; | |||
| protected String appidOrCorpid; | |||
| public WxCryptUtil() { | |||
| super(); | |||
| } | |||
| /** | |||
| * 构造函数 | |||
| * | |||
| * @param token 公众平台上,开发者设置的token | |||
| * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey | |||
| * @param appidOrCorpid 公众平台appid/corpid | |||
| */ | |||
| public WxCryptUtil(String token, String encodingAesKey, | |||
| String appidOrCorpid) { | |||
| this.token = token; | |||
| this.appidOrCorpid = appidOrCorpid; | |||
| this.aesKey = Base64.decodeBase64(encodingAesKey + "="); | |||
| } | |||
| static String extractEncryptPart(String xml) { | |||
| try { | |||
| DocumentBuilder db = builderLocal.get(); | |||
| Document document = db.parse(new InputSource(new StringReader(xml))); | |||
| Element root = document.getDocumentElement(); | |||
| return root.getElementsByTagName("Encrypt").item(0).getTextContent(); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| /** | |||
| * 将公众平台回复用户的消息加密打包. | |||
| * <ol> | |||
| * <li>对要发送的消息进行AES-CBC加密</li> | |||
| * <li>生成安全签名</li> | |||
| * <li>将消息密文和安全签名打包成xml格式</li> | |||
| * </ol> | |||
| * | |||
| * @param plainText 公众平台待回复用户的消息,xml格式的字符串 | |||
| * @return 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串 | |||
| */ | |||
| public String encrypt(String plainText) { | |||
| // 加密 | |||
| String encryptedXml = encrypt(genRandomStr(), plainText); | |||
| // 生成安全签名 | |||
| String timeStamp = Long.toString(System.currentTimeMillis() / 1000l); | |||
| String nonce = genRandomStr(); | |||
| String signature = SHA1.gen(this.token, timeStamp, nonce, encryptedXml); | |||
| String result = generateXml(encryptedXml, signature, timeStamp, nonce); | |||
| return result; | |||
| } | |||
| /** | |||
| * 对明文进行加密. | |||
| * | |||
| * @param plainText 需要加密的明文 | |||
| * @return 加密后base64编码的字符串 | |||
| */ | |||
| protected String encrypt(String randomStr, String plainText) { | |||
| ByteGroup byteCollector = new ByteGroup(); | |||
| byte[] randomStringBytes = randomStr.getBytes(CHARSET); | |||
| byte[] plainTextBytes = plainText.getBytes(CHARSET); | |||
| byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder( | |||
| plainTextBytes.length); | |||
| byte[] appIdBytes = this.appidOrCorpid.getBytes(CHARSET); | |||
| // randomStr + networkBytesOrder + text + appid | |||
| byteCollector.addBytes(randomStringBytes); | |||
| byteCollector.addBytes(bytesOfSizeInNetworkOrder); | |||
| byteCollector.addBytes(plainTextBytes); | |||
| byteCollector.addBytes(appIdBytes); | |||
| // ... + pad: 使用自定义的填充方式对明文进行补位填充 | |||
| byte[] padBytes = PKCS7Encoder.encode(byteCollector.size()); | |||
| byteCollector.addBytes(padBytes); | |||
| // 获得最终的字节流, 未加密 | |||
| byte[] unencrypted = byteCollector.toBytes(); | |||
| try { | |||
| // 设置加密模式为AES的CBC模式 | |||
| Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | |||
| SecretKeySpec keySpec = new SecretKeySpec(this.aesKey, "AES"); | |||
| IvParameterSpec iv = new IvParameterSpec(this.aesKey, 0, 16); | |||
| cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv); | |||
| // 加密 | |||
| byte[] encrypted = cipher.doFinal(unencrypted); | |||
| // 使用BASE64对加密后的字符串进行编码 | |||
| String base64Encrypted = base64.encodeToString(encrypted); | |||
| return base64Encrypted; | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| } | |||
| /** | |||
| * 检验消息的真实性,并且获取解密后的明文. | |||
| * <ol> | |||
| * <li>利用收到的密文生成安全签名,进行签名验证</li> | |||
| * <li>若验证通过,则提取xml中的加密消息</li> | |||
| * <li>对消息进行解密</li> | |||
| * </ol> | |||
| * | |||
| * @param msgSignature 签名串,对应URL参数的msg_signature | |||
| * @param timeStamp 时间戳,对应URL参数的timestamp | |||
| * @param nonce 随机串,对应URL参数的nonce | |||
| * @param encryptedXml 密文,对应POST请求的数据 | |||
| * @return 解密后的原文 | |||
| */ | |||
| public String decrypt(String msgSignature, String timeStamp, String nonce, | |||
| String encryptedXml) { | |||
| // 密钥,公众账号的app corpSecret | |||
| // 提取密文 | |||
| String cipherText = extractEncryptPart(encryptedXml); | |||
| // 验证安全签名 | |||
| String signature = SHA1.gen(this.token, timeStamp, nonce, cipherText); | |||
| if (!signature.equals(msgSignature)) { | |||
| throw new RuntimeException("加密消息签名校验失败"); | |||
| } | |||
| // 解密 | |||
| String result = decrypt(cipherText); | |||
| return result; | |||
| } | |||
| /** | |||
| * 对密文进行解密. | |||
| * | |||
| * @param cipherText 需要解密的密文 | |||
| * @return 解密得到的明文 | |||
| */ | |||
| public String decrypt(String cipherText) { | |||
| byte[] original; | |||
| try { | |||
| // 设置解密模式为AES的CBC模式 | |||
| Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | |||
| SecretKeySpec key_spec = new SecretKeySpec(this.aesKey, "AES"); | |||
| IvParameterSpec iv = new IvParameterSpec( | |||
| Arrays.copyOfRange(this.aesKey, 0, 16)); | |||
| cipher.init(Cipher.DECRYPT_MODE, key_spec, iv); | |||
| // 使用BASE64对密文进行解码 | |||
| byte[] encrypted = Base64.decodeBase64(cipherText); | |||
| // 解密 | |||
| original = cipher.doFinal(encrypted); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| String xmlContent, from_appid; | |||
| try { | |||
| // 去除补位字符 | |||
| byte[] bytes = PKCS7Encoder.decode(original); | |||
| // 分离16位随机字符串,网络字节序和AppId | |||
| byte[] networkOrder = Arrays.copyOfRange(bytes, 16, 20); | |||
| int xmlLength = bytesNetworkOrder2Number(networkOrder); | |||
| xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), | |||
| CHARSET); | |||
| from_appid = new String( | |||
| Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET); | |||
| } catch (Exception e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| // appid不相同的情况 | |||
| if (!from_appid.equals(this.appidOrCorpid)) { | |||
| throw new RuntimeException("AppID不正确"); | |||
| } | |||
| return xmlContent; | |||
| } | |||
| /** | |||
| * 将一个数字转换成生成4个字节的网络字节序bytes数组 | |||
| * | |||
| * @param number | |||
| */ | |||
| private static byte[] number2BytesInNetworkOrder(int number) { | |||
| byte[] orderBytes = new byte[4]; | |||
| orderBytes[3] = (byte) (number & 0xFF); | |||
| orderBytes[2] = (byte) (number >> 8 & 0xFF); | |||
| orderBytes[1] = (byte) (number >> 16 & 0xFF); | |||
| orderBytes[0] = (byte) (number >> 24 & 0xFF); | |||
| return orderBytes; | |||
| } | |||
| /** | |||
| * 4个字节的网络字节序bytes数组还原成一个数字 | |||
| * | |||
| * @param bytesInNetworkOrder | |||
| */ | |||
| private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) { | |||
| int sourceNumber = 0; | |||
| for (int i = 0; i < 4; i++) { | |||
| sourceNumber <<= 8; | |||
| sourceNumber |= bytesInNetworkOrder[i] & 0xff; | |||
| } | |||
| return sourceNumber; | |||
| } | |||
| /** | |||
| * 随机生成16位字符串 | |||
| */ | |||
| private static String genRandomStr() { | |||
| String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | |||
| Random random = new Random(); | |||
| StringBuffer sb = new StringBuffer(); | |||
| for (int i = 0; i < 16; i++) { | |||
| int number = random.nextInt(base.length()); | |||
| sb.append(base.charAt(number)); | |||
| } | |||
| return sb.toString(); | |||
| } | |||
| /** | |||
| * 生成xml消息 | |||
| * | |||
| * @param encrypt 加密后的消息密文 | |||
| * @param signature 安全签名 | |||
| * @param timestamp 时间戳 | |||
| * @param nonce 随机字符串 | |||
| * @return 生成的xml字符串 | |||
| */ | |||
| private static String generateXml(String encrypt, String signature, | |||
| String timestamp, String nonce) { | |||
| String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n" | |||
| + "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n" | |||
| + "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n" | |||
| + "</xml>"; | |||
| return String.format(format, encrypt, signature, timestamp, nonce); | |||
| } | |||
| } | |||
| @@ -1,8 +1,6 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import java.io.IOException; | |||
| import java.util.concurrent.TimeUnit; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.http.annotation.NotThreadSafe; | |||
| import org.apache.http.auth.AuthScope; | |||
| import org.apache.http.auth.UsernamePasswordCredentials; | |||
| @@ -22,15 +20,22 @@ import org.apache.http.impl.client.HttpClientBuilder; | |||
| import org.apache.http.impl.client.HttpClients; | |||
| import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; | |||
| import org.apache.http.protocol.HttpContext; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import me.chanjar.weixin.common.util.StringUtils; | |||
| import java.io.IOException; | |||
| import java.util.concurrent.TimeUnit; | |||
| import java.util.concurrent.atomic.AtomicBoolean; | |||
| /** | |||
| * httpclient 连接管理器 | |||
| * | |||
| * @author kakotor | |||
| */ | |||
| @NotThreadSafe | |||
| public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { | |||
| protected final Logger log = LoggerFactory.getLogger(DefaultApacheHttpClientBuilder.class); | |||
| private final AtomicBoolean prepared = new AtomicBoolean(false); | |||
| private int connectionRequestTimeout = 3000; | |||
| private int connectionTimeout = 5000; | |||
| private int soTimeout = 5000; | |||
| @@ -47,21 +52,17 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { | |||
| }; | |||
| private SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory(); | |||
| private PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); | |||
| private String httpProxyHost; | |||
| private int httpProxyPort; | |||
| private String httpProxyUsername; | |||
| private String httpProxyPassword; | |||
| /** | |||
| * 闲置连接监控线程 | |||
| */ | |||
| private IdleConnectionMonitorThread idleConnectionMonitorThread; | |||
| private HttpClientBuilder httpClientBuilder; | |||
| private DefaultApacheHttpClientBuilder() { | |||
| prepare(); | |||
| } | |||
| public static DefaultApacheHttpClientBuilder get() { | |||
| @@ -98,11 +99,98 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { | |||
| return this; | |||
| } | |||
| /** | |||
| * 获取链接的超时时间设置,默认3000ms | |||
| * <p> | |||
| * 设置为零时不超时,一直等待. | |||
| * 设置为负数是使用系统默认设置(非上述的3000ms的默认值,而是httpclient的默认设置). | |||
| * </p> | |||
| * | |||
| * @param connectionRequestTimeout 获取链接的超时时间设置(单位毫秒),默认3000ms | |||
| */ | |||
| public void setConnectionRequestTimeout(int connectionRequestTimeout) { | |||
| this.connectionRequestTimeout = connectionRequestTimeout; | |||
| } | |||
| /** | |||
| * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用 | |||
| * <p> | |||
| * 设置为零时不超时,一直等待. | |||
| * 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置). | |||
| * </p> | |||
| * | |||
| * @param connectionTimeout 建立链接的超时时间设置(单位毫秒),默认5000ms | |||
| */ | |||
| public void setConnectionTimeout(int connectionTimeout) { | |||
| this.connectionTimeout = connectionTimeout; | |||
| } | |||
| /** | |||
| * 默认NIO的socket超时设置,默认5000ms. | |||
| * | |||
| * @param soTimeout 默认NIO的socket超时设置,默认5000ms. | |||
| * @see java.net.SocketOptions#SO_TIMEOUT | |||
| */ | |||
| public void setSoTimeout(int soTimeout) { | |||
| this.soTimeout = soTimeout; | |||
| } | |||
| /** | |||
| * 空闲链接的超时时间,默认60000ms. | |||
| * <p> | |||
| * 超时的链接将在下一次空闲链接检查是被销毁 | |||
| * </p> | |||
| * | |||
| * @param idleConnTimeout 空闲链接的超时时间,默认60000ms. | |||
| */ | |||
| public void setIdleConnTimeout(int idleConnTimeout) { | |||
| this.idleConnTimeout = idleConnTimeout; | |||
| } | |||
| /** | |||
| * 检查空间链接的间隔周期,默认60000ms. | |||
| * | |||
| * @param checkWaitTime 检查空间链接的间隔周期,默认60000ms. | |||
| */ | |||
| public void setCheckWaitTime(int checkWaitTime) { | |||
| this.checkWaitTime = checkWaitTime; | |||
| } | |||
| /** | |||
| * 每路的最大链接数,默认10 | |||
| * | |||
| * @param maxConnPerHost 每路的最大链接数,默认10 | |||
| */ | |||
| public void setMaxConnPerHost(int maxConnPerHost) { | |||
| this.maxConnPerHost = maxConnPerHost; | |||
| } | |||
| /** | |||
| * 最大总连接数,默认50 | |||
| * | |||
| * @param maxTotalConn 最大总连接数,默认50 | |||
| */ | |||
| public void setMaxTotalConn(int maxTotalConn) { | |||
| this.maxTotalConn = maxTotalConn; | |||
| } | |||
| /** | |||
| * 自定义httpclient的User Agent | |||
| * | |||
| * @param userAgent User Agent | |||
| */ | |||
| public void setUserAgent(String userAgent) { | |||
| this.userAgent = userAgent; | |||
| } | |||
| public IdleConnectionMonitorThread getIdleConnectionMonitorThread() { | |||
| return this.idleConnectionMonitorThread; | |||
| } | |||
| private void prepare() { | |||
| private synchronized void prepare() { | |||
| if(prepared.get()){ | |||
| return; | |||
| } | |||
| Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create() | |||
| .register("http", this.plainConnectionSocketFactory) | |||
| .register("https", this.sslConnectionSocketFactory) | |||
| @@ -149,11 +237,14 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder { | |||
| if (StringUtils.isNotBlank(this.userAgent)) { | |||
| this.httpClientBuilder.setUserAgent(this.userAgent); | |||
| } | |||
| prepared.set(true); | |||
| } | |||
| @Override | |||
| public CloseableHttpClient build() { | |||
| if(!prepared.get()){ | |||
| prepare(); | |||
| } | |||
| return this.httpClientBuilder.build(); | |||
| } | |||
| @@ -1,55 +0,0 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.impl.client.CloseableHttpClient; | |||
| import jodd.http.HttpRequest; | |||
| import jodd.http.HttpResponse; | |||
| import jodd.http.ProxyInfo; | |||
| import jodd.http.net.SocketHttpConnectionProvider; | |||
| import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| /** | |||
| * 简单的GET请求执行器,请求的参数是String, 返回的结果也是String | |||
| * | |||
| * @author Daniel Qian | |||
| */ | |||
| public class JoddGetRequestExecutor implements RequestExecutor<String, String> { | |||
| @Override | |||
| public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, | |||
| String queryParam) throws WxErrorException { | |||
| if (queryParam != null) { | |||
| if (uri.indexOf('?') == -1) { | |||
| uri += '?'; | |||
| } | |||
| uri += uri.endsWith("?") ? queryParam : '&' + queryParam; | |||
| } | |||
| SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider(); | |||
| if (httpProxy != null) { | |||
| ProxyInfo proxyInfoObj = new ProxyInfo( | |||
| ProxyInfo.ProxyType.HTTP, | |||
| httpProxy.getHostName(), | |||
| httpProxy.getPort(), "", ""); | |||
| provider.useProxy(proxyInfoObj); | |||
| } | |||
| HttpRequest request = HttpRequest.get(uri); | |||
| request.method("GET"); | |||
| request.charset("UTF-8"); | |||
| HttpResponse response = request.open(provider).send(); | |||
| response.charset("UTF-8"); | |||
| String result = response.bodyText(); | |||
| WxError error = WxError.fromJson(result); | |||
| if (error.getErrorCode() != 0) { | |||
| throw new WxErrorException(error); | |||
| } | |||
| return result; | |||
| } | |||
| } | |||
| @@ -1,49 +0,0 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.impl.client.CloseableHttpClient; | |||
| import jodd.http.HttpRequest; | |||
| import jodd.http.HttpResponse; | |||
| import jodd.http.ProxyInfo; | |||
| import jodd.http.net.SocketHttpConnectionProvider; | |||
| import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| /** | |||
| * 简单的POST请求执行器,请求的参数是String, 返回的结果也是String | |||
| * | |||
| * @author Edison Guo | |||
| */ | |||
| public class JoddPostRequestExecutor implements RequestExecutor<String, String> { | |||
| @Override | |||
| public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, | |||
| String postEntity) throws WxErrorException { | |||
| SocketHttpConnectionProvider provider = new SocketHttpConnectionProvider(); | |||
| if (httpProxy != null) { | |||
| ProxyInfo proxyInfoObj = new ProxyInfo( | |||
| ProxyInfo.ProxyType.HTTP, | |||
| httpProxy.getAddress().getHostAddress(), | |||
| httpProxy.getPort(), "", ""); | |||
| provider.useProxy(proxyInfoObj); | |||
| } | |||
| HttpRequest request = HttpRequest.get(uri); | |||
| request.method("POST"); | |||
| request.charset("UTF-8"); | |||
| request.bodyText(postEntity); | |||
| HttpResponse response = request.open(provider).send(); | |||
| response.charset("UTF-8"); | |||
| String result = response.bodyText(); | |||
| WxError error = WxError.fromJson(result); | |||
| if (error.getErrorCode() != 0) { | |||
| throw new WxErrorException(error); | |||
| } | |||
| return result; | |||
| } | |||
| } | |||
| @@ -2,8 +2,8 @@ package me.chanjar.weixin.common.util.http; | |||
| import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.util.StringUtils; | |||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.http.Header; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.client.config.RequestConfig; | |||
| @@ -1,6 +1,7 @@ | |||
| package me.chanjar.weixin.common.util.http; | |||
| import me.chanjar.weixin.common.util.StringUtils; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import java.io.UnsupportedEncodingException; | |||
| @@ -1,103 +1,103 @@ | |||
| package me.chanjar.weixin.common.util.crypto; | |||
| import org.testng.annotations.Test; | |||
| import org.w3c.dom.Document; | |||
| import org.w3c.dom.Element; | |||
| import org.w3c.dom.NodeList; | |||
| import org.xml.sax.InputSource; | |||
| import org.xml.sax.SAXException; | |||
| import javax.xml.parsers.DocumentBuilder; | |||
| import javax.xml.parsers.DocumentBuilderFactory; | |||
| import javax.xml.parsers.ParserConfigurationException; | |||
| import java.io.IOException; | |||
| import java.io.StringReader; | |||
| import static org.testng.Assert.assertEquals; | |||
| import static org.testng.Assert.fail; | |||
| @Test | |||
| public class WxCryptUtilTest { | |||
| String encodingAesKey = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG"; | |||
| String token = "pamtest"; | |||
| String timestamp = "1409304348"; | |||
| String nonce = "xxxxxx"; | |||
| String appId = "wxb11529c136998cb6"; | |||
| String randomStr = "aaaabbbbccccdddd"; | |||
| String xmlFormat = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>"; | |||
| String replyMsg = "我是中文abcd123"; | |||
| String afterAesEncrypt = "jn1L23DB+6ELqJ+6bruv21Y6MD7KeIfP82D6gU39rmkgczbWwt5+3bnyg5K55bgVtVzd832WzZGMhkP72vVOfg=="; | |||
| String replyMsg2 = "<xml><ToUserName><![CDATA[oia2Tj我是中文jewbmiOUlr6X-1crbLOvLw]]></ToUserName><FromUserName><![CDATA[gh_7f083739789a]]></FromUserName><CreateTime>1407743423</CreateTime><MsgType><![CDATA[video]]></MsgType><Video><MediaId><![CDATA[eYJ1MbwPRJtOvIEabaxHs7TX2D-HV71s79GUxqdUkjm6Gs2Ed1KF3ulAOA9H1xG0]]></MediaId><Title><![CDATA[testCallBackReplyVideo]]></Title><Description><![CDATA[testCallBackReplyVideo]]></Description></Video></xml>"; | |||
| String afterAesEncrypt2 = "jn1L23DB+6ELqJ+6bruv23M2GmYfkv0xBh2h+XTBOKVKcgDFHle6gqcZ1cZrk3e1qjPQ1F4RsLWzQRG9udbKWesxlkupqcEcW7ZQweImX9+wLMa0GaUzpkycA8+IamDBxn5loLgZpnS7fVAbExOkK5DYHBmv5tptA9tklE/fTIILHR8HLXa5nQvFb3tYPKAlHF3rtTeayNf0QuM+UW/wM9enGIDIJHF7CLHiDNAYxr+r+OrJCmPQyTy8cVWlu9iSvOHPT/77bZqJucQHQ04sq7KZI27OcqpQNSto2OdHCoTccjggX5Z9Mma0nMJBU+jLKJ38YB1fBIz+vBzsYjrTmFQ44YfeEuZ+xRTQwr92vhA9OxchWVINGC50qE/6lmkwWTwGX9wtQpsJKhP+oS7rvTY8+VdzETdfakjkwQ5/Xka042OlUb1/slTwo4RscuQ+RdxSGvDahxAJ6+EAjLt9d8igHngxIbf6YyqqROxuxqIeIch3CssH/LqRs+iAcILvApYZckqmA7FNERspKA5f8GoJ9sv8xmGvZ9Yrf57cExWtnX8aCMMaBropU/1k+hKP5LVdzbWCG0hGwx/dQudYR/eXp3P0XxjlFiy+9DMlaFExWUZQDajPkdPrEeOwofJb"; | |||
| public void testNormal() throws ParserConfigurationException, SAXException, IOException { | |||
| WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | |||
| String encryptedXml = pc.encrypt(this.replyMsg); | |||
| System.out.println(encryptedXml); | |||
| DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||
| DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||
| Document document = documentBuilder.parse(new InputSource(new StringReader(encryptedXml))); | |||
| Element root = document.getDocumentElement(); | |||
| String cipherText = root.getElementsByTagName("Encrypt").item(0).getTextContent(); | |||
| System.out.println(cipherText); | |||
| String msgSignature = root.getElementsByTagName("MsgSignature").item(0).getTextContent(); | |||
| System.out.println(msgSignature); | |||
| String timestamp = root.getElementsByTagName("TimeStamp").item(0).getTextContent(); | |||
| System.out.println(timestamp); | |||
| String nonce = root.getElementsByTagName("Nonce").item(0).getTextContent(); | |||
| System.out.println(nonce); | |||
| String messageText = String.format(this.xmlFormat, cipherText); | |||
| System.out.println(messageText); | |||
| // 第三方收到企业号平台发送的消息 | |||
| String plainMessage = pc.decrypt(cipherText); | |||
| System.out.println(plainMessage); | |||
| assertEquals(plainMessage, this.replyMsg); | |||
| } | |||
| public void testAesEncrypt() { | |||
| WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | |||
| assertEquals(pc.encrypt(this.randomStr, this.replyMsg), this.afterAesEncrypt); | |||
| } | |||
| public void testAesEncrypt2() { | |||
| WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | |||
| assertEquals(pc.encrypt(this.randomStr, this.replyMsg2), this.afterAesEncrypt2); | |||
| } | |||
| public void testValidateSignatureError() throws ParserConfigurationException, SAXException, | |||
| IOException { | |||
| try { | |||
| WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | |||
| String afterEncrpt = pc.encrypt(this.replyMsg); | |||
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); | |||
| DocumentBuilder db = dbf.newDocumentBuilder(); | |||
| StringReader sr = new StringReader(afterEncrpt); | |||
| InputSource is = new InputSource(sr); | |||
| Document document = db.parse(is); | |||
| Element root = document.getDocumentElement(); | |||
| NodeList nodelist1 = root.getElementsByTagName("Encrypt"); | |||
| String encrypt = nodelist1.item(0).getTextContent(); | |||
| String fromXML = String.format(this.xmlFormat, encrypt); | |||
| pc.decrypt("12345", this.timestamp, this.nonce, fromXML); // 这里签名错误 | |||
| } catch (RuntimeException e) { | |||
| assertEquals(e.getMessage(), "加密消息签名校验失败"); | |||
| return; | |||
| } | |||
| fail("错误流程不抛出异常???"); | |||
| } | |||
| } | |||
| package me.chanjar.weixin.common.util.crypto; | |||
| import org.testng.annotations.Test; | |||
| import org.w3c.dom.Document; | |||
| import org.w3c.dom.Element; | |||
| import org.w3c.dom.NodeList; | |||
| import org.xml.sax.InputSource; | |||
| import org.xml.sax.SAXException; | |||
| import javax.xml.parsers.DocumentBuilder; | |||
| import javax.xml.parsers.DocumentBuilderFactory; | |||
| import javax.xml.parsers.ParserConfigurationException; | |||
| import java.io.IOException; | |||
| import java.io.StringReader; | |||
| import static org.testng.Assert.assertEquals; | |||
| import static org.testng.Assert.fail; | |||
| @Test | |||
| public class WxCryptUtilTest { | |||
| String encodingAesKey = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG"; | |||
| String token = "pamtest"; | |||
| String timestamp = "1409304348"; | |||
| String nonce = "xxxxxx"; | |||
| String appId = "wxb11529c136998cb6"; | |||
| String randomStr = "aaaabbbbccccdddd"; | |||
| String xmlFormat = "<xml><ToUserName><![CDATA[toUser]]></ToUserName><Encrypt><![CDATA[%1$s]]></Encrypt></xml>"; | |||
| String replyMsg = "我是中文abcd123"; | |||
| String afterAesEncrypt = "jn1L23DB+6ELqJ+6bruv21Y6MD7KeIfP82D6gU39rmkgczbWwt5+3bnyg5K55bgVtVzd832WzZGMhkP72vVOfg=="; | |||
| String replyMsg2 = "<xml><ToUserName><![CDATA[oia2Tj我是中文jewbmiOUlr6X-1crbLOvLw]]></ToUserName><FromUserName><![CDATA[gh_7f083739789a]]></FromUserName><CreateTime>1407743423</CreateTime><MsgType><![CDATA[video]]></MsgType><Video><MediaId><![CDATA[eYJ1MbwPRJtOvIEabaxHs7TX2D-HV71s79GUxqdUkjm6Gs2Ed1KF3ulAOA9H1xG0]]></MediaId><Title><![CDATA[testCallBackReplyVideo]]></Title><Description><![CDATA[testCallBackReplyVideo]]></Description></Video></xml>"; | |||
| String afterAesEncrypt2 = "jn1L23DB+6ELqJ+6bruv23M2GmYfkv0xBh2h+XTBOKVKcgDFHle6gqcZ1cZrk3e1qjPQ1F4RsLWzQRG9udbKWesxlkupqcEcW7ZQweImX9+wLMa0GaUzpkycA8+IamDBxn5loLgZpnS7fVAbExOkK5DYHBmv5tptA9tklE/fTIILHR8HLXa5nQvFb3tYPKAlHF3rtTeayNf0QuM+UW/wM9enGIDIJHF7CLHiDNAYxr+r+OrJCmPQyTy8cVWlu9iSvOHPT/77bZqJucQHQ04sq7KZI27OcqpQNSto2OdHCoTccjggX5Z9Mma0nMJBU+jLKJ38YB1fBIz+vBzsYjrTmFQ44YfeEuZ+xRTQwr92vhA9OxchWVINGC50qE/6lmkwWTwGX9wtQpsJKhP+oS7rvTY8+VdzETdfakjkwQ5/Xka042OlUb1/slTwo4RscuQ+RdxSGvDahxAJ6+EAjLt9d8igHngxIbf6YyqqROxuxqIeIch3CssH/LqRs+iAcILvApYZckqmA7FNERspKA5f8GoJ9sv8xmGvZ9Yrf57cExWtnX8aCMMaBropU/1k+hKP5LVdzbWCG0hGwx/dQudYR/eXp3P0XxjlFiy+9DMlaFExWUZQDajPkdPrEeOwofJb"; | |||
| public void testNormal() throws ParserConfigurationException, SAXException, IOException { | |||
| WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | |||
| String encryptedXml = pc.encrypt(this.replyMsg); | |||
| System.out.println(encryptedXml); | |||
| DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||
| DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||
| Document document = documentBuilder.parse(new InputSource(new StringReader(encryptedXml))); | |||
| Element root = document.getDocumentElement(); | |||
| String cipherText = root.getElementsByTagName("Encrypt").item(0).getTextContent(); | |||
| System.out.println(cipherText); | |||
| String msgSignature = root.getElementsByTagName("MsgSignature").item(0).getTextContent(); | |||
| System.out.println(msgSignature); | |||
| String timestamp = root.getElementsByTagName("TimeStamp").item(0).getTextContent(); | |||
| System.out.println(timestamp); | |||
| String nonce = root.getElementsByTagName("Nonce").item(0).getTextContent(); | |||
| System.out.println(nonce); | |||
| String messageText = String.format(this.xmlFormat, cipherText); | |||
| System.out.println(messageText); | |||
| // 第三方收到企业号平台发送的消息 | |||
| String plainMessage = pc.decrypt(cipherText); | |||
| System.out.println(plainMessage); | |||
| assertEquals(plainMessage, this.replyMsg); | |||
| } | |||
| public void testAesEncrypt() { | |||
| WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | |||
| assertEquals(pc.encrypt(this.randomStr, this.replyMsg), this.afterAesEncrypt); | |||
| } | |||
| public void testAesEncrypt2() { | |||
| WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | |||
| assertEquals(pc.encrypt(this.randomStr, this.replyMsg2), this.afterAesEncrypt2); | |||
| } | |||
| public void testValidateSignatureError() throws ParserConfigurationException, SAXException, | |||
| IOException { | |||
| try { | |||
| WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId); | |||
| String afterEncrpt = pc.encrypt(this.replyMsg); | |||
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); | |||
| DocumentBuilder db = dbf.newDocumentBuilder(); | |||
| StringReader sr = new StringReader(afterEncrpt); | |||
| InputSource is = new InputSource(sr); | |||
| Document document = db.parse(is); | |||
| Element root = document.getDocumentElement(); | |||
| NodeList nodelist1 = root.getElementsByTagName("Encrypt"); | |||
| String encrypt = nodelist1.item(0).getTextContent(); | |||
| String fromXML = String.format(this.xmlFormat, encrypt); | |||
| pc.decrypt("12345", this.timestamp, this.nonce, fromXML); // 这里签名错误 | |||
| } catch (RuntimeException e) { | |||
| assertEquals(e.getMessage(), "加密消息签名校验失败"); | |||
| return; | |||
| } | |||
| fail("错误流程不抛出异常???"); | |||
| } | |||
| } | |||
| @@ -6,7 +6,7 @@ dependencies { | |||
| testCompile group: 'org.testng', name: 'testng', version:'6.8.7' | |||
| testCompile group: 'org.mockito', name: 'mockito-all', version:'1.9.5' | |||
| testCompile group: 'com.google.inject', name: 'guice', version:'3.0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.M0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.M0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.RC0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.RC0' | |||
| } | |||
| test.useTestNG() | |||
| @@ -6,7 +6,7 @@ | |||
| <parent> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>2.3.0-SNAPSHOT</version> | |||
| <version>2.4.0-SNAPSHOT</version> | |||
| </parent> | |||
| <artifactId>weixin-java-cp</artifactId> | |||
| @@ -45,7 +45,7 @@ public interface WxCpConfigStorage { | |||
| String getCorpSecret(); | |||
| String getAgentId(); | |||
| Integer getAgentId(); | |||
| String getToken(); | |||
| @@ -1,13 +1,11 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import java.io.File; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||
| import java.io.File; | |||
| /** | |||
| * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||
| * | |||
| @@ -21,7 +19,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
| protected volatile String token; | |||
| protected volatile String accessToken; | |||
| protected volatile String aesKey; | |||
| protected volatile String agentId; | |||
| protected volatile Integer agentId; | |||
| protected volatile long expiresTime; | |||
| protected volatile String oauth2redirectUri; | |||
| @@ -148,11 +146,11 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
| } | |||
| @Override | |||
| public String getAgentId() { | |||
| public Integer getAgentId() { | |||
| return this.agentId; | |||
| } | |||
| public void setAgentId(String agentId) { | |||
| public void setAgentId(Integer agentId) { | |||
| this.agentId = agentId; | |||
| } | |||
| @@ -203,7 +201,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| @Override | |||
| @@ -1,267 +1,259 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import java.io.File; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||
| import redis.clients.jedis.Jedis; | |||
| import redis.clients.jedis.JedisPool; | |||
| /** | |||
| * Jedis client implementor for wechat config storage | |||
| * | |||
| * @author gaigeshen | |||
| */ | |||
| public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||
| /* Redis keys here */ | |||
| private static final String ACCESS_TOKEN_KEY = "WX_CP_ACCESS_TOKEN"; | |||
| private static final String ACCESS_TOKEN_EXPIRES_TIME_KEY = "WX_CP_ACCESS_TOKEN_EXPIRES_TIME"; | |||
| private static final String JS_API_TICKET_KEY = "WX_CP_JS_API_TICKET"; | |||
| private static final String JS_API_TICKET_EXPIRES_TIME_KEY = "WX_CP_JS_API_TICKET_EXPIRES_TIME"; | |||
| private volatile String corpId; | |||
| private volatile String corpSecret; | |||
| private volatile String token; | |||
| private volatile String aesKey; | |||
| private volatile String agentId; | |||
| private volatile String oauth2redirectUri; | |||
| private volatile String httpProxyHost; | |||
| private volatile int httpProxyPort; | |||
| private volatile String httpProxyUsername; | |||
| private volatile String httpProxyPassword; | |||
| private volatile File tmpDirFile; | |||
| private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; | |||
| /* Redis clients pool */ | |||
| private final JedisPool jedisPool; | |||
| public WxCpJedisConfigStorage(String host, int port) { | |||
| this.jedisPool = new JedisPool(host, port); | |||
| } | |||
| /** | |||
| * | |||
| * This method will be destroy jedis pool | |||
| */ | |||
| public void destroy() { | |||
| this.jedisPool.destroy(); | |||
| } | |||
| @Override | |||
| public String getAccessToken() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| return jedis.get(ACCESS_TOKEN_KEY); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean isAccessTokenExpired() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY); | |||
| if (expiresTimeStr != null) { | |||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||
| return System.currentTimeMillis() > expiresTime; | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| @Override | |||
| public void expireAccessToken() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(ACCESS_TOKEN_EXPIRES_TIME_KEY, "0"); | |||
| } | |||
| } | |||
| @Override | |||
| public synchronized void updateAccessToken(WxAccessToken accessToken) { | |||
| this.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
| } | |||
| @Override | |||
| public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(ACCESS_TOKEN_KEY, accessToken); | |||
| jedis.set(ACCESS_TOKEN_EXPIRES_TIME_KEY, | |||
| (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L) + ""); | |||
| } | |||
| } | |||
| @Override | |||
| public String getJsapiTicket() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| return jedis.get(JS_API_TICKET_KEY); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean isJsapiTicketExpired() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| String expiresTimeStr = jedis.get(JS_API_TICKET_EXPIRES_TIME_KEY); | |||
| if (expiresTimeStr != null) { | |||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||
| return System.currentTimeMillis() > expiresTime; | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| @Override | |||
| public void expireJsapiTicket() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY, "0"); | |||
| } | |||
| } | |||
| @Override | |||
| public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(JS_API_TICKET_KEY, jsapiTicket); | |||
| jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY, | |||
| (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + "")); | |||
| } | |||
| } | |||
| @Override | |||
| public String getCorpId() { | |||
| return this.corpId; | |||
| } | |||
| @Override | |||
| public String getCorpSecret() { | |||
| return this.corpSecret; | |||
| } | |||
| @Override | |||
| public String getAgentId() { | |||
| return this.agentId; | |||
| } | |||
| @Override | |||
| public String getToken() { | |||
| return this.token; | |||
| } | |||
| @Override | |||
| public String getAesKey() { | |||
| return this.aesKey; | |||
| } | |||
| @Override | |||
| public long getExpiresTime() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY); | |||
| if (expiresTimeStr != null) { | |||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||
| return expiresTime; | |||
| } | |||
| return 0L; | |||
| } | |||
| } | |||
| @Override | |||
| public String getOauth2redirectUri() { | |||
| return this.oauth2redirectUri; | |||
| } | |||
| @Override | |||
| public String getHttpProxyHost() { | |||
| return this.httpProxyHost; | |||
| } | |||
| @Override | |||
| public int getHttpProxyPort() { | |||
| return this.httpProxyPort; | |||
| } | |||
| @Override | |||
| public String getHttpProxyUsername() { | |||
| return this.httpProxyUsername; | |||
| } | |||
| @Override | |||
| public String getHttpProxyPassword() { | |||
| return this.httpProxyPassword; | |||
| } | |||
| @Override | |||
| public File getTmpDirFile() { | |||
| return this.tmpDirFile; | |||
| } | |||
| @Override | |||
| public ApacheHttpClientBuilder getApacheHttpClientBuilder() { | |||
| return this.apacheHttpClientBuilder; | |||
| } | |||
| public void setCorpId(String corpId) { | |||
| this.corpId = corpId; | |||
| } | |||
| public void setCorpSecret(String corpSecret) { | |||
| this.corpSecret = corpSecret; | |||
| } | |||
| public void setToken(String token) { | |||
| this.token = token; | |||
| } | |||
| public void setAesKey(String aesKey) { | |||
| this.aesKey = aesKey; | |||
| } | |||
| public void setAgentId(String agentId) { | |||
| this.agentId = agentId; | |||
| } | |||
| // ============================ Setters below | |||
| public void setOauth2redirectUri(String oauth2redirectUri) { | |||
| this.oauth2redirectUri = oauth2redirectUri; | |||
| } | |||
| public void setHttpProxyHost(String httpProxyHost) { | |||
| this.httpProxyHost = httpProxyHost; | |||
| } | |||
| public void setHttpProxyPort(int httpProxyPort) { | |||
| this.httpProxyPort = httpProxyPort; | |||
| } | |||
| public void setHttpProxyUsername(String httpProxyUsername) { | |||
| this.httpProxyUsername = httpProxyUsername; | |||
| } | |||
| public void setHttpProxyPassword(String httpProxyPassword) { | |||
| this.httpProxyPassword = httpProxyPassword; | |||
| } | |||
| public void setTmpDirFile(File tmpDirFile) { | |||
| this.tmpDirFile = tmpDirFile; | |||
| } | |||
| public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) { | |||
| this.apacheHttpClientBuilder = apacheHttpClientBuilder; | |||
| } | |||
| } | |||
| package me.chanjar.weixin.cp.api; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||
| import redis.clients.jedis.Jedis; | |||
| import redis.clients.jedis.JedisPool; | |||
| import java.io.File; | |||
| /** | |||
| * Jedis client implementor for wechat config storage | |||
| * | |||
| * @author gaigeshen | |||
| */ | |||
| public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||
| /* Redis keys here */ | |||
| private static final String ACCESS_TOKEN_KEY = "WX_CP_ACCESS_TOKEN"; | |||
| private static final String ACCESS_TOKEN_EXPIRES_TIME_KEY = "WX_CP_ACCESS_TOKEN_EXPIRES_TIME"; | |||
| private static final String JS_API_TICKET_KEY = "WX_CP_JS_API_TICKET"; | |||
| private static final String JS_API_TICKET_EXPIRES_TIME_KEY = "WX_CP_JS_API_TICKET_EXPIRES_TIME"; | |||
| /* Redis clients pool */ | |||
| private final JedisPool jedisPool; | |||
| private volatile String corpId; | |||
| private volatile String corpSecret; | |||
| private volatile String token; | |||
| private volatile String aesKey; | |||
| private volatile Integer agentId; | |||
| private volatile String oauth2redirectUri; | |||
| private volatile String httpProxyHost; | |||
| private volatile int httpProxyPort; | |||
| private volatile String httpProxyUsername; | |||
| private volatile String httpProxyPassword; | |||
| private volatile File tmpDirFile; | |||
| private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; | |||
| public WxCpJedisConfigStorage(String host, int port) { | |||
| this.jedisPool = new JedisPool(host, port); | |||
| } | |||
| /** | |||
| * This method will be destroy jedis pool | |||
| */ | |||
| public void destroy() { | |||
| this.jedisPool.destroy(); | |||
| } | |||
| @Override | |||
| public String getAccessToken() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| return jedis.get(ACCESS_TOKEN_KEY); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean isAccessTokenExpired() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY); | |||
| if (expiresTimeStr != null) { | |||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||
| return System.currentTimeMillis() > expiresTime; | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| @Override | |||
| public void expireAccessToken() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(ACCESS_TOKEN_EXPIRES_TIME_KEY, "0"); | |||
| } | |||
| } | |||
| @Override | |||
| public synchronized void updateAccessToken(WxAccessToken accessToken) { | |||
| this.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
| } | |||
| @Override | |||
| public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(ACCESS_TOKEN_KEY, accessToken); | |||
| jedis.set(ACCESS_TOKEN_EXPIRES_TIME_KEY, | |||
| (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L) + ""); | |||
| } | |||
| } | |||
| @Override | |||
| public String getJsapiTicket() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| return jedis.get(JS_API_TICKET_KEY); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean isJsapiTicketExpired() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| String expiresTimeStr = jedis.get(JS_API_TICKET_EXPIRES_TIME_KEY); | |||
| if (expiresTimeStr != null) { | |||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||
| return System.currentTimeMillis() > expiresTime; | |||
| } | |||
| return true; | |||
| } | |||
| } | |||
| @Override | |||
| public void expireJsapiTicket() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY, "0"); | |||
| } | |||
| } | |||
| @Override | |||
| public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| jedis.set(JS_API_TICKET_KEY, jsapiTicket); | |||
| jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY, | |||
| (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + "")); | |||
| } | |||
| } | |||
| @Override | |||
| public String getCorpId() { | |||
| return this.corpId; | |||
| } | |||
| public void setCorpId(String corpId) { | |||
| this.corpId = corpId; | |||
| } | |||
| @Override | |||
| public String getCorpSecret() { | |||
| return this.corpSecret; | |||
| } | |||
| public void setCorpSecret(String corpSecret) { | |||
| this.corpSecret = corpSecret; | |||
| } | |||
| @Override | |||
| public Integer getAgentId() { | |||
| return this.agentId; | |||
| } | |||
| public void setAgentId(Integer agentId) { | |||
| this.agentId = agentId; | |||
| } | |||
| @Override | |||
| public String getToken() { | |||
| return this.token; | |||
| } | |||
| public void setToken(String token) { | |||
| this.token = token; | |||
| } | |||
| @Override | |||
| public String getAesKey() { | |||
| return this.aesKey; | |||
| } | |||
| public void setAesKey(String aesKey) { | |||
| this.aesKey = aesKey; | |||
| } | |||
| @Override | |||
| public long getExpiresTime() { | |||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||
| String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY); | |||
| if (expiresTimeStr != null) { | |||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||
| return expiresTime; | |||
| } | |||
| return 0L; | |||
| } | |||
| } | |||
| @Override | |||
| public String getOauth2redirectUri() { | |||
| return this.oauth2redirectUri; | |||
| } | |||
| public void setOauth2redirectUri(String oauth2redirectUri) { | |||
| this.oauth2redirectUri = oauth2redirectUri; | |||
| } | |||
| @Override | |||
| public String getHttpProxyHost() { | |||
| return this.httpProxyHost; | |||
| } | |||
| public void setHttpProxyHost(String httpProxyHost) { | |||
| this.httpProxyHost = httpProxyHost; | |||
| } | |||
| @Override | |||
| public int getHttpProxyPort() { | |||
| return this.httpProxyPort; | |||
| } | |||
| public void setHttpProxyPort(int httpProxyPort) { | |||
| this.httpProxyPort = httpProxyPort; | |||
| } | |||
| @Override | |||
| public String getHttpProxyUsername() { | |||
| return this.httpProxyUsername; | |||
| } | |||
| // ============================ Setters below | |||
| public void setHttpProxyUsername(String httpProxyUsername) { | |||
| this.httpProxyUsername = httpProxyUsername; | |||
| } | |||
| @Override | |||
| public String getHttpProxyPassword() { | |||
| return this.httpProxyPassword; | |||
| } | |||
| public void setHttpProxyPassword(String httpProxyPassword) { | |||
| this.httpProxyPassword = httpProxyPassword; | |||
| } | |||
| @Override | |||
| public File getTmpDirFile() { | |||
| return this.tmpDirFile; | |||
| } | |||
| public void setTmpDirFile(File tmpDirFile) { | |||
| this.tmpDirFile = tmpDirFile; | |||
| } | |||
| @Override | |||
| public ApacheHttpClientBuilder getApacheHttpClientBuilder() { | |||
| return this.apacheHttpClientBuilder; | |||
| } | |||
| public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) { | |||
| this.apacheHttpClientBuilder = apacheHttpClientBuilder; | |||
| } | |||
| } | |||
| @@ -163,12 +163,12 @@ public class WxCpMessageRouter { | |||
| // 返回最后一个非异步的rule的执行结果 | |||
| if (rule.isAsync()) { | |||
| futures.add( | |||
| this.executorService.submit(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| rule.service(wxMessage, WxCpMessageRouter.this.wxCpService, WxCpMessageRouter.this.sessionManager, WxCpMessageRouter.this.exceptionHandler); | |||
| } | |||
| }) | |||
| this.executorService.submit(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| rule.service(wxMessage, WxCpMessageRouter.this.wxCpService, WxCpMessageRouter.this.sessionManager, WxCpMessageRouter.this.exceptionHandler); | |||
| } | |||
| }) | |||
| ); | |||
| } else { | |||
| res = rule.service(wxMessage, this.wxCpService, this.sessionManager, this.exceptionHandler); | |||
| @@ -205,10 +205,10 @@ public class WxCpMessageRouter { | |||
| String messageId = ""; | |||
| if (wxMessage.getMsgId() == null) { | |||
| messageId = String.valueOf(wxMessage.getCreateTime()) | |||
| + "-" + String.valueOf(wxMessage.getAgentId() == null ? "" : wxMessage.getAgentId()) | |||
| + "-" + wxMessage.getFromUserName() | |||
| + "-" + String.valueOf(wxMessage.getEventKey() == null ? "" : wxMessage.getEventKey()) | |||
| + "-" + String.valueOf(wxMessage.getEvent() == null ? "" : wxMessage.getEvent()) | |||
| + "-" + String.valueOf(wxMessage.getAgentId() == null ? "" : wxMessage.getAgentId()) | |||
| + "-" + wxMessage.getFromUserName() | |||
| + "-" + String.valueOf(wxMessage.getEventKey() == null ? "" : wxMessage.getEventKey()) | |||
| + "-" + String.valueOf(wxMessage.getEvent() == null ? "" : wxMessage.getEvent()) | |||
| ; | |||
| } else { | |||
| messageId = String.valueOf(wxMessage.getMsgId()); | |||
| @@ -202,24 +202,24 @@ public class WxCpMessageRouterRule { | |||
| protected boolean test(WxCpXmlMessage wxMessage) { | |||
| return | |||
| (this.fromUser == null || this.fromUser.equals(wxMessage.getFromUserName())) | |||
| && | |||
| (this.agentId == null || this.agentId.equals(wxMessage.getAgentId())) | |||
| && | |||
| (this.msgType == null || this.msgType.equals(wxMessage.getMsgType())) | |||
| && | |||
| (this.event == null || this.event.equals(wxMessage.getEvent())) | |||
| && | |||
| (this.eventKey == null || this.eventKey.equals(wxMessage.getEventKey())) | |||
| && | |||
| (this.content == null || this.content | |||
| .equals(wxMessage.getContent() == null ? null : wxMessage.getContent().trim())) | |||
| && | |||
| (this.rContent == null || Pattern | |||
| .matches(this.rContent, wxMessage.getContent() == null ? "" : wxMessage.getContent().trim())) | |||
| && | |||
| (this.matcher == null || this.matcher.match(wxMessage)) | |||
| ; | |||
| (this.fromUser == null || this.fromUser.equals(wxMessage.getFromUserName())) | |||
| && | |||
| (this.agentId == null || this.agentId.equals(wxMessage.getAgentId())) | |||
| && | |||
| (this.msgType == null || this.msgType.equals(wxMessage.getMsgType())) | |||
| && | |||
| (this.event == null || this.event.equals(wxMessage.getEvent())) | |||
| && | |||
| (this.eventKey == null || this.eventKey.equals(wxMessage.getEventKey())) | |||
| && | |||
| (this.content == null || this.content | |||
| .equals(wxMessage.getContent() == null ? null : wxMessage.getContent().trim())) | |||
| && | |||
| (this.rContent == null || Pattern | |||
| .matches(this.rContent, wxMessage.getContent() == null ? "" : wxMessage.getContent().trim())) | |||
| && | |||
| (this.matcher == null || this.matcher.match(wxMessage)) | |||
| ; | |||
| } | |||
| /** | |||
| @@ -160,7 +160,7 @@ public interface WxCpService { | |||
| * | |||
| * @param menu | |||
| * @throws WxErrorException | |||
| * @see #menuCreate(String, me.chanjar.weixin.common.bean.menu.WxMenu) | |||
| * @see #menuCreate(Integer, WxMenu) | |||
| */ | |||
| void menuCreate(WxMenu menu) throws WxErrorException; | |||
| @@ -177,7 +177,7 @@ public interface WxCpService { | |||
| * @throws WxErrorException | |||
| * @see #menuCreate(me.chanjar.weixin.common.bean.menu.WxMenu) | |||
| */ | |||
| void menuCreate(String agentId, WxMenu menu) throws WxErrorException; | |||
| void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| @@ -188,7 +188,7 @@ public interface WxCpService { | |||
| * </pre> | |||
| * | |||
| * @throws WxErrorException | |||
| * @see #menuDelete(String) | |||
| * @see #menuDelete(Integer) | |||
| */ | |||
| void menuDelete() throws WxErrorException; | |||
| @@ -204,7 +204,7 @@ public interface WxCpService { | |||
| * @throws WxErrorException | |||
| * @see #menuDelete() | |||
| */ | |||
| void menuDelete(String agentId) throws WxErrorException; | |||
| void menuDelete(Integer agentId) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| @@ -215,7 +215,7 @@ public interface WxCpService { | |||
| * </pre> | |||
| * | |||
| * @throws WxErrorException | |||
| * @see #menuGet(String) | |||
| * @see #menuGet(Integer) | |||
| */ | |||
| WxMenu menuGet() throws WxErrorException; | |||
| @@ -231,7 +231,7 @@ public interface WxCpService { | |||
| * @throws WxErrorException | |||
| * @see #menuGet() | |||
| */ | |||
| WxMenu menuGet(String agentId) throws WxErrorException; | |||
| WxMenu menuGet(Integer agentId) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| @@ -396,12 +396,12 @@ public interface WxCpService { | |||
| * <pre> | |||
| * 构造oauth2授权的url连接 | |||
| * </pre> | |||
| * | |||
| * | |||
| * @param state | |||
| * @return url | |||
| */ | |||
| String oauth2buildAuthorizationUrl(String state); | |||
| /** | |||
| * <pre> | |||
| * 构造oauth2授权的url连接 | |||
| @@ -425,7 +425,7 @@ public interface WxCpService { | |||
| * | |||
| * @param code | |||
| * @return [userid, deviceid] | |||
| * @see #oauth2getUserInfo(String, String) | |||
| * @see #oauth2getUserInfo(Integer, String) | |||
| */ | |||
| String[] oauth2getUserInfo(String code) throws WxErrorException; | |||
| @@ -443,7 +443,7 @@ public interface WxCpService { | |||
| * @return [userid, deviceid] | |||
| * @see #oauth2getUserInfo(String) | |||
| */ | |||
| String[] oauth2getUserInfo(String agentId, String code) throws WxErrorException; | |||
| String[] oauth2getUserInfo(Integer agentId, String code) throws WxErrorException; | |||
| /** | |||
| @@ -12,7 +12,6 @@ import me.chanjar.weixin.common.session.StandardSessionManager; | |||
| import me.chanjar.weixin.common.session.WxSession; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.common.util.RandomUtils; | |||
| import me.chanjar.weixin.common.util.StringUtils; | |||
| import me.chanjar.weixin.common.util.crypto.SHA1; | |||
| import me.chanjar.weixin.common.util.fs.FileUtils; | |||
| import me.chanjar.weixin.common.util.http.*; | |||
| @@ -22,8 +21,8 @@ import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpTag; | |||
| import me.chanjar.weixin.cp.bean.WxCpUser; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.http.HttpHost; | |||
| import org.apache.http.client.ClientProtocolException; | |||
| import org.apache.http.client.config.RequestConfig; | |||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||
| import org.apache.http.client.methods.HttpGet; | |||
| @@ -69,7 +68,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | |||
| try { | |||
| return SHA1.gen(this.configStorage.getToken(), timestamp, nonce, data) | |||
| .equals(msgSignature); | |||
| .equals(msgSignature); | |||
| } catch (Exception e) { | |||
| return false; | |||
| } | |||
| @@ -95,18 +94,18 @@ public class WxCpServiceImpl implements WxCpService { | |||
| synchronized (this.globalAccessTokenRefreshLock) { | |||
| if (this.configStorage.isAccessTokenExpired()) { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||
| + "&corpid=" + this.configStorage.getCorpId() | |||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||
| + "&corpid=" + this.configStorage.getCorpId() | |||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||
| try { | |||
| HttpGet httpGet = new HttpGet(url); | |||
| if (this.httpProxy != null) { | |||
| RequestConfig config = RequestConfig.custom() | |||
| .setProxy(this.httpProxy).build(); | |||
| .setProxy(this.httpProxy).build(); | |||
| httpGet.setConfig(config); | |||
| } | |||
| String resultContent = null; | |||
| try (CloseableHttpClient httpclient = getHttpclient(); | |||
| CloseableHttpResponse response = httpclient.execute(httpGet)) { | |||
| CloseableHttpResponse response = httpclient.execute(httpGet)) { | |||
| resultContent = new BasicResponseHandler().handleResponse(response); | |||
| } finally { | |||
| httpGet.releaseConnection(); | |||
| @@ -117,9 +116,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| } | |||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||
| this.configStorage.updateAccessToken( | |||
| accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
| } catch (ClientProtocolException e) { | |||
| throw new RuntimeException(e); | |||
| accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
| } catch (IOException e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| @@ -149,7 +146,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||
| int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||
| this.configStorage.updateJsapiTicket(jsapiTicket, | |||
| expiresInSeconds); | |||
| expiresInSeconds); | |||
| } | |||
| } | |||
| } | |||
| @@ -162,10 +159,10 @@ public class WxCpServiceImpl implements WxCpService { | |||
| String noncestr = RandomUtils.getRandomStr(); | |||
| String jsapiTicket = getJsapiTicket(false); | |||
| String signature = SHA1.genWithAmple( | |||
| "jsapi_ticket=" + jsapiTicket, | |||
| "noncestr=" + noncestr, | |||
| "timestamp=" + timestamp, | |||
| "url=" + url | |||
| "jsapi_ticket=" + jsapiTicket, | |||
| "noncestr=" + noncestr, | |||
| "timestamp=" + timestamp, | |||
| "url=" + url | |||
| ); | |||
| WxJsapiSignature jsapiSignature = new WxJsapiSignature(); | |||
| jsapiSignature.setTimestamp(timestamp); | |||
| @@ -191,9 +188,9 @@ public class WxCpServiceImpl implements WxCpService { | |||
| } | |||
| @Override | |||
| public void menuCreate(String agentId, WxMenu menu) throws WxErrorException { | |||
| public void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/create?agentid=" | |||
| + this.configStorage.getAgentId(); | |||
| + this.configStorage.getAgentId(); | |||
| post(url, menu.toJson()); | |||
| } | |||
| @@ -203,7 +200,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| } | |||
| @Override | |||
| public void menuDelete(String agentId) throws WxErrorException { | |||
| public void menuDelete(Integer agentId) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/delete?agentid=" + agentId; | |||
| get(url, null); | |||
| } | |||
| @@ -214,7 +211,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| } | |||
| @Override | |||
| public WxMenu menuGet(String agentId) throws WxErrorException { | |||
| public WxMenu menuGet(Integer agentId) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/get?agentid=" + agentId; | |||
| try { | |||
| String resultContent = get(url, null); | |||
| @@ -230,7 +227,7 @@ public class WxCpServiceImpl implements WxCpService { | |||
| @Override | |||
| public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) | |||
| throws WxErrorException, IOException { | |||
| throws WxErrorException, IOException { | |||
| return mediaUpload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType)); | |||
| } | |||
| @@ -244,9 +241,9 @@ public class WxCpServiceImpl implements WxCpService { | |||
| public File mediaDownload(String media_id) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/media/get"; | |||
| return execute( | |||
| new MediaDownloadRequestExecutor( | |||
| this.configStorage.getTmpDirFile()), | |||
| url, "media_id=" + media_id); | |||
| new MediaDownloadRequestExecutor( | |||
| this.configStorage.getTmpDirFile()), | |||
| url, "media_id=" + media_id); | |||
| } | |||
| @@ -254,9 +251,9 @@ this.configStorage.getTmpDirFile()), | |||
| public Integer departCreate(WxCpDepart depart) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create"; | |||
| String responseContent = execute( | |||
| new SimplePostRequestExecutor(), | |||
| url, | |||
| depart.toJson()); | |||
| new SimplePostRequestExecutor(), | |||
| url, | |||
| depart.toJson()); | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id")); | |||
| } | |||
| @@ -283,11 +280,11 @@ this.configStorage.getTmpDirFile()), | |||
| */ | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("department"), | |||
| new TypeToken<List<WxCpDepart>>() { | |||
| }.getType() | |||
| ); | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("department"), | |||
| new TypeToken<List<WxCpDepart>>() { | |||
| }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| @@ -313,8 +310,8 @@ this.configStorage.getTmpDirFile()), | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/batchdelete"; | |||
| JsonObject jsonObject = new JsonObject(); | |||
| JsonArray jsonArray = new JsonArray(); | |||
| for (int i = 0; i < userids.length; i++) { | |||
| jsonArray.add(new JsonPrimitive(userids[i])); | |||
| for (String userid : userids) { | |||
| jsonArray.add(new JsonPrimitive(userid)); | |||
| } | |||
| jsonObject.add("useridlist", jsonArray); | |||
| post(url, jsonObject.toString()); | |||
| @@ -343,11 +340,11 @@ this.configStorage.getTmpDirFile()), | |||
| String responseContent = get(url, params); | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| }.getType() | |||
| ); | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| @@ -366,11 +363,11 @@ this.configStorage.getTmpDirFile()), | |||
| String responseContent = get(url, params); | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| }.getType() | |||
| ); | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| @@ -404,11 +401,11 @@ this.configStorage.getTmpDirFile()), | |||
| String responseContent = get(url, null); | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("taglist"), | |||
| new TypeToken<List<WxCpTag>>() { | |||
| }.getType() | |||
| ); | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("taglist"), | |||
| new TypeToken<List<WxCpTag>>() { | |||
| }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| @@ -417,11 +414,11 @@ this.configStorage.getTmpDirFile()), | |||
| String responseContent = get(url, null); | |||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
| return WxCpGsonBuilder.INSTANCE.create() | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| }.getType() | |||
| ); | |||
| .fromJson( | |||
| tmpJsonElement.getAsJsonObject().get("userlist"), | |||
| new TypeToken<List<WxCpUser>>() { | |||
| }.getType() | |||
| ); | |||
| } | |||
| @Override | |||
| @@ -460,14 +457,14 @@ this.configStorage.getTmpDirFile()), | |||
| } | |||
| @Override | |||
| public String oauth2buildAuthorizationUrl(String state) { | |||
| return this.oauth2buildAuthorizationUrl( | |||
| this.configStorage.getOauth2redirectUri(), | |||
| state | |||
| ); | |||
| } | |||
| public String oauth2buildAuthorizationUrl(String state) { | |||
| return this.oauth2buildAuthorizationUrl( | |||
| this.configStorage.getOauth2redirectUri(), | |||
| state | |||
| ); | |||
| } | |||
| @Override | |||
| @Override | |||
| public String oauth2buildAuthorizationUrl(String redirectUri, String state) { | |||
| String url = "https://open.weixin.qq.com/connect/oauth2/authorize?"; | |||
| url += "appid=" + this.configStorage.getCorpId(); | |||
| @@ -487,10 +484,10 @@ this.configStorage.getOauth2redirectUri(), | |||
| } | |||
| @Override | |||
| public String[] oauth2getUserInfo(String agentId, String code) throws WxErrorException { | |||
| public String[] oauth2getUserInfo(Integer agentId, String code) throws WxErrorException { | |||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?" | |||
| + "code=" + code | |||
| + "&agentid=" + agentId; | |||
| + "code=" + code | |||
| + "&agentid=" + agentId; | |||
| String responseText = get(url, null); | |||
| JsonElement je = new JsonParser().parse(responseText); | |||
| JsonObject jo = je.getAsJsonObject(); | |||
| @@ -544,14 +541,14 @@ this.configStorage.getOauth2redirectUri(), | |||
| return executeInternal(executor, uri, data); | |||
| } catch (WxErrorException e) { | |||
| WxError error = e.getError(); | |||
| /** | |||
| /* | |||
| * -1 系统繁忙, 1000ms后重试 | |||
| */ | |||
| if (error.getErrorCode() == -1) { | |||
| int sleepMillis = this.retrySleepMillis * (1 << retryTimes); | |||
| try { | |||
| this.log.debug("微信系统繁忙,{}ms 后重试(第{}次)", sleepMillis, | |||
| retryTimes + 1); | |||
| retryTimes + 1); | |||
| Thread.sleep(sleepMillis); | |||
| } catch (InterruptedException e1) { | |||
| throw new RuntimeException(e1); | |||
| @@ -566,7 +563,7 @@ this.configStorage.getOauth2redirectUri(), | |||
| } | |||
| protected synchronized <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||
| if (uri.indexOf("access_token=") != -1) { | |||
| if (uri.contains("access_token=")) { | |||
| throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri); | |||
| } | |||
| String accessToken = getAccessToken(false); | |||
| @@ -576,7 +573,7 @@ this.configStorage.getOauth2redirectUri(), | |||
| try { | |||
| return executor.execute(getHttpclient(), this.httpProxy, | |||
| uriWithAccessToken, data); | |||
| uriWithAccessToken, data); | |||
| } catch (WxErrorException e) { | |||
| WxError error = e.getError(); | |||
| /* | |||
| @@ -593,8 +590,6 @@ this.configStorage.getOauth2redirectUri(), | |||
| throw new WxErrorException(error); | |||
| } | |||
| return null; | |||
| } catch (ClientProtocolException e) { | |||
| throw new RuntimeException(e); | |||
| } catch (IOException e) { | |||
| throw new RuntimeException(e); | |||
| } | |||
| @@ -608,15 +603,15 @@ this.configStorage.getOauth2redirectUri(), | |||
| public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) { | |||
| this.configStorage = wxConfigProvider; | |||
| ApacheHttpClientBuilder apacheHttpClientBuilder = this.configStorage | |||
| .getApacheHttpClientBuilder(); | |||
| .getApacheHttpClientBuilder(); | |||
| if (null == apacheHttpClientBuilder) { | |||
| apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get(); | |||
| } | |||
| apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost()) | |||
| .httpProxyPort(this.configStorage.getHttpProxyPort()) | |||
| .httpProxyUsername(this.configStorage.getHttpProxyUsername()) | |||
| .httpProxyPassword(this.configStorage.getHttpProxyPassword()); | |||
| .httpProxyPort(this.configStorage.getHttpProxyPort()) | |||
| .httpProxyUsername(this.configStorage.getHttpProxyUsername()) | |||
| .httpProxyPassword(this.configStorage.getHttpProxyPassword()); | |||
| if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) { | |||
| this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort()); | |||
| @@ -1,17 +1,12 @@ | |||
| package me.chanjar.weixin.cp.bean; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.*; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| import java.io.Serializable; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.FileBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.ImageBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.NewsBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.TextBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.VideoBuilder; | |||
| import me.chanjar.weixin.cp.bean.messagebuilder.VoiceBuilder; | |||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
| /** | |||
| * 消息 | |||
| * | |||
| @@ -23,7 +18,7 @@ public class WxCpMessage implements Serializable { | |||
| private String toUser; | |||
| private String toParty; | |||
| private String toTag; | |||
| private String agentId; | |||
| private Integer agentId; | |||
| private String msgType; | |||
| private String content; | |||
| private String mediaId; | |||
| @@ -101,11 +96,11 @@ public class WxCpMessage implements Serializable { | |||
| this.toTag = toTag; | |||
| } | |||
| public String getAgentId() { | |||
| public Integer getAgentId() { | |||
| return this.agentId; | |||
| } | |||
| public void setAgentId(String agentId) { | |||
| public void setAgentId(Integer agentId) { | |||
| this.agentId = agentId; | |||
| } | |||
| @@ -5,13 +5,13 @@ import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| public class BaseBuilder<T> { | |||
| protected String msgType; | |||
| protected String agentId; | |||
| protected Integer agentId; | |||
| protected String toUser; | |||
| protected String toParty; | |||
| protected String toTag; | |||
| protected String safe; | |||
| public T agentId(String agentId) { | |||
| public T agentId(Integer agentId) { | |||
| this.agentId = agentId; | |||
| return (T) this; | |||
| } | |||
| @@ -10,8 +10,8 @@ package me.chanjar.weixin.cp.util.json; | |||
| import com.google.gson.*; | |||
| import me.chanjar.weixin.common.api.WxConsts; | |||
| import me.chanjar.weixin.common.util.StringUtils; | |||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import java.lang.reflect.Type; | |||
| @@ -1,14 +1,12 @@ | |||
| package me.chanjar.weixin.cp.api; | |||
| import com.google.inject.Inject; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.testng.Assert; | |||
| import org.testng.annotations.Guice; | |||
| import org.testng.annotations.Test; | |||
| import com.google.inject.Inject; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.util.StringUtils; | |||
| /** | |||
| * 基础API测试 | |||
| * | |||
| @@ -1,18 +1,17 @@ | |||
| package me.chanjar.weixin.cp.demo; | |||
| import java.io.IOException; | |||
| import javax.servlet.http.HttpServlet; | |||
| import javax.servlet.http.HttpServletRequest; | |||
| import javax.servlet.http.HttpServletResponse; | |||
| import me.chanjar.weixin.common.util.StringUtils; | |||
| import me.chanjar.weixin.cp.api.WxCpConfigStorage; | |||
| import me.chanjar.weixin.cp.api.WxCpMessageRouter; | |||
| import me.chanjar.weixin.cp.api.WxCpService; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||
| import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||
| import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import javax.servlet.http.HttpServlet; | |||
| import javax.servlet.http.HttpServletRequest; | |||
| import javax.servlet.http.HttpServletResponse; | |||
| import java.io.IOException; | |||
| /** | |||
| * @author Daniel Qian | |||
| @@ -6,8 +6,8 @@ dependencies { | |||
| testCompile group: 'org.testng', name: 'testng', version:'6.8.7' | |||
| testCompile group: 'org.mockito', name: 'mockito-all', version:'1.9.5' | |||
| testCompile group: 'com.google.inject', name: 'guice', version:'3.0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.M0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.M0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.RC0' | |||
| testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.RC0' | |||
| testCompile group: 'joda-time', name: 'joda-time', version:'2.9.4' | |||
| } | |||
| test.useTestNG() | |||
| @@ -6,7 +6,7 @@ | |||
| <parent> | |||
| <groupId>com.github.binarywang</groupId> | |||
| <artifactId>weixin-java-parent</artifactId> | |||
| <version>2.3.0-SNAPSHOT</version> | |||
| <version>2.4.0-SNAPSHOT</version> | |||
| </parent> | |||
| <artifactId>weixin-java-mp</artifactId> | |||
| <name>WeiXin Java Tools - MP</name> | |||
| @@ -24,16 +24,16 @@ public interface WxMpConfigStorage { | |||
| /** | |||
| * 应该是线程安全的 | |||
| * @param accessToken | |||
| * @param accessToken 要更新的WxAccessToken对象 | |||
| */ | |||
| void updateAccessToken(WxAccessToken accessToken); | |||
| /** | |||
| * 应该是线程安全的 | |||
| * @param accessToken | |||
| * @param expiresIn | |||
| * @param accessToken 新的accessToken值 | |||
| * @param expiresInSeconds 过期时间,以秒为单位 | |||
| */ | |||
| void updateAccessToken(String accessToken, int expiresIn); | |||
| void updateAccessToken(String accessToken, int expiresInSeconds); | |||
| String getJsapiTicket(); | |||
| @@ -46,7 +46,8 @@ public interface WxMpConfigStorage { | |||
| /** | |||
| * 应该是线程安全的 | |||
| * @param jsapiTicket | |||
| * @param jsapiTicket 新的jsapi ticket值 | |||
| * @param expiresInSeconds 过期时间,以秒为单位 | |||
| */ | |||
| void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | |||
| @@ -61,7 +62,8 @@ public interface WxMpConfigStorage { | |||
| /** | |||
| * 应该是线程安全的 | |||
| * @param cardApiTicket | |||
| * @param cardApiTicket 新的cardApi ticket值 | |||
| * @param expiresInSeconds 过期时间,以秒为单位 | |||
| */ | |||
| void updateCardApiTicket(String cardApiTicket, int expiresInSeconds); | |||
| @@ -70,7 +72,7 @@ public interface WxMpConfigStorage { | |||
| String getSecret(); | |||
| String getPartnerId(); | |||
| String getPartnerKey(); | |||
| String getToken(); | |||
| @@ -88,7 +90,7 @@ public interface WxMpConfigStorage { | |||
| String getHttpProxyUsername(); | |||
| String getHttpProxyPassword(); | |||
| File getTmpDirFile(); | |||
| SSLContext getSSLContext(); | |||
| @@ -98,4 +100,9 @@ public interface WxMpConfigStorage { | |||
| * @return ApacheHttpClientBuilder | |||
| */ | |||
| ApacheHttpClientBuilder getApacheHttpClientBuilder(); | |||
| /** | |||
| * 是否自动刷新token | |||
| */ | |||
| boolean autoRefreshToken(); | |||
| } | |||
| @@ -1,13 +1,10 @@ | |||
| package me.chanjar.weixin.mp.api; | |||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder; | |||
| import javax.net.ssl.SSLContext; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import java.io.File; | |||
| /** | |||
| @@ -43,7 +40,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
| * 临时文件目录 | |||
| */ | |||
| protected volatile File tmpDirFile; | |||
| protected volatile SSLContext sslContext; | |||
| protected volatile ApacheHttpClientBuilder apacheHttpClientBuilder; | |||
| @@ -62,11 +59,11 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
| public synchronized void updateAccessToken(WxAccessToken accessToken) { | |||
| updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||
| } | |||
| @Override | |||
| public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { | |||
| this.accessToken = accessToken; | |||
| this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||
| this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||
| } | |||
| @Override | |||
| @@ -100,7 +97,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
| public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||
| this.jsapiTicket = jsapiTicket; | |||
| // 预留200秒的时间 | |||
| this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||
| this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||
| } | |||
| @Override | |||
| @@ -125,7 +122,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
| public synchronized void updateCardApiTicket(String cardApiTicket, int expiresInSeconds) { | |||
| this.cardApiTicket = cardApiTicket; | |||
| // 预留200秒的时间 | |||
| this.cardApiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||
| this.cardApiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||
| } | |||
| @Override | |||
| @@ -229,7 +226,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this,ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| @Override | |||
| @@ -263,7 +260,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
| public SSLContext getSSLContext() { | |||
| return this.sslContext; | |||
| } | |||
| public void setSSLContext(SSLContext context) { | |||
| this.sslContext = context; | |||
| } | |||
| @@ -273,6 +270,11 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||
| return this.apacheHttpClientBuilder; | |||
| } | |||
| @Override | |||
| public boolean autoRefreshToken() { | |||
| return true; | |||
| } | |||
| public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) { | |||
| this.apacheHttpClientBuilder = apacheHttpClientBuilder; | |||
| } | |||
| @@ -1,17 +1,12 @@ | |||
| package me.chanjar.weixin.mp.api; | |||
| import java.io.File; | |||
| import java.util.Date; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.mp.bean.WxMpCustomMessage; | |||
| import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; | |||
| import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfAccountRequest; | |||
| import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfList; | |||
| import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfMsgList; | |||
| import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfOnlineList; | |||
| import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionGetResult; | |||
| import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionList; | |||
| import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfSessionWaitCaseList; | |||
| import me.chanjar.weixin.mp.bean.kefu.result.*; | |||
| import java.io.File; | |||
| import java.util.Date; | |||
| /** | |||
| * 客服接口 , | |||
| @@ -27,9 +22,10 @@ public interface WxMpKefuService { | |||
| * <pre> | |||
| * 发送客服消息 | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140547&token=&lang=zh_CN">发送客服消息</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| */ | |||
| boolean customMessageSend(WxMpCustomMessage message) throws WxErrorException; | |||
| boolean sendKefuMessage(WxMpKefuMessage message) throws WxErrorException; | |||
| //*******************客服管理接口***********************// | |||
| @@ -2,11 +2,12 @@ package me.chanjar.weixin.mp.api; | |||
| import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.mp.bean.*; | |||
| import me.chanjar.weixin.mp.bean.result.*; | |||
| import me.chanjar.weixin.mp.bean.material.WxMpMaterial; | |||
| import me.chanjar.weixin.mp.bean.material.WxMpMaterialArticleUpdate; | |||
| import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews; | |||
| import me.chanjar.weixin.mp.bean.material.*; | |||
| import java.io.File; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| /** | |||
| @@ -20,21 +21,58 @@ import java.io.InputStream; | |||
| public interface WxMpMaterialService { | |||
| /** | |||
| * <pre> | |||
| * 新增临时素材 | |||
| * | |||
| * @param mediaType | |||
| * @param file | |||
| * 公众号经常有需要用到一些临时性的多媒体素材的场景,例如在使用接口特别是发送消息时,对多媒体文件、多媒体消息的获取和调用等操作,是通过media_id来进行的。 | |||
| * 素材管理接口对所有认证的订阅号和服务号开放。通过本接口,公众号可以新增临时素材(即上传临时多媒体文件)。 | |||
| * 请注意: | |||
| * 1、对于临时素材,每个素材(media_id)会在开发者上传或粉丝发送到微信服务器3天后自动删除(所以用户发送给开发者的素材,若开发者需要,应尽快下载到本地),以节省服务器资源。 | |||
| * 2、media_id是可复用的。 | |||
| * 3、素材的格式大小等要求与公众平台官网一致。具体是,图片大小不超过2M,支持png/jpeg/jpg/gif格式,语音大小不超过5M,长度不超过60秒,支持mp3/amr格式 | |||
| * 4、需使用https调用本接口。 | |||
| * 本接口即为原“上传多媒体文件”接口。 | |||
| * 注意事项: | |||
| * 上传的临时多媒体文件有格式和大小限制,如下: | |||
| * 图片(image): 2M,支持PNG\JPEG\JPG\GIF格式 | |||
| * 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | |||
| * 视频(video):10MB,支持MP4格式 | |||
| * 缩略图(thumb):64KB,支持JPG格式 | |||
| *媒体文件在后台保存时间为3天,即3天后media_id失效。 | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738726&token=&lang=zh_CN">新增临时素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE | |||
| * </pre> | |||
| * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | |||
| * @param file 文件对象 | |||
| * @throws WxErrorException | |||
| * @see #mediaUpload(String, String, InputStream) | |||
| */ | |||
| WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 新增临时素材 | |||
| * 本接口即为原“上传多媒体文件”接口。 | |||
| * | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738726&token=&lang=zh_CN">新增临时素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE | |||
| * </pre> | |||
| * | |||
| * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | |||
| * @param fileType 文件类型,请看{@link me.chanjar.weixin.common.api.WxConsts} | |||
| * @param inputStream 输入流 | |||
| * @throws WxErrorException | |||
| * @see #mediaUpload(java.lang.String, java.io.File) | |||
| */ | |||
| WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获取临时素材 | |||
| * 公众号可以使用本接口获取临时素材(即下载临时的多媒体文件)。请注意,视频文件不支持https下载,调用该接口需http协议。 | |||
| * 本接口即为原“下载多媒体文件”接口。 | |||
| * 根据微信文档,视频文件下载不了,会返回null | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki/9/677a85e3f3849af35de54bb5516c2521.html">获取临时素材</a> | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738727&token=&lang=zh_CN">获取临时素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID | |||
| * </pre> | |||
| * | |||
| * @param media_id | |||
| @@ -46,10 +84,12 @@ public interface WxMpMaterialService { | |||
| /** | |||
| * <pre> | |||
| * 上传图文消息内的图片获取URL | |||
| * 详情请见:http://mp.weixin.qq.com/wiki/15/40b6865b893947b764e2de8e4a1fb55f.html#.E4.B8.8A.E4.BC.A0.E5.9B.BE.E6.96.87.E6.B6.88.E6.81.AF.E5.86.85.E7.9A.84.E5.9B.BE.E7.89.87.E8.8E.B7.E5.8F.96URL.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8E.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.9D.87.E5.8F.AF.E7.94.A8.E3.80.91 | |||
| * 请注意,本接口所上传的图片不占用公众号的素材库中图片数量的5000个的限制。图片仅支持jpg/png格式,大小必须在1MB以下。 | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">新增永久素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param file | |||
| * @param file 上传的文件对象 | |||
| * @return WxMediaImgUploadResult 返回图片url | |||
| * @throws WxErrorException | |||
| */ | |||
| @@ -57,114 +97,123 @@ public interface WxMpMaterialService { | |||
| /** | |||
| * <pre> | |||
| * 新增临时素材 | |||
| * 本接口即为原“上传多媒体文件”接口。 | |||
| * | |||
| * 上传的多媒体文件有格式和大小限制,如下: | |||
| * 图片(image): 1M,支持JPG格式 | |||
| * 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | |||
| * 视频(video):10MB,支持MP4格式 | |||
| * 缩略图(thumb):64KB,支持JPG格式 | |||
| * | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki/15/2d353966323806a202cd2deaafe8e557.html">新增临时素材</a> | |||
| * </pre> | |||
| * | |||
| * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | |||
| * @param fileType 文件类型,请看{@link me.chanjar.weixin.common.api.WxConsts} | |||
| * @param inputStream 输入流 | |||
| * @throws WxErrorException | |||
| */ | |||
| WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) throws WxErrorException, IOException; | |||
| /** | |||
| * <pre> | |||
| * 上传非图文永久素材 | |||
| * | |||
| * 上传的多媒体文件有格式和大小限制,如下: | |||
| * 图片(image): 图片大小不超过2M,支持bmp/png/jpeg/jpg/gif格式 | |||
| * 语音(voice):语音大小不超过5M,长度不超过60秒,支持mp3/wma/wav/amr格式 | |||
| * 视频(video):在上传视频素材时需要POST另一个表单,id为description,包含素材的描述信息,内容格式为JSON | |||
| * 缩略图(thumb):文档未说明 | |||
| * | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/14/7e6c03263063f4813141c3e17dd4350a.html | |||
| * 新增非图文永久素材 | |||
| * 通过POST表单来调用接口,表单id为media,包含需要上传的素材内容,有filename、filelength、content-type等信息。请注意:图片素材将进入公众平台官网素材管理模块中的默认分组。 | |||
| * 新增永久视频素材需特别注意: | |||
| * 在上传视频素材时需要POST另一个表单,id为description,包含素材的描述信息,内容格式为JSON,格式如下: | |||
| * { "title":VIDEO_TITLE, "introduction":INTRODUCTION } | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">新增永久素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=TYPE | |||
| * | |||
| * 除了3天就会失效的临时素材外,开发者有时需要永久保存一些素材,届时就可以通过本接口新增永久素材。 | |||
| 永久图片素材新增后,将带有URL返回给开发者,开发者可以在腾讯系域名内使用(腾讯系域名外使用,图片将被屏蔽)。 | |||
| 请注意: | |||
| 1、新增的永久素材也可以在公众平台官网素材管理模块中看到 | |||
| 2、永久素材的数量是有上限的,请谨慎新增。图文消息素材和图片素材的上限为5000,其他类型为1000 | |||
| 3、素材的格式大小等要求与公众平台官网一致。具体是,图片大小不超过2M,支持bmp/png/jpeg/jpg/gif格式,语音大小不超过5M,长度不超过60秒,支持mp3/wma/wav/amr格式 | |||
| 4、调用该接口需https协议 | |||
| * </pre> | |||
| * | |||
| * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | |||
| * @param material 上传的素材, 请看{@link me.chanjar.weixin.mp.bean.WxMpMaterial} | |||
| * @param material 上传的素材, 请看{@link WxMpMaterial} | |||
| */ | |||
| WxMpMaterialUploadResult materialFileUpload(String mediaType, WxMpMaterial material) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 上传永久图文素材 | |||
| * | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/14/7e6c03263063f4813141c3e17dd4350a.html | |||
| * 新增永久图文素材 | |||
| * | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">新增永久素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/add_news?access_token=ACCESS_TOKEN | |||
| * | |||
| * 除了3天就会失效的临时素材外,开发者有时需要永久保存一些素材,届时就可以通过本接口新增永久素材。 | |||
| 永久图片素材新增后,将带有URL返回给开发者,开发者可以在腾讯系域名内使用(腾讯系域名外使用,图片将被屏蔽)。 | |||
| 请注意: | |||
| 1、新增的永久素材也可以在公众平台官网素材管理模块中看到 | |||
| 2、永久素材的数量是有上限的,请谨慎新增。图文消息素材和图片素材的上限为5000,其他类型为1000 | |||
| 3、素材的格式大小等要求与公众平台官网一致。具体是,图片大小不超过2M,支持bmp/png/jpeg/jpg/gif格式,语音大小不超过5M,长度不超过60秒,支持mp3/wma/wav/amr格式 | |||
| 4、调用该接口需https协议 | |||
| * </pre> | |||
| * | |||
| * @param news 上传的图文消息, 请看{@link me.chanjar.weixin.mp.bean.WxMpMaterialNews} | |||
| * @param news 上传的图文消息, 请看{@link WxMpMaterialNews} | |||
| */ | |||
| WxMpMaterialUploadResult materialNewsUpload(WxMpMaterialNews news) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 下载声音或者图片永久素材 | |||
| * 获取声音或者图片永久素材 | |||
| * | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/4/b3546879f07623cb30df9ca0e420a5d0.html | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">获取永久素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param media_id 永久素材的id | |||
| * @param mediaId 永久素材的id | |||
| */ | |||
| InputStream materialImageOrVoiceDownload(String media_id) throws WxErrorException; | |||
| InputStream materialImageOrVoiceDownload(String mediaId) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获取视频永久素材的信息和下载地址 | |||
| * | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/4/b3546879f07623cb30df9ca0e420a5d0.html | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">获取永久素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param media_id 永久素材的id | |||
| * @param mediaId 永久素材的id | |||
| */ | |||
| WxMpMaterialVideoInfoResult materialVideoInfo(String media_id) throws WxErrorException; | |||
| WxMpMaterialVideoInfoResult materialVideoInfo(String mediaId) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获取图文永久素材的信息 | |||
| * | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/4/b3546879f07623cb30df9ca0e420a5d0.html | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738729&token=&lang=zh_CN">获取永久素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param media_id 永久素材的id | |||
| * @param mediaId 永久素材的id | |||
| */ | |||
| WxMpMaterialNews materialNewsInfo(String media_id) throws WxErrorException; | |||
| WxMpMaterialNews materialNewsInfo(String mediaId) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 更新图文永久素材 | |||
| * 修改永久图文素材 | |||
| * | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/4/19a59cba020d506e767360ca1be29450.html | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738732&token=&lang=zh_CN">修改永久图文素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/update_news?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param wxMpMaterialArticleUpdate 用来更新图文素材的bean, 请看{@link me.chanjar.weixin.mp.bean.WxMpMaterialArticleUpdate} | |||
| * @param wxMpMaterialArticleUpdate 用来更新图文素材的bean, 请看{@link WxMpMaterialArticleUpdate} | |||
| */ | |||
| boolean materialNewsUpdate(WxMpMaterialArticleUpdate wxMpMaterialArticleUpdate) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 删除永久素材 | |||
| * | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/5/e66f61c303db51a6c0f90f46b15af5f5.html | |||
| * 在新增了永久素材后,开发者可以根据本接口来删除不再需要的永久素材,节省空间。 | |||
| * 请注意: | |||
| * 1、请谨慎操作本接口,因为它可以删除公众号在公众平台官网素材管理模块中新建的图文消息、语音、视频等素材(但需要先通过获取素材列表来获知素材的media_id) | |||
| * 2、临时素材无法通过本接口删除 | |||
| * 3、调用该接口需https协议 | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738731&token=&lang=zh_CN">删除永久素材</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/del_material?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param media_id 永久素材的id | |||
| * @param mediaId 永久素材的id | |||
| */ | |||
| boolean materialDelete(String media_id) throws WxErrorException; | |||
| boolean materialDelete(String mediaId) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获取各类素材总数 | |||
| * | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/16/8cc64f8c189674b421bee3ed403993b8.html | |||
| * 开发者可以根据本接口来获取永久素材的列表,需要时也可保存到本地。 | |||
| * 请注意: | |||
| * 1.永久素材的总数,也会计算公众平台官网素材管理中的素材 | |||
| * 2.图片和图文消息素材(包括单图文和多图文)的总数上限为5000,其他素材的总数上限为1000 | |||
| * 3.调用该接口需https协议 | |||
| * | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738733&token=&lang=zh_CN">获取素材总数</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/get_materialcount?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| */ | |||
| WxMpMaterialCountResult materialCount() throws WxErrorException; | |||
| @@ -173,7 +222,8 @@ public interface WxMpMaterialService { | |||
| * <pre> | |||
| * 分页获取图文素材列表 | |||
| * | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/12/2108cd7aafff7f388f41f37efa710204.html | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738734&token=&lang=zh_CN">获取素材列表</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param offset 从全部素材的该偏移位置开始返回,0表示从第一个素材 返回 | |||
| @@ -185,7 +235,8 @@ public interface WxMpMaterialService { | |||
| * <pre> | |||
| * 分页获取其他媒体素材列表 | |||
| * | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/12/2108cd7aafff7f388f41f37efa710204.html | |||
| * 详情请见: <a href="http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1444738734&token=&lang=zh_CN">获取素材列表</a> | |||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param type 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | |||
| @@ -2,8 +2,8 @@ package me.chanjar.weixin.mp.api; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import java.util.Map; | |||
| @@ -2,7 +2,7 @@ package me.chanjar.weixin.mp.api; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import java.util.Map; | |||
| @@ -1,6 +1,6 @@ | |||
| package me.chanjar.weixin.mp.api; | |||
| import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| /** | |||
| * 消息匹配器,用在消息路由的时候 | |||
| @@ -1,230 +1,230 @@ | |||
| package me.chanjar.weixin.mp.api; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import java.util.concurrent.ExecutionException; | |||
| import java.util.concurrent.ExecutorService; | |||
| import java.util.concurrent.Executors; | |||
| import java.util.concurrent.Future; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
| import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | |||
| import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
| import me.chanjar.weixin.common.session.InternalSession; | |||
| import me.chanjar.weixin.common.session.InternalSessionManager; | |||
| import me.chanjar.weixin.common.session.StandardSessionManager; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.common.util.LogExceptionHandler; | |||
| import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | |||
| /** | |||
| * <pre> | |||
| * 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理 | |||
| * | |||
| * 说明: | |||
| * 1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理 | |||
| * 2. 默认情况下消息只会被处理一次,除非使用 {@link WxMpMessageRouterRule#next()} | |||
| * 3. 规则的结束必须用{@link WxMpMessageRouterRule#end()}或者{@link WxMpMessageRouterRule#next()},否则不会生效 | |||
| * | |||
| * 使用方法: | |||
| * WxMpMessageRouter router = new WxMpMessageRouter(); | |||
| * router | |||
| * .rule() | |||
| * .msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT") | |||
| * .interceptor(interceptor, ...).handler(handler, ...) | |||
| * .end() | |||
| * .rule() | |||
| * // 另外一个匹配规则 | |||
| * .end() | |||
| * ; | |||
| * | |||
| * // 将WxXmlMessage交给消息路由器 | |||
| * router.route(message); | |||
| * | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public class WxMpMessageRouter { | |||
| protected final Logger log = LoggerFactory.getLogger(WxMpMessageRouter.class); | |||
| private static final int DEFAULT_THREAD_POOL_SIZE = 100; | |||
| private final List<WxMpMessageRouterRule> rules = new ArrayList<>(); | |||
| private final WxMpService wxMpService; | |||
| private ExecutorService executorService; | |||
| private WxMessageDuplicateChecker messageDuplicateChecker; | |||
| private WxSessionManager sessionManager; | |||
| private WxErrorExceptionHandler exceptionHandler; | |||
| public WxMpMessageRouter(WxMpService wxMpService) { | |||
| this.wxMpService = wxMpService; | |||
| this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | |||
| this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); | |||
| this.sessionManager = new StandardSessionManager(); | |||
| this.exceptionHandler = new LogExceptionHandler(); | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 设置自定义的 {@link ExecutorService} | |||
| * 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100) | |||
| * </pre> | |||
| * @param executorService | |||
| */ | |||
| public void setExecutorService(ExecutorService executorService) { | |||
| this.executorService = executorService; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 设置自定义的 {@link me.chanjar.weixin.common.api.WxMessageDuplicateChecker} | |||
| * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker} | |||
| * </pre> | |||
| * @param messageDuplicateChecker | |||
| */ | |||
| public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) { | |||
| this.messageDuplicateChecker = messageDuplicateChecker; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager} | |||
| * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.session.StandardSessionManager} | |||
| * </pre> | |||
| * @param sessionManager | |||
| */ | |||
| public void setSessionManager(WxSessionManager sessionManager) { | |||
| this.sessionManager = sessionManager; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 设置自定义的{@link me.chanjar.weixin.common.api.WxErrorExceptionHandler} | |||
| * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.LogExceptionHandler} | |||
| * </pre> | |||
| * @param exceptionHandler | |||
| */ | |||
| public void setExceptionHandler(WxErrorExceptionHandler exceptionHandler) { | |||
| this.exceptionHandler = exceptionHandler; | |||
| } | |||
| List<WxMpMessageRouterRule> getRules() { | |||
| return this.rules; | |||
| } | |||
| /** | |||
| * 开始一个新的Route规则 | |||
| */ | |||
| public WxMpMessageRouterRule rule() { | |||
| return new WxMpMessageRouterRule(this); | |||
| } | |||
| /** | |||
| * 处理微信消息 | |||
| * @param wxMessage | |||
| */ | |||
| public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) { | |||
| if (isDuplicateMessage(wxMessage)) { | |||
| // 如果是重复消息,那么就不做处理 | |||
| return null; | |||
| } | |||
| final List<WxMpMessageRouterRule> matchRules = new ArrayList<>(); | |||
| // 收集匹配的规则 | |||
| for (final WxMpMessageRouterRule rule : this.rules) { | |||
| if (rule.test(wxMessage)) { | |||
| matchRules.add(rule); | |||
| if(!rule.isReEnter()) { | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| if (matchRules.size() == 0) { | |||
| return null; | |||
| } | |||
| WxMpXmlOutMessage res = null; | |||
| final List<Future<?>> futures = new ArrayList<>(); | |||
| for (final WxMpMessageRouterRule rule : matchRules) { | |||
| // 返回最后一个非异步的rule的执行结果 | |||
| if(rule.isAsync()) { | |||
| futures.add( | |||
| this.executorService.submit(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| rule.service(wxMessage, WxMpMessageRouter.this.wxMpService, WxMpMessageRouter.this.sessionManager, WxMpMessageRouter.this.exceptionHandler); | |||
| } | |||
| }) | |||
| ); | |||
| } else { | |||
| res = rule.service(wxMessage, this.wxMpService, this.sessionManager, this.exceptionHandler); | |||
| // 在同步操作结束,session访问结束 | |||
| this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUser()); | |||
| sessionEndAccess(wxMessage); | |||
| } | |||
| } | |||
| if (futures.size() > 0) { | |||
| this.executorService.submit(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| for (Future<?> future : futures) { | |||
| try { | |||
| future.get(); | |||
| WxMpMessageRouter.this.log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUser()); | |||
| // 异步操作结束,session访问结束 | |||
| sessionEndAccess(wxMessage); | |||
| } catch (InterruptedException e) { | |||
| WxMpMessageRouter.this.log.error("Error happened when wait task finish", e); | |||
| } catch (ExecutionException e) { | |||
| WxMpMessageRouter.this.log.error("Error happened when wait task finish", e); | |||
| } | |||
| } | |||
| } | |||
| }); | |||
| } | |||
| return res; | |||
| } | |||
| protected boolean isDuplicateMessage(WxMpXmlMessage wxMessage) { | |||
| StringBuffer messageId = new StringBuffer(); | |||
| if (wxMessage.getMsgId() == null) { | |||
| messageId.append(wxMessage.getCreateTime()) | |||
| .append("-").append(wxMessage.getFromUser()) | |||
| .append("-").append(wxMessage.getEventKey() == null ? "" : wxMessage.getEventKey()) | |||
| .append("-").append(wxMessage.getEvent() == null ? "" : wxMessage.getEvent()) | |||
| ; | |||
| } else { | |||
| messageId.append(wxMessage.getMsgId()); | |||
| } | |||
| return this.messageDuplicateChecker.isDuplicate(messageId.toString()); | |||
| } | |||
| /** | |||
| * 对session的访问结束 | |||
| * @param wxMessage | |||
| */ | |||
| protected void sessionEndAccess(WxMpXmlMessage wxMessage) { | |||
| InternalSession session = ((InternalSessionManager)this.sessionManager).findSession(wxMessage.getFromUser()); | |||
| if (session != null) { | |||
| session.endAccess(); | |||
| } | |||
| } | |||
| } | |||
| package me.chanjar.weixin.mp.api; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import java.util.concurrent.ExecutionException; | |||
| import java.util.concurrent.ExecutorService; | |||
| import java.util.concurrent.Executors; | |||
| import java.util.concurrent.Future; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
| import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; | |||
| import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; | |||
| import me.chanjar.weixin.common.session.InternalSession; | |||
| import me.chanjar.weixin.common.session.InternalSessionManager; | |||
| import me.chanjar.weixin.common.session.StandardSessionManager; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.common.util.LogExceptionHandler; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| /** | |||
| * <pre> | |||
| * 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理 | |||
| * | |||
| * 说明: | |||
| * 1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理 | |||
| * 2. 默认情况下消息只会被处理一次,除非使用 {@link WxMpMessageRouterRule#next()} | |||
| * 3. 规则的结束必须用{@link WxMpMessageRouterRule#end()}或者{@link WxMpMessageRouterRule#next()},否则不会生效 | |||
| * | |||
| * 使用方法: | |||
| * WxMpMessageRouter router = new WxMpMessageRouter(); | |||
| * router | |||
| * .rule() | |||
| * .msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT") | |||
| * .interceptor(interceptor, ...).handler(handler, ...) | |||
| * .end() | |||
| * .rule() | |||
| * // 另外一个匹配规则 | |||
| * .end() | |||
| * ; | |||
| * | |||
| * // 将WxXmlMessage交给消息路由器 | |||
| * router.route(message); | |||
| * | |||
| * </pre> | |||
| * @author Daniel Qian | |||
| * | |||
| */ | |||
| public class WxMpMessageRouter { | |||
| protected final Logger log = LoggerFactory.getLogger(WxMpMessageRouter.class); | |||
| private static final int DEFAULT_THREAD_POOL_SIZE = 100; | |||
| private final List<WxMpMessageRouterRule> rules = new ArrayList<>(); | |||
| private final WxMpService wxMpService; | |||
| private ExecutorService executorService; | |||
| private WxMessageDuplicateChecker messageDuplicateChecker; | |||
| private WxSessionManager sessionManager; | |||
| private WxErrorExceptionHandler exceptionHandler; | |||
| public WxMpMessageRouter(WxMpService wxMpService) { | |||
| this.wxMpService = wxMpService; | |||
| this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | |||
| this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); | |||
| this.sessionManager = new StandardSessionManager(); | |||
| this.exceptionHandler = new LogExceptionHandler(); | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 设置自定义的 {@link ExecutorService} | |||
| * 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100) | |||
| * </pre> | |||
| * @param executorService | |||
| */ | |||
| public void setExecutorService(ExecutorService executorService) { | |||
| this.executorService = executorService; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 设置自定义的 {@link me.chanjar.weixin.common.api.WxMessageDuplicateChecker} | |||
| * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker} | |||
| * </pre> | |||
| * @param messageDuplicateChecker | |||
| */ | |||
| public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) { | |||
| this.messageDuplicateChecker = messageDuplicateChecker; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager} | |||
| * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.session.StandardSessionManager} | |||
| * </pre> | |||
| * @param sessionManager | |||
| */ | |||
| public void setSessionManager(WxSessionManager sessionManager) { | |||
| this.sessionManager = sessionManager; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 设置自定义的{@link me.chanjar.weixin.common.api.WxErrorExceptionHandler} | |||
| * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.LogExceptionHandler} | |||
| * </pre> | |||
| * @param exceptionHandler | |||
| */ | |||
| public void setExceptionHandler(WxErrorExceptionHandler exceptionHandler) { | |||
| this.exceptionHandler = exceptionHandler; | |||
| } | |||
| List<WxMpMessageRouterRule> getRules() { | |||
| return this.rules; | |||
| } | |||
| /** | |||
| * 开始一个新的Route规则 | |||
| */ | |||
| public WxMpMessageRouterRule rule() { | |||
| return new WxMpMessageRouterRule(this); | |||
| } | |||
| /** | |||
| * 处理微信消息 | |||
| * @param wxMessage | |||
| */ | |||
| public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) { | |||
| if (isDuplicateMessage(wxMessage)) { | |||
| // 如果是重复消息,那么就不做处理 | |||
| return null; | |||
| } | |||
| final List<WxMpMessageRouterRule> matchRules = new ArrayList<>(); | |||
| // 收集匹配的规则 | |||
| for (final WxMpMessageRouterRule rule : this.rules) { | |||
| if (rule.test(wxMessage)) { | |||
| matchRules.add(rule); | |||
| if(!rule.isReEnter()) { | |||
| break; | |||
| } | |||
| } | |||
| } | |||
| if (matchRules.size() == 0) { | |||
| return null; | |||
| } | |||
| WxMpXmlOutMessage res = null; | |||
| final List<Future<?>> futures = new ArrayList<>(); | |||
| for (final WxMpMessageRouterRule rule : matchRules) { | |||
| // 返回最后一个非异步的rule的执行结果 | |||
| if(rule.isAsync()) { | |||
| futures.add( | |||
| this.executorService.submit(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| rule.service(wxMessage, WxMpMessageRouter.this.wxMpService, WxMpMessageRouter.this.sessionManager, WxMpMessageRouter.this.exceptionHandler); | |||
| } | |||
| }) | |||
| ); | |||
| } else { | |||
| res = rule.service(wxMessage, this.wxMpService, this.sessionManager, this.exceptionHandler); | |||
| // 在同步操作结束,session访问结束 | |||
| this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUser()); | |||
| sessionEndAccess(wxMessage); | |||
| } | |||
| } | |||
| if (futures.size() > 0) { | |||
| this.executorService.submit(new Runnable() { | |||
| @Override | |||
| public void run() { | |||
| for (Future<?> future : futures) { | |||
| try { | |||
| future.get(); | |||
| WxMpMessageRouter.this.log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUser()); | |||
| // 异步操作结束,session访问结束 | |||
| sessionEndAccess(wxMessage); | |||
| } catch (InterruptedException e) { | |||
| WxMpMessageRouter.this.log.error("Error happened when wait task finish", e); | |||
| } catch (ExecutionException e) { | |||
| WxMpMessageRouter.this.log.error("Error happened when wait task finish", e); | |||
| } | |||
| } | |||
| } | |||
| }); | |||
| } | |||
| return res; | |||
| } | |||
| protected boolean isDuplicateMessage(WxMpXmlMessage wxMessage) { | |||
| StringBuffer messageId = new StringBuffer(); | |||
| if (wxMessage.getMsgId() == null) { | |||
| messageId.append(wxMessage.getCreateTime()) | |||
| .append("-").append(wxMessage.getFromUser()) | |||
| .append("-").append(wxMessage.getEventKey() == null ? "" : wxMessage.getEventKey()) | |||
| .append("-").append(wxMessage.getEvent() == null ? "" : wxMessage.getEvent()) | |||
| ; | |||
| } else { | |||
| messageId.append(wxMessage.getMsgId()); | |||
| } | |||
| return this.messageDuplicateChecker.isDuplicate(messageId.toString()); | |||
| } | |||
| /** | |||
| * 对session的访问结束 | |||
| * @param wxMessage | |||
| */ | |||
| protected void sessionEndAccess(WxMpXmlMessage wxMessage) { | |||
| InternalSession session = ((InternalSessionManager)this.sessionManager).findSession(wxMessage.getFromUser()); | |||
| if (session != null) { | |||
| session.endAccess(); | |||
| } | |||
| } | |||
| } | |||
| @@ -3,8 +3,8 @@ package me.chanjar.weixin.mp.api; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.session.WxSessionManager; | |||
| import me.chanjar.weixin.common.api.WxErrorExceptionHandler; | |||
| import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; | |||
| import java.util.ArrayList; | |||
| import java.util.HashMap; | |||
| @@ -1,7 +1,13 @@ | |||
| package me.chanjar.weixin.mp.api; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.mp.bean.pay.*; | |||
| import me.chanjar.weixin.mp.bean.pay.WxPayJsSDKCallback; | |||
| import me.chanjar.weixin.mp.bean.pay.result.WxPayOrderCloseResult; | |||
| import me.chanjar.weixin.mp.bean.pay.request.WxEntPayRequest; | |||
| import me.chanjar.weixin.mp.bean.pay.request.WxPayRefundRequest; | |||
| import me.chanjar.weixin.mp.bean.pay.request.WxPaySendRedpackRequest; | |||
| import me.chanjar.weixin.mp.bean.pay.request.WxPayUnifiedOrderRequest; | |||
| import me.chanjar.weixin.mp.bean.pay.result.*; | |||
| import java.io.File; | |||
| import java.util.Map; | |||
| @@ -14,38 +20,54 @@ import java.util.Map; | |||
| public interface WxMpPayService { | |||
| /** | |||
| * 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) | |||
| * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" | |||
| * 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder | |||
| * <pre> | |||
| * 查询订单(详见https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2) | |||
| * 该接口提供所有微信支付订单的查询,商户可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。 | |||
| * 需要调用查询接口的情况: | |||
| ◆ 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知; | |||
| ◆ 调用支付接口后,返回系统错误或未知交易状态情况; | |||
| ◆ 调用被扫支付API,返回USERPAYING的状态; | |||
| ◆ 调用关单或撤销接口API之前,需确认支付状态; | |||
| * 接口地址:https://api.mch.weixin.qq.com/pay/orderquery | |||
| * </pre> | |||
| * @param transactionId 微信支付分配的商户号 | |||
| * @param outTradeNo 商户系统内部的订单号,当没提供transaction_id时需要传这个。 | |||
| * @throws WxErrorException | |||
| * @param request 请求对象 | |||
| * | |||
| */ | |||
| WxUnifiedOrderResult unifiedOrder(WxUnifiedOrderRequest request) | |||
| throws WxErrorException; | |||
| WxPayOrderQueryResult queryOrder(String transactionId, String outTradeNo) throws WxErrorException; | |||
| /** | |||
| * 该接口调用“统一下单”接口,并拼装发起支付请求需要的参数 | |||
| * 详见http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN | |||
| * @param request 请求对象 | |||
| * <pre> | |||
| * 关闭订单 | |||
| * 应用场景 | |||
| * 以下情况需要调用关单接口: | |||
| * 1. 商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付; | |||
| * 2. 系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口。 | |||
| * 注意:订单生成后不能马上调用关单接口,最短调用时间间隔为5分钟。 | |||
| * 接口地址:https://api.mch.weixin.qq.com/pay/closeorder | |||
| * 是否需要证书: 不需要。 | |||
| * </pre> | |||
| * @param outTradeNo 商户系统内部的订单号,当没提供transaction_id时需要传这个。 | |||
| * @throws WxErrorException | |||
| */ | |||
| Map<String, String> getPayInfo(WxUnifiedOrderRequest request) throws WxErrorException; | |||
| WxPayOrderCloseResult closeOrder(String outTradeNo) throws WxErrorException; | |||
| /** | |||
| * 该接口提供所有微信支付订单的查询,当支付通知处理异常戒丢失的情冴,商户可以通过该接口查询订单支付状态。 | |||
| * 详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2 | |||
| * 统一下单(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_1) | |||
| * 在发起微信支付前,需要调用统一下单接口,获取"预支付交易会话标识" | |||
| * 接口地址:https://api.mch.weixin.qq.com/pay/unifiedorder | |||
| * @throws WxErrorException | |||
| * @param request 请求对象 | |||
| * | |||
| */ | |||
| WxMpPayResult getJSSDKPayResult(String transactionId, String outTradeNo) | |||
| throws WxErrorException; | |||
| WxPayUnifiedOrderResult unifiedOrder(WxPayUnifiedOrderRequest request) throws WxErrorException; | |||
| /** | |||
| * 读取支付结果通知 | |||
| * 详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7 | |||
| * | |||
| * 该接口调用“统一下单”接口,并拼装发起支付请求需要的参数 | |||
| * 详见http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115&token=&lang=zh_CN | |||
| * @param request 请求对象 | |||
| */ | |||
| WxMpPayCallback getJSSDKCallbackData(String xmlData); | |||
| Map<String, String> getPayInfo(WxPayUnifiedOrderRequest request) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| @@ -57,7 +79,14 @@ public interface WxMpPayService { | |||
| * @param keyFile 证书文件对象 | |||
| * @return 退款操作结果 | |||
| */ | |||
| WxMpPayRefundResult refund(WxMpPayRefundRequest request, File keyFile) throws WxErrorException; | |||
| WxPayRefundResult refund(WxPayRefundRequest request, File keyFile) throws WxErrorException; | |||
| /** | |||
| * 读取支付结果通知 | |||
| * 详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7 | |||
| * | |||
| */ | |||
| WxPayJsSDKCallback getJSSDKCallbackData(String xmlData) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| @@ -78,7 +107,7 @@ public interface WxMpPayService { | |||
| * @param request 请求对象 | |||
| * @param keyFile 证书文件对象 | |||
| */ | |||
| WxRedpackResult sendRedpack(WxSendRedpackRequest request, File keyFile) throws WxErrorException; | |||
| WxPaySendRedpackResult sendRedpack(WxPaySendRedpackRequest request, File keyFile) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| @@ -5,6 +5,7 @@ import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
| import me.chanjar.weixin.mp.bean.*; | |||
| import me.chanjar.weixin.mp.bean.result.*; | |||
| import org.apache.http.HttpHost; | |||
| /** | |||
| * 微信API的Service | |||
| @@ -113,6 +114,20 @@ public interface WxMpService { | |||
| */ | |||
| WxMpMassSendResult massOpenIdsMessageSend(WxMpMassOpenIdsMessage message) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 群发消息预览接口 | |||
| * 开发者可通过该接口发送消息给指定用户,在手机端查看消息的样式和排版。为了满足第三方平台开发者的需求,在保留对openID预览能力的同时,增加了对指定微信号发送预览的能力,但该能力每日调用次数有限制(100次),请勿滥用。 | |||
| * 接口调用请求说明 | |||
| * http请求方式: POST | |||
| * https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=ACCESS_TOKEN | |||
| * 详情请见:http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN | |||
| * </pre> | |||
| * | |||
| * @return wxMpMassSendResult | |||
| */ | |||
| WxMpMassSendResult massMessagePreview(WxMpMassPreviewMessage wxMpMassPreviewMessage) throws Exception; | |||
| /** | |||
| * <pre> | |||
| * 长链接转短链接接口 | |||
| @@ -214,6 +229,11 @@ public interface WxMpService { | |||
| */ | |||
| <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException; | |||
| /** | |||
| * 获取代理对象 | |||
| */ | |||
| HttpHost getHttpProxy(); | |||
| /** | |||
| * 注入 {@link WxMpConfigStorage} 的实现 | |||
| */ | |||
| @@ -235,16 +255,6 @@ public interface WxMpService { | |||
| */ | |||
| void setMaxRetryTimes(int maxRetryTimes); | |||
| /** | |||
| * <pre> | |||
| * 预览接口 | |||
| * 详情请见:http://mp.weixin.qq.com/wiki/15/40b6865b893947b764e2de8e4a1fb55f.html#.E9.A2.84.E8.A7.88.E6.8E.A5.E5.8F.A3.E3.80.90.E8.AE.A2.E9.98.85.E5.8F.B7.E4.B8.8E.E6.9C.8D.E5.8A.A1.E5.8F.B7.E8.AE.A4.E8.AF.81.E5.90.8E.E5.9D.87.E5.8F.AF.E7.94.A8.E3.80.91 | |||
| * </pre> | |||
| * | |||
| * @return wxMpMassSendResult | |||
| */ | |||
| WxMpMassSendResult massMessagePreview(WxMpMassPreviewMessage wxMpMassPreviewMessage) throws Exception; | |||
| /** | |||
| * 获取WxMpConfigStorage 对象 | |||
| * | |||
| @@ -1,12 +1,12 @@ | |||
| package me.chanjar.weixin.mp.api; | |||
| import java.util.List; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.mp.bean.WxMpUserQuery; | |||
| import me.chanjar.weixin.mp.bean.result.WxMpUser; | |||
| import me.chanjar.weixin.mp.bean.result.WxMpUserList; | |||
| import java.util.List; | |||
| /** | |||
| * 用户管理相关操作接口 | |||
| * | |||
| @@ -16,8 +16,10 @@ public interface WxMpUserService { | |||
| /** | |||
| * <pre> | |||
| * 设置用户备注名接口 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=设置用户备注名接口 | |||
| * 设置用户备注名 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140838&token=&lang=zh_CN | |||
| * http请求方式: POST(请使用https协议) | |||
| * 接口地址:https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param openid 用户openid | |||
| @@ -25,10 +27,24 @@ public interface WxMpUserService { | |||
| */ | |||
| void userUpdateRemark(String openid, String remark) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获取用户基本信息(语言为默认的zh_CN 简体) | |||
| * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140839&token=&lang=zh_CN | |||
| * http请求方式: GET | |||
| * 接口地址:https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN | |||
| * </pre> | |||
| * | |||
| * @param openid 用户openid | |||
| */ | |||
| WxMpUser userInfo(String openid) throws WxErrorException; | |||
| /** | |||
| * <pre> | |||
| * 获取用户基本信息 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取用户基本信息 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140839&token=&lang=zh_CN | |||
| * http请求方式: GET | |||
| * 接口地址:https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN | |||
| * </pre> | |||
| * | |||
| * @param openid 用户openid | |||
| @@ -39,7 +55,10 @@ public interface WxMpUserService { | |||
| /** | |||
| * <pre> | |||
| * 获取用户基本信息列表 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=批量获取用户基本信息 | |||
| * 开发者可通过该接口来批量获取用户基本信息。最多支持一次拉取100条。 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140839&token=&lang=zh_CN | |||
| * http请求方式: POST | |||
| * 接口地址:https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param openids 用户openid列表 | |||
| @@ -49,7 +68,10 @@ public interface WxMpUserService { | |||
| /** | |||
| * <pre> | |||
| * 获取用户基本信息列表 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=批量获取用户基本信息 | |||
| * 开发者可通过该接口来批量获取用户基本信息。最多支持一次拉取100条。 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140839&token=&lang=zh_CN | |||
| * http请求方式: POST | |||
| * 接口地址:https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=ACCESS_TOKEN | |||
| * </pre> | |||
| * | |||
| * @param userQuery 详细查询参数 | |||
| @@ -58,8 +80,11 @@ public interface WxMpUserService { | |||
| /** | |||
| * <pre> | |||
| * 获取关注者列表 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取关注者列表 | |||
| * 获取用户列表 | |||
| * 公众号可通过本接口来获取帐号的关注者列表,关注者列表由一串OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的)组成。一次拉取调用最多拉取10000个关注者的OpenID,可以通过多次拉取的方式来满足需求。 | |||
| * 详情请见: http://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140840&token=&lang=zh_CN | |||
| * http请求方式: GET(请使用https协议) | |||
| * 接口地址:https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID | |||
| * </pre> | |||
| * | |||
| * @param nextOpenid 可选,第一个拉取的OPENID,null为从头开始拉取 | |||
| @@ -3,6 +3,7 @@ package me.chanjar.weixin.mp.api.impl; | |||
| import java.io.File; | |||
| import java.util.Date; | |||
| import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| @@ -14,7 +15,6 @@ import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
| import me.chanjar.weixin.mp.api.WxMpKefuService; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.WxMpCustomMessage; | |||
| import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfAccountRequest; | |||
| import me.chanjar.weixin.mp.bean.kefu.request.WxMpKfSessionRequest; | |||
| import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfList; | |||
| @@ -41,7 +41,7 @@ public class WxMpKefuServiceImpl implements WxMpKefuService { | |||
| } | |||
| @Override | |||
| public boolean customMessageSend(WxMpCustomMessage message) | |||
| public boolean sendKefuMessage(WxMpKefuMessage message) | |||
| throws WxErrorException { | |||
| String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | |||
| String responseContent = this.wxMpService.post(url, message.toJson()); | |||
| @@ -10,10 +10,10 @@ import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | |||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
| import me.chanjar.weixin.mp.api.WxMpMaterialService; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.WxMpMaterial; | |||
| import me.chanjar.weixin.mp.bean.WxMpMaterialArticleUpdate; | |||
| import me.chanjar.weixin.mp.bean.WxMpMaterialNews; | |||
| import me.chanjar.weixin.mp.bean.result.*; | |||
| import me.chanjar.weixin.mp.bean.material.WxMpMaterial; | |||
| import me.chanjar.weixin.mp.bean.material.WxMpMaterialArticleUpdate; | |||
| import me.chanjar.weixin.mp.bean.material.WxMpMaterialNews; | |||
| import me.chanjar.weixin.mp.bean.material.*; | |||
| import me.chanjar.weixin.mp.util.http.*; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| @@ -37,8 +37,13 @@ public class WxMpMaterialServiceImpl implements WxMpMaterialService { | |||
| } | |||
| @Override | |||
| public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) throws WxErrorException, IOException { | |||
| return this.mediaUpload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType)); | |||
| public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream) throws WxErrorException { | |||
| try { | |||
| return this.mediaUpload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType)); | |||
| } catch (IOException e) { | |||
| e.printStackTrace(); | |||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg(e.getMessage()).build()); | |||
| } | |||
| } | |||
| @Override | |||
| @@ -1,19 +1,21 @@ | |||
| package me.chanjar.weixin.mp.api.impl; | |||
| import com.google.common.collect.Lists; | |||
| import com.google.common.collect.Maps; | |||
| import com.thoughtworks.xstream.XStream; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import me.chanjar.weixin.common.annotation.Required; | |||
| import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.util.BeanUtils; | |||
| import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||
| import me.chanjar.weixin.mp.api.WxMpPayService; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.bean.pay.*; | |||
| import me.chanjar.weixin.mp.bean.pay.WxPayJsSDKCallback; | |||
| import me.chanjar.weixin.mp.bean.pay.result.WxPayOrderCloseResult; | |||
| import me.chanjar.weixin.mp.bean.pay.request.*; | |||
| import me.chanjar.weixin.mp.bean.pay.result.*; | |||
| import org.apache.commons.codec.digest.DigestUtils; | |||
| import org.apache.commons.lang3.ArrayUtils; | |||
| import org.apache.commons.lang3.StringUtils; | |||
| import org.apache.http.Consts; | |||
| import org.apache.http.client.config.RequestConfig; | |||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||
| import org.apache.http.client.methods.HttpPost; | |||
| import org.apache.http.conn.ssl.DefaultHostnameVerifier; | |||
| @@ -23,15 +25,15 @@ import org.apache.http.impl.client.CloseableHttpClient; | |||
| import org.apache.http.impl.client.HttpClients; | |||
| import org.apache.http.ssl.SSLContexts; | |||
| import org.apache.http.util.EntityUtils; | |||
| import org.joor.Reflect; | |||
| import org.slf4j.Logger; | |||
| import org.slf4j.LoggerFactory; | |||
| import javax.net.ssl.SSLContext; | |||
| import java.io.File; | |||
| import java.io.FileInputStream; | |||
| import java.lang.reflect.Field; | |||
| import java.io.IOException; | |||
| import java.security.KeyStore; | |||
| import java.util.*; | |||
| import java.util.Map.Entry; | |||
| /** | |||
| * Created by Binary Wang on 2016/7/28. | |||
| @@ -40,6 +42,8 @@ import java.util.Map.Entry; | |||
| */ | |||
| public class WxMpPayServiceImpl implements WxMpPayService { | |||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | |||
| private static final String PAY_BASE_URL = "https://api.mch.weixin.qq.com"; | |||
| private static final String[] TRADE_TYPES = new String[]{"JSAPI","NATIVE", "APP"}; | |||
| private static final String[] REFUND_ACCOUNT = new String[]{"REFUND_SOURCE_RECHARGE_FUNDS", | |||
| @@ -52,94 +56,42 @@ public class WxMpPayServiceImpl implements WxMpPayService { | |||
| } | |||
| @Override | |||
| public WxMpPayResult getJSSDKPayResult(String transactionId, | |||
| String outTradeNo) throws WxErrorException { | |||
| String nonce_str = System.currentTimeMillis() + ""; | |||
| SortedMap<String, String> packageParams = new TreeMap<>(); | |||
| packageParams.put("appid", | |||
| this.wxMpService.getWxMpConfigStorage().getAppId()); | |||
| packageParams.put("mch_id", | |||
| this.wxMpService.getWxMpConfigStorage().getPartnerId()); | |||
| if (transactionId != null && !"".equals(transactionId.trim())) { | |||
| packageParams.put("transaction_id", transactionId); | |||
| } else if (outTradeNo != null && !"".equals(outTradeNo.trim())) { | |||
| packageParams.put("out_trade_no", outTradeNo); | |||
| } else { | |||
| throw new IllegalArgumentException( | |||
| "Either 'transactionId' or 'outTradeNo' must be given."); | |||
| } | |||
| packageParams.put("nonce_str", nonce_str); | |||
| packageParams.put("sign", this.createSign(packageParams, | |||
| this.wxMpService.getWxMpConfigStorage().getPartnerKey())); | |||
| StringBuilder request = new StringBuilder("<xml>"); | |||
| for (Map.Entry<String, String> para : packageParams.entrySet()) { | |||
| request.append(String.format("<%s>%s</%s>", para.getKey(), | |||
| para.getValue(), para.getKey())); | |||
| } | |||
| request.append("</xml>"); | |||
| String url = PAY_BASE_URL + "/pay/orderquery"; | |||
| String responseContent = this.wxMpService.post(url, request.toString()); | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.alias("xml", WxMpPayResult.class); | |||
| return (WxMpPayResult) xstream.fromXML(responseContent); | |||
| } | |||
| @Override | |||
| public WxMpPayCallback getJSSDKCallbackData(String xmlData) { | |||
| try { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.alias("xml", WxMpPayCallback.class); | |||
| return (WxMpPayCallback) xstream.fromXML(xmlData); | |||
| } catch (Exception e) { | |||
| e.printStackTrace(); | |||
| } | |||
| return new WxMpPayCallback(); | |||
| } | |||
| @Override | |||
| public WxMpPayRefundResult refund(WxMpPayRefundRequest request, File keyFile) | |||
| public WxPayRefundResult refund(WxPayRefundRequest request, File keyFile) | |||
| throws WxErrorException { | |||
| checkParameters(request); | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxMpPayRefundResult.class); | |||
| xstream.processAnnotations(WxMpPayRefundRequest.class); | |||
| xstream.processAnnotations(WxPayRefundRequest.class); | |||
| xstream.processAnnotations(WxPayRefundResult.class); | |||
| request.setAppid(this.wxMpService.getWxMpConfigStorage().getAppId()); | |||
| String partnerId = this.wxMpService.getWxMpConfigStorage().getPartnerId(); | |||
| request.setMchId(partnerId); | |||
| request.setNonceStr( System.currentTimeMillis() + ""); | |||
| request.setOpUserId(partnerId); | |||
| String sign = this.createSign(this.xmlBean2Map(request), this.wxMpService.getWxMpConfigStorage().getPartnerKey()); | |||
| String sign = this.createSign(BeanUtils.xmlBean2Map(request), this.wxMpService.getWxMpConfigStorage().getPartnerKey()); | |||
| request.setSign(sign); | |||
| String url = PAY_BASE_URL + "/secapi/pay/refund"; | |||
| String responseContent = this.executeRequestWithKeyFile(url, keyFile, xstream.toXML(request), partnerId); | |||
| WxMpPayRefundResult wxMpPayRefundResult = (WxMpPayRefundResult) xstream.fromXML(responseContent); | |||
| if (!"SUCCESS".equalsIgnoreCase(wxMpPayRefundResult.getResultCode()) | |||
| || !"SUCCESS".equalsIgnoreCase(wxMpPayRefundResult.getReturnCode())) { | |||
| WxError error = new WxError(); | |||
| error.setErrorCode(-1); | |||
| error.setErrorMsg("return_code:" + wxMpPayRefundResult.getReturnCode() | |||
| + ";return_msg:" + wxMpPayRefundResult.getReturnMsg() | |||
| + ";result_code:" + wxMpPayRefundResult.getResultCode() + ";err_code" | |||
| + wxMpPayRefundResult.getErrCode() + ";err_code_des" | |||
| + wxMpPayRefundResult.getErrCodeDes()); | |||
| throw new WxErrorException(error); | |||
| } | |||
| WxPayRefundResult result = (WxPayRefundResult) xstream.fromXML(responseContent); | |||
| this.checkResult(result); | |||
| return result; | |||
| } | |||
| return wxMpPayRefundResult; | |||
| private void checkResult(WxPayBaseResult result) throws WxErrorException { | |||
| if (!"SUCCESS".equalsIgnoreCase(result.getReturnCode()) | |||
| || !"SUCCESS".equalsIgnoreCase(result.getResultCode())) { | |||
| throw new WxErrorException(WxError.newBuilder().setErrorCode(-1) | |||
| .setErrorMsg("返回代码:" + result.getReturnCode() + ", 返回信息: " | |||
| + result.getReturnMsg() + ", 结果代码: " + result.getResultCode() + ", 错误代码: " | |||
| + result.getErrCode() + ", 错误详情: " + result.getErrCodeDes()) | |||
| .build()); | |||
| } | |||
| } | |||
| private void checkParameters(WxMpPayRefundRequest request) { | |||
| checkNotNullParams(request); | |||
| private void checkParameters(WxPayRefundRequest request) throws WxErrorException { | |||
| BeanUtils.checkRequiredFields(request); | |||
| if (StringUtils.isNotBlank(request.getRefundAccount())) { | |||
| if(!ArrayUtils.contains(REFUND_ACCOUNT, request.getRefundAccount())){ | |||
| @@ -152,6 +104,18 @@ public class WxMpPayServiceImpl implements WxMpPayService { | |||
| } | |||
| } | |||
| @Override | |||
| public WxPayJsSDKCallback getJSSDKCallbackData(String xmlData) throws WxErrorException { | |||
| try { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.alias("xml", WxPayJsSDKCallback.class); | |||
| return (WxPayJsSDKCallback) xstream.fromXML(xmlData); | |||
| } catch (Exception e) { | |||
| e.printStackTrace(); | |||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg("发生异常" + e.getMessage()).build()); | |||
| } | |||
| } | |||
| @Override | |||
| public boolean checkJSSDKCallbackDataSignature(Map<String, String> kvm, | |||
| String signature) { | |||
| @@ -160,18 +124,18 @@ public class WxMpPayServiceImpl implements WxMpPayService { | |||
| } | |||
| @Override | |||
| public WxRedpackResult sendRedpack(WxSendRedpackRequest request, File keyFile) | |||
| public WxPaySendRedpackResult sendRedpack(WxPaySendRedpackRequest request, File keyFile) | |||
| throws WxErrorException { | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxSendRedpackRequest.class); | |||
| xstream.processAnnotations(WxRedpackResult.class); | |||
| xstream.processAnnotations(WxPaySendRedpackRequest.class); | |||
| xstream.processAnnotations(WxPaySendRedpackResult.class); | |||
| request.setWxAppid(this.wxMpService.getWxMpConfigStorage().getAppId()); | |||
| String mchId = this.wxMpService.getWxMpConfigStorage().getPartnerId(); | |||
| request.setMchId(mchId); | |||
| request.setNonceStr(System.currentTimeMillis() + ""); | |||
| String sign = this.createSign(this.xmlBean2Map(request), | |||
| String sign = this.createSign(BeanUtils.xmlBean2Map(request), | |||
| this.wxMpService.getWxMpConfigStorage().getPartnerKey()); | |||
| request.setSign(sign); | |||
| @@ -182,38 +146,9 @@ public class WxMpPayServiceImpl implements WxMpPayService { | |||
| } | |||
| String responseContent = this.executeRequestWithKeyFile(url, keyFile, xstream.toXML(request), mchId); | |||
| WxRedpackResult redpackResult = (WxRedpackResult) xstream | |||
| WxPaySendRedpackResult result = (WxPaySendRedpackResult) xstream | |||
| .fromXML(responseContent); | |||
| if ("FAIL".equals(redpackResult.getResultCode())) { | |||
| throw new WxErrorException(WxError.newBuilder() | |||
| .setErrorMsg( | |||
| redpackResult.getErrCode() + ":" + redpackResult.getErrCodeDes()) | |||
| .build()); | |||
| } | |||
| return redpackResult; | |||
| } | |||
| private Map<String, String> xmlBean2Map(Object bean) { | |||
| Map<String, String> result = Maps.newHashMap(); | |||
| for (Entry<String, Reflect> entry : Reflect.on(bean).fields().entrySet()) { | |||
| Reflect reflect = entry.getValue(); | |||
| if (reflect.get() == null) { | |||
| continue; | |||
| } | |||
| try { | |||
| Field field = bean.getClass().getDeclaredField(entry.getKey()); | |||
| if (field.isAnnotationPresent(XStreamAlias.class)) { | |||
| result.put(field.getAnnotation(XStreamAlias.class).value(), | |||
| reflect.get().toString()); | |||
| } | |||
| } catch (NoSuchFieldException | SecurityException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| this.checkResult(result); | |||
| return result; | |||
| } | |||
| @@ -241,38 +176,93 @@ public class WxMpPayServiceImpl implements WxMpPayService { | |||
| } | |||
| @Override | |||
| public WxUnifiedOrderResult unifiedOrder(WxUnifiedOrderRequest request) | |||
| public WxPayOrderQueryResult queryOrder(String transactionId, String outTradeNo) throws WxErrorException { | |||
| if ((StringUtils.isBlank(transactionId) && StringUtils.isBlank(outTradeNo)) || | |||
| (StringUtils.isNotBlank(transactionId) && StringUtils.isNotBlank(outTradeNo))) { | |||
| throw new IllegalArgumentException("transaction_id 和 out_trade_no 不能同时存在或同时为空,必须二选一"); | |||
| } | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxPayOrderQueryRequest.class); | |||
| xstream.processAnnotations(WxPayOrderQueryResult.class); | |||
| WxPayOrderQueryRequest request = new WxPayOrderQueryRequest(); | |||
| request.setOutTradeNo(StringUtils.trimToNull(outTradeNo)); | |||
| request.setTransactionId(StringUtils.trimToNull(transactionId)); | |||
| request.setAppid(this.wxMpService.getWxMpConfigStorage().getAppId()); | |||
| request.setMchId(this.wxMpService.getWxMpConfigStorage().getPartnerId()); | |||
| request.setNonceStr(System.currentTimeMillis() + ""); | |||
| String sign = this.createSign(BeanUtils.xmlBean2Map(request), | |||
| this.wxMpService.getWxMpConfigStorage().getPartnerKey()); | |||
| request.setSign(sign); | |||
| String url = PAY_BASE_URL + "/pay/orderquery"; | |||
| String responseContent = this.executeRequest(url, xstream.toXML(request)); | |||
| WxPayOrderQueryResult result = (WxPayOrderQueryResult) xstream.fromXML(responseContent); | |||
| result.composeCoupons(responseContent); | |||
| this.checkResult(result); | |||
| return result; | |||
| } | |||
| @Override | |||
| public WxPayOrderCloseResult closeOrder(String outTradeNo) throws WxErrorException { | |||
| if (StringUtils.isBlank(outTradeNo)) { | |||
| throw new IllegalArgumentException("out_trade_no 不能为空"); | |||
| } | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxPayOrderCloseRequest.class); | |||
| xstream.processAnnotations(WxPayOrderCloseResult.class); | |||
| WxPayOrderCloseRequest request = new WxPayOrderCloseRequest(); | |||
| request.setOutTradeNo(StringUtils.trimToNull(outTradeNo)); | |||
| request.setAppid(this.wxMpService.getWxMpConfigStorage().getAppId()); | |||
| request.setMchId(this.wxMpService.getWxMpConfigStorage().getPartnerId()); | |||
| request.setNonceStr(System.currentTimeMillis() + ""); | |||
| String sign = this.createSign(BeanUtils.xmlBean2Map(request), | |||
| this.wxMpService.getWxMpConfigStorage().getPartnerKey()); | |||
| request.setSign(sign); | |||
| String url = PAY_BASE_URL + "/pay/closeorder"; | |||
| String responseContent = this.executeRequest(url, xstream.toXML(request)); | |||
| WxPayOrderCloseResult result = (WxPayOrderCloseResult) xstream.fromXML(responseContent); | |||
| this.checkResult(result); | |||
| return result; | |||
| } | |||
| @Override | |||
| public WxPayUnifiedOrderResult unifiedOrder(WxPayUnifiedOrderRequest request) | |||
| throws WxErrorException { | |||
| checkParameters(request); | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxUnifiedOrderRequest.class); | |||
| xstream.processAnnotations(WxUnifiedOrderResult.class); | |||
| xstream.processAnnotations(WxPayUnifiedOrderRequest.class); | |||
| xstream.processAnnotations(WxPayUnifiedOrderResult.class); | |||
| request.setAppid(this.wxMpService.getWxMpConfigStorage().getAppId()); | |||
| request.setMchId(this.wxMpService.getWxMpConfigStorage().getPartnerId()); | |||
| request.setNonceStr(System.currentTimeMillis() + ""); | |||
| String sign = this.createSign(this.xmlBean2Map(request), | |||
| String sign = this.createSign(BeanUtils.xmlBean2Map(request), | |||
| this.wxMpService.getWxMpConfigStorage().getPartnerKey()); | |||
| request.setSign(sign); | |||
| String url = PAY_BASE_URL + "/pay/unifiedorder"; | |||
| String responseContent = this.wxMpService.post(url, xstream.toXML(request)); | |||
| WxUnifiedOrderResult result = (WxUnifiedOrderResult) xstream | |||
| String responseContent = this.executeRequest(url, xstream.toXML(request)); | |||
| WxPayUnifiedOrderResult result = (WxPayUnifiedOrderResult) xstream | |||
| .fromXML(responseContent); | |||
| if ("FAIL".equals(result.getResultCode())) { | |||
| throw new WxErrorException(WxError.newBuilder() | |||
| .setErrorMsg(result.getErrCode() + ":" + result.getErrCodeDes()) | |||
| .build()); | |||
| } | |||
| this.checkResult(result); | |||
| return result; | |||
| } | |||
| private void checkParameters(WxUnifiedOrderRequest request) { | |||
| checkNotNullParams(request); | |||
| private void checkParameters(WxPayUnifiedOrderRequest request) throws WxErrorException { | |||
| BeanUtils.checkRequiredFields(request); | |||
| if (! ArrayUtils.contains(TRADE_TYPES, request.getTradeType())) { | |||
| throw new IllegalArgumentException("trade_type目前必须为" + Arrays.toString(TRADE_TYPES) + "其中之一"); | |||
| @@ -287,40 +277,9 @@ public class WxMpPayServiceImpl implements WxMpPayService { | |||
| } | |||
| } | |||
| private void checkNotNullParams(Object request) { | |||
| List<String> nullFields = Lists.newArrayList(); | |||
| for (Entry<String, Reflect> entry : Reflect.on(request).fields() | |||
| .entrySet()) { | |||
| Reflect reflect = entry.getValue(); | |||
| try { | |||
| Field field = request.getClass().getDeclaredField(entry.getKey()); | |||
| if (field.isAnnotationPresent(Required.class) | |||
| && reflect.get() == null) { | |||
| nullFields.add(entry.getKey()); | |||
| } | |||
| } catch (NoSuchFieldException | SecurityException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| if (!nullFields.isEmpty()) { | |||
| throw new IllegalArgumentException("必填字段[" + nullFields + "]必须提供值"); | |||
| } | |||
| } | |||
| @Override | |||
| public Map<String, String> getPayInfo(WxUnifiedOrderRequest request) throws WxErrorException { | |||
| WxUnifiedOrderResult unifiedOrderResult = this.unifiedOrder(request); | |||
| if (!"SUCCESS".equalsIgnoreCase(unifiedOrderResult.getReturnCode()) | |||
| || !"SUCCESS".equalsIgnoreCase(unifiedOrderResult.getResultCode())) { | |||
| throw new WxErrorException(WxError.newBuilder().setErrorCode(-1) | |||
| .setErrorMsg("return_code:" + unifiedOrderResult.getReturnCode() + ";return_msg:" | |||
| + unifiedOrderResult.getReturnMsg() + ";result_code:" + unifiedOrderResult.getResultCode() + ";err_code" | |||
| + unifiedOrderResult.getErrCode() + ";err_code_des" + unifiedOrderResult.getErrCodeDes()) | |||
| .build()); | |||
| } | |||
| public Map<String, String> getPayInfo(WxPayUnifiedOrderRequest request) throws WxErrorException { | |||
| WxPayUnifiedOrderResult unifiedOrderResult = this.unifiedOrder(request); | |||
| String prepayId = unifiedOrderResult.getPrepayId(); | |||
| if (StringUtils.isBlank(prepayId)) { | |||
| throw new RuntimeException(String.format("Failed to get prepay id due to error code '%s'(%s).", | |||
| @@ -345,7 +304,7 @@ public class WxMpPayServiceImpl implements WxMpPayService { | |||
| @Override | |||
| public WxEntPayResult entPay(WxEntPayRequest request, File keyFile) throws WxErrorException { | |||
| checkNotNullParams(request); | |||
| BeanUtils.checkRequiredFields(request); | |||
| XStream xstream = XStreamInitializer.getInstance(); | |||
| xstream.processAnnotations(WxEntPayRequest.class); | |||
| @@ -355,17 +314,14 @@ public class WxMpPayServiceImpl implements WxMpPayService { | |||
| request.setMchId(this.wxMpService.getWxMpConfigStorage().getPartnerId()); | |||
| request.setNonceStr(System.currentTimeMillis() + ""); | |||
| String sign = this.createSign(xmlBean2Map(request), this.wxMpService.getWxMpConfigStorage().getPartnerKey()); | |||
| String sign = this.createSign(BeanUtils.xmlBean2Map(request), this.wxMpService.getWxMpConfigStorage().getPartnerKey()); | |||
| request.setSign(sign); | |||
| String url = PAY_BASE_URL + "/mmpaymkttransfers/promotion/transfers"; | |||
| String responseContent = this.executeRequestWithKeyFile(url, keyFile, xstream.toXML(request), request.getMchId()); | |||
| WxEntPayResult result = (WxEntPayResult) xstream.fromXML(responseContent); | |||
| if ("FAIL".equals(result.getResultCode())) { | |||
| throw new WxErrorException( | |||
| WxError.newBuilder().setErrorMsg(result.getErrCode() + ":" + result.getErrCodeDes()).build()); | |||
| } | |||
| this.checkResult(result); | |||
| return result; | |||
| } | |||
| @@ -380,20 +336,39 @@ public class WxMpPayServiceImpl implements WxMpPayService { | |||
| request.setMchId(this.wxMpService.getWxMpConfigStorage().getPartnerId()); | |||
| request.setNonceStr(System.currentTimeMillis() + ""); | |||
| String sign = this.createSign(xmlBean2Map(request), this.wxMpService.getWxMpConfigStorage().getPartnerKey()); | |||
| String sign = this.createSign(BeanUtils.xmlBean2Map(request), this.wxMpService.getWxMpConfigStorage().getPartnerKey()); | |||
| request.setSign(sign); | |||
| String url = PAY_BASE_URL + "/mmpaymkttransfers/gettransferinfo"; | |||
| String responseContent = this.executeRequestWithKeyFile(url, keyFile, xstream.toXML(request), request.getMchId()); | |||
| WxEntPayQueryResult result = (WxEntPayQueryResult) xstream.fromXML(responseContent); | |||
| if ("FAIL".equals(result.getResultCode())) { | |||
| throw new WxErrorException( | |||
| WxError.newBuilder().setErrorMsg(result.getErrCode() + ":" + result.getErrCodeDes()).build()); | |||
| } | |||
| this.checkResult(result); | |||
| return result; | |||
| } | |||
| private String executeRequest( String url, String requestStr) throws WxErrorException { | |||
| HttpPost httpPost = new HttpPost(url); | |||
| if (this.wxMpService.getHttpProxy() != null) { | |||
| httpPost.setConfig(RequestConfig.custom().setProxy(this.wxMpService.getHttpProxy()).build()); | |||
| } | |||
| try (CloseableHttpClient httpclient = HttpClients.custom().build()) { | |||
| httpPost.setEntity(new StringEntity(new String(requestStr.getBytes("UTF-8"), "ISO-8859-1"))); | |||
| try (CloseableHttpResponse response = httpclient.execute(httpPost)) { | |||
| String result = EntityUtils.toString(response.getEntity(), Consts.UTF_8); | |||
| this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}",url, requestStr, result); | |||
| return result; | |||
| } | |||
| } catch (IOException e) { | |||
| this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[EXCEPTION]: {}", url, requestStr, e.getMessage()); | |||
| throw new WxErrorException(WxError.newBuilder().setErrorCode(-1).setErrorMsg(e.getMessage()).build(), e); | |||
| }finally { | |||
| httpPost.releaseConnection(); | |||
| } | |||
| } | |||
| private String executeRequestWithKeyFile( String url, File keyFile, String requestStr, String mchId) throws WxErrorException { | |||
| try (FileInputStream inputStream = new FileInputStream(keyFile)) { | |||
| KeyStore keyStore = KeyStore.getInstance("PKCS12"); | |||
| @@ -403,16 +378,24 @@ public class WxMpPayServiceImpl implements WxMpPayService { | |||
| SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext, new String[] { "TLSv1" }, null, | |||
| new DefaultHostnameVerifier()); | |||
| HttpPost httpPost = new HttpPost(url); | |||
| if (this.wxMpService.getHttpProxy() != null) { | |||
| httpPost.setConfig(RequestConfig.custom().setProxy(this.wxMpService.getHttpProxy()).build()); | |||
| } | |||
| try (CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build()) { | |||
| HttpPost httpPost = new HttpPost(url); | |||
| httpPost.setEntity(new StringEntity(new String(requestStr.getBytes("UTF-8"), "ISO-8859-1"))); | |||
| try (CloseableHttpResponse response = httpclient.execute(httpPost)) { | |||
| return EntityUtils.toString(response.getEntity()); | |||
| String result = EntityUtils.toString(response.getEntity(), Consts.UTF_8); | |||
| this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}",url, requestStr, result); | |||
| return result; | |||
| } | |||
| }finally { | |||
| httpPost.releaseConnection(); | |||
| } | |||
| } catch (Exception e) { | |||
| throw new WxErrorException(WxError.newBuilder().setErrorMsg(e.getMessage()).build(), e); | |||
| this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[EXCEPTION]: {}", url, requestStr, e.getMessage()); | |||
| throw new WxErrorException(WxError.newBuilder().setErrorCode(-1).setErrorMsg(e.getMessage()).build(), e); | |||
| } | |||
| } | |||
| @@ -33,7 +33,7 @@ public class WxMpServiceImpl implements WxMpService { | |||
| private static final JsonParser JSON_PARSER = new JsonParser(); | |||
| protected final Logger log = LoggerFactory.getLogger(WxMpServiceImpl.class); | |||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | |||
| /** | |||
| * 全局的是否正在刷新access token的锁 | |||
| @@ -101,6 +101,7 @@ public class WxMpServiceImpl implements WxMpService { | |||
| if (forceRefresh) { | |||
| this.configStorage.expireAccessToken(); | |||
| } | |||
| if (this.configStorage.isAccessTokenExpired()) { | |||
| synchronized (this.globalAccessTokenRefreshLock) { | |||
| if (this.configStorage.isAccessTokenExpired()) { | |||
| @@ -411,8 +412,11 @@ public class WxMpServiceImpl implements WxMpService { | |||
| if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | |||
| // 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||
| this.configStorage.expireAccessToken(); | |||
| return this.execute(executor, uri, data); | |||
| if(this.configStorage.autoRefreshToken()){ | |||
| return this.execute(executor, uri, data); | |||
| } | |||
| } | |||
| if (error.getErrorCode() != 0) { | |||
| this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", uri, data, | |||
| error); | |||
| @@ -425,6 +429,7 @@ public class WxMpServiceImpl implements WxMpService { | |||
| } | |||
| } | |||
| @Override | |||
| public HttpHost getHttpProxy() { | |||
| return this.httpProxy; | |||
| } | |||
| @@ -1,23 +1,19 @@ | |||
| package me.chanjar.weixin.mp.api.impl; | |||
| import com.google.common.collect.Lists; | |||
| import com.google.gson.JsonObject; | |||
| import com.google.gson.JsonParser; | |||
| import com.google.gson.reflect.TypeToken; | |||
| import me.chanjar.weixin.common.annotation.Required; | |||
| import me.chanjar.weixin.common.bean.result.WxError; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.common.util.BeanUtils; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.api.WxMpStoreService; | |||
| import me.chanjar.weixin.mp.bean.store.WxMpStoreBaseInfo; | |||
| import me.chanjar.weixin.mp.bean.store.WxMpStoreInfo; | |||
| import me.chanjar.weixin.mp.bean.store.WxMpStoreListResult; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import org.joor.Reflect; | |||
| import java.lang.reflect.Field; | |||
| import java.util.List; | |||
| import java.util.Map.Entry; | |||
| /** | |||
| * Created by Binary Wang on 2016/9/26. | |||
| @@ -35,7 +31,7 @@ public class WxMpStoreServiceImpl implements WxMpStoreService { | |||
| @Override | |||
| public void add(WxMpStoreBaseInfo request) throws WxErrorException { | |||
| checkParameters(request); | |||
| BeanUtils.checkRequiredFields(request); | |||
| String url = API_BASE_URL + "/addpoi"; | |||
| String response = this.wxMpService.post(url, request.toJson()); | |||
| @@ -71,28 +67,6 @@ public class WxMpStoreServiceImpl implements WxMpStoreService { | |||
| } | |||
| } | |||
| private void checkParameters(WxMpStoreBaseInfo request) { | |||
| List<String> nullFields = Lists.newArrayList(); | |||
| for (Entry<String, Reflect> entry : Reflect.on(request).fields() | |||
| .entrySet()) { | |||
| Reflect reflect = entry.getValue(); | |||
| try { | |||
| Field field = request.getClass().getDeclaredField(entry.getKey()); | |||
| if (field.isAnnotationPresent(Required.class) | |||
| && reflect.get() == null) { | |||
| nullFields.add(entry.getKey()); | |||
| } | |||
| } catch (NoSuchFieldException | SecurityException e) { | |||
| e.printStackTrace(); | |||
| } | |||
| } | |||
| if (!nullFields.isEmpty()) { | |||
| throw new IllegalArgumentException("必填字段[" + nullFields + "]必须提供值"); | |||
| } | |||
| } | |||
| @Override | |||
| public WxMpStoreListResult list(int begin, int limit) | |||
| throws WxErrorException { | |||
| @@ -1,9 +1,6 @@ | |||
| package me.chanjar.weixin.mp.api.impl; | |||
| import java.util.List; | |||
| import com.google.gson.JsonObject; | |||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||
| import me.chanjar.weixin.mp.api.WxMpService; | |||
| import me.chanjar.weixin.mp.api.WxMpUserService; | |||
| @@ -11,6 +8,8 @@ import me.chanjar.weixin.mp.bean.WxMpUserQuery; | |||
| import me.chanjar.weixin.mp.bean.result.WxMpUser; | |||
| import me.chanjar.weixin.mp.bean.result.WxMpUserList; | |||
| import java.util.List; | |||
| /** | |||
| * Created by Binary Wang on 2016/7/21. | |||
| */ | |||
| @@ -31,6 +30,11 @@ public class WxMpUserServiceImpl implements WxMpUserService { | |||
| this.wxMpService.post(url, json.toString()); | |||
| } | |||
| @Override | |||
| public WxMpUser userInfo(String openid) throws WxErrorException { | |||
| return this.userInfo(openid, null); | |||
| } | |||
| @Override | |||
| public WxMpUser userInfo(String openid, String lang) throws WxErrorException { | |||
| String url = API_URL_PREFIX + "/info"; | |||
| @@ -1,6 +1,8 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| /** | |||
| * 微信卡券 | |||
| * | |||
| @@ -61,12 +63,6 @@ public class WxMpCard { | |||
| @Override | |||
| public String toString() { | |||
| return "WxMpCard{" + | |||
| "cardId='" + this.cardId + '\'' + | |||
| ", beginTime=" + this.beginTime + | |||
| ", endTime=" + this.endTime + | |||
| ", userCardStatus='" + this.userCardStatus + '\'' + | |||
| ", canConsume=" + this.canConsume + | |||
| '}'; | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.io.Serializable; | |||
| @@ -14,7 +15,7 @@ import java.util.List; | |||
| public class WxMpMassNews implements Serializable { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = 565937155013581016L; | |||
| private List<WxMpMassNewsArticle> articles = new ArrayList<>(); | |||
| @@ -137,14 +138,12 @@ public class WxMpMassNews implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return "WxMpMassNewsArticle [" + "thumbMediaId=" + this.thumbMediaId + ", author=" + this.author + ", title=" + this.title + | |||
| ", contentSourceUrl=" + this.contentSourceUrl + ", content=" + this.content + ", digest=" + this.digest + | |||
| ", showCoverPic=" + this.showCoverPic + "]"; | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "WxMpMassNews [" + "articles=" + this.articles + "]"; | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| @@ -1,13 +1,13 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.io.Serializable; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| /** | |||
| * OpenId列表群发的消息 | |||
| * openid列表群发的消息 | |||
| * | |||
| * @author chanjarster | |||
| */ | |||
| @@ -64,17 +64,26 @@ public class WxMpMassOpenIdsMessage implements Serializable { | |||
| } | |||
| /** | |||
| * OpenId列表,最多支持10,000个 | |||
| * openid列表,最多支持10,000个 | |||
| */ | |||
| public List<String> getToUsers() { | |||
| return this.toUsers; | |||
| } | |||
| /** | |||
| * 添加OpenId,最多支持10,000个 | |||
| * @param openId | |||
| * 添加openid,最多支持10,000个 | |||
| * @param openid | |||
| */ | |||
| public void addUser(String openId) { | |||
| this.toUsers.add(openId); | |||
| public void addUser(String openid) { | |||
| this.toUsers.add(openid); | |||
| } | |||
| /** | |||
| * 提供set方法,方便客户端直接设置所有群发对象的openid列表 | |||
| * @param toUsers | |||
| */ | |||
| public void setToUsers(List<String> toUsers) { | |||
| this.toUsers = toUsers; | |||
| } | |||
| } | |||
| @@ -8,9 +8,6 @@ import java.io.Serializable; | |||
| * @author miller | |||
| */ | |||
| public class WxMpMassPreviewMessage implements Serializable { | |||
| /** | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = 9095211638358424020L; | |||
| private String toWxUsername; | |||
| private String msgType; | |||
| @@ -1,13 +1,12 @@ | |||
| package me.chanjar.weixin.mp.bean.datacube; | |||
| import java.util.List; | |||
| import com.google.gson.JsonParser; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import com.google.gson.reflect.TypeToken; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.util.List; | |||
| /** | |||
| * 图文分析数据接口返回结果对象 | |||
| * @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||
| @@ -26,7 +25,9 @@ public class WxDataCubeArticleResult extends WxDataCubeBaseResult { | |||
| /** | |||
| * msgid | |||
| * 请注意:这里的msgid实际上是由msgid(图文消息id,这也就是群发接口调用后返回的msg_data_id)和index(消息次序索引)组成, 例如12003_3, 其中12003是msgid,即一次群发的消息的id; 3为index,假设该次群发的图文消息共5个文章(因为可能为多图文),3表示5个中的第3个 | |||
| * 请注意:这里的msgid实际上是由msgid(图文消息id,这也就是群发接口调用后返回的msg_data_id) | |||
| * 和index(消息次序索引)组成, 例如12003_3, 其中12003是msgid,即一次群发的消息的id; 3为index, | |||
| * 假设该次群发的图文消息共5个文章(因为可能为多图文),3表示5个中的第3个 | |||
| */ | |||
| @SerializedName("msgid") | |||
| private String msgId; | |||
| @@ -1,8 +1,7 @@ | |||
| package me.chanjar.weixin.mp.bean.datacube; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| /** | |||
| * 统计接口的共用属性类 | |||
| @@ -12,7 +11,7 @@ import org.apache.commons.lang3.builder.ToStringStyle; | |||
| public class WxDataCubeBaseResult { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| /** | |||
| @@ -1,17 +1,14 @@ | |||
| package me.chanjar.weixin.mp.bean.datacube; | |||
| import java.io.Serializable; | |||
| import java.util.Date; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.JsonParser; | |||
| import com.google.gson.reflect.TypeToken; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.io.Serializable; | |||
| import java.util.Date; | |||
| import java.util.List; | |||
| /** | |||
| * <pre> | |||
| * 累计用户数据接口的返回JSON数据包 | |||
| @@ -46,7 +43,7 @@ public class WxDataCubeUserCumulate implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public static List<WxDataCubeUserCumulate> fromJson(String json) { | |||
| @@ -1,17 +1,14 @@ | |||
| package me.chanjar.weixin.mp.bean.datacube; | |||
| import java.io.Serializable; | |||
| import java.util.Date; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.JsonParser; | |||
| import com.google.gson.reflect.TypeToken; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.io.Serializable; | |||
| import java.util.Date; | |||
| import java.util.List; | |||
| /** | |||
| * <pre> | |||
| * 用户增减数据接口的返回JSON数据包 | |||
| @@ -65,7 +62,7 @@ public class WxDataCubeUserSummary implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public static List<WxDataCubeUserSummary> fromJson(String json) { | |||
| @@ -1,24 +1,25 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.kefu; | |||
| import java.io.Serializable; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import me.chanjar.weixin.mp.bean.custombuilder.ImageBuilder; | |||
| import me.chanjar.weixin.mp.bean.custombuilder.MusicBuilder; | |||
| import me.chanjar.weixin.mp.bean.custombuilder.NewsBuilder; | |||
| import me.chanjar.weixin.mp.bean.custombuilder.TextBuilder; | |||
| import me.chanjar.weixin.mp.bean.custombuilder.VideoBuilder; | |||
| import me.chanjar.weixin.mp.bean.custombuilder.VoiceBuilder; | |||
| import me.chanjar.weixin.mp.bean.custombuilder.WxCardBuilder; | |||
| import me.chanjar.weixin.mp.builder.kefu.ImageBuilder; | |||
| import me.chanjar.weixin.mp.builder.kefu.MpNewsBuilder; | |||
| import me.chanjar.weixin.mp.builder.kefu.MusicBuilder; | |||
| import me.chanjar.weixin.mp.builder.kefu.NewsBuilder; | |||
| import me.chanjar.weixin.mp.builder.kefu.TextBuilder; | |||
| import me.chanjar.weixin.mp.builder.kefu.VideoBuilder; | |||
| import me.chanjar.weixin.mp.builder.kefu.VoiceBuilder; | |||
| import me.chanjar.weixin.mp.builder.kefu.WxCardBuilder; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| /** | |||
| * 客服消息 | |||
| * @author chanjarster | |||
| * | |||
| * @author chanjarster | |||
| */ | |||
| public class WxMpCustomMessage implements Serializable { | |||
| public class WxMpKefuMessage implements Serializable { | |||
| private static final long serialVersionUID = -9196732086954365246L; | |||
| private String toUser; | |||
| @@ -32,18 +33,85 @@ public class WxMpCustomMessage implements Serializable { | |||
| private String hqMusicUrl; | |||
| private String kfAccount; | |||
| private String cardId; | |||
| private String mpNewsMediaId; | |||
| private List<WxArticle> articles = new ArrayList<>(); | |||
| /** | |||
| * 获得文本消息builder | |||
| */ | |||
| public static TextBuilder TEXT() { | |||
| return new TextBuilder(); | |||
| } | |||
| /** | |||
| * 获得图片消息builder | |||
| */ | |||
| public static ImageBuilder IMAGE() { | |||
| return new ImageBuilder(); | |||
| } | |||
| /** | |||
| * 获得语音消息builder | |||
| */ | |||
| public static VoiceBuilder VOICE() { | |||
| return new VoiceBuilder(); | |||
| } | |||
| /** | |||
| * 获得视频消息builder | |||
| */ | |||
| public static VideoBuilder VIDEO() { | |||
| return new VideoBuilder(); | |||
| } | |||
| /** | |||
| * 获得音乐消息builder | |||
| */ | |||
| public static MusicBuilder MUSIC() { | |||
| return new MusicBuilder(); | |||
| } | |||
| /** | |||
| * 获得图文消息(点击跳转到外链)builder | |||
| */ | |||
| public static NewsBuilder NEWS() { | |||
| return new NewsBuilder(); | |||
| } | |||
| /** | |||
| * 获得图文消息(点击跳转到图文消息页面)builder | |||
| */ | |||
| public static MpNewsBuilder MPNEWS() { | |||
| return new MpNewsBuilder(); | |||
| } | |||
| /** | |||
| * 获得卡券消息builder | |||
| */ | |||
| public static WxCardBuilder WXCARD() { | |||
| return new WxCardBuilder(); | |||
| } | |||
| public String getToUser() { | |||
| return this.toUser; | |||
| } | |||
| public void setToUser(String toUser) { | |||
| this.toUser = toUser; | |||
| } | |||
| public String getMsgType() { | |||
| return this.msgType; | |||
| } | |||
| public String getMpNewsMediaId() { | |||
| return this.mpNewsMediaId; | |||
| } | |||
| public void setMpNewsMediaId(String mpNewsMediaId) { | |||
| this.mpNewsMediaId = mpNewsMediaId; | |||
| } | |||
| /** | |||
| * <pre> | |||
| * 请使用 | |||
| @@ -53,52 +121,68 @@ public class WxMpCustomMessage implements Serializable { | |||
| * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_MUSIC} | |||
| * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_VIDEO} | |||
| * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_NEWS} | |||
| * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_MPNEWS} | |||
| * {@link me.chanjar.weixin.common.api.WxConsts#CUSTOM_MSG_WXCARD} | |||
| * </pre> | |||
| * | |||
| * @param msgType | |||
| */ | |||
| public void setMsgType(String msgType) { | |||
| this.msgType = msgType; | |||
| } | |||
| public String getContent() { | |||
| return this.content; | |||
| } | |||
| public void setContent(String content) { | |||
| this.content = content; | |||
| } | |||
| public String getMediaId() { | |||
| return this.mediaId; | |||
| } | |||
| public void setMediaId(String mediaId) { | |||
| this.mediaId = mediaId; | |||
| } | |||
| public String getThumbMediaId() { | |||
| return this.thumbMediaId; | |||
| } | |||
| public void setThumbMediaId(String thumbMediaId) { | |||
| this.thumbMediaId = thumbMediaId; | |||
| } | |||
| public String getTitle() { | |||
| return this.title; | |||
| } | |||
| public void setTitle(String title) { | |||
| this.title = title; | |||
| } | |||
| public String getDescription() { | |||
| return this.description; | |||
| } | |||
| public void setDescription(String description) { | |||
| this.description = description; | |||
| } | |||
| public String getMusicUrl() { | |||
| return this.musicUrl; | |||
| } | |||
| public void setMusicUrl(String musicUrl) { | |||
| this.musicUrl = musicUrl; | |||
| } | |||
| public String getHqMusicUrl() { | |||
| return this.hqMusicUrl; | |||
| } | |||
| public void setHqMusicUrl(String hqMusicUrl) { | |||
| this.hqMusicUrl = hqMusicUrl; | |||
| } | |||
| @@ -114,6 +198,7 @@ public class WxMpCustomMessage implements Serializable { | |||
| public List<WxArticle> getArticles() { | |||
| return this.articles; | |||
| } | |||
| public void setArticles(List<WxArticle> articles) { | |||
| this.articles = articles; | |||
| } | |||
| @@ -122,8 +207,15 @@ public class WxMpCustomMessage implements Serializable { | |||
| return WxMpGsonBuilder.INSTANCE.create().toJson(this); | |||
| } | |||
| public static class WxArticle { | |||
| public String getKfAccount() { | |||
| return this.kfAccount; | |||
| } | |||
| public void setKfAccount(String kfAccount) { | |||
| this.kfAccount = kfAccount; | |||
| } | |||
| public static class WxArticle { | |||
| private String title; | |||
| private String description; | |||
| private String url; | |||
| @@ -132,85 +224,34 @@ public class WxMpCustomMessage implements Serializable { | |||
| public String getTitle() { | |||
| return this.title; | |||
| } | |||
| public void setTitle(String title) { | |||
| this.title = title; | |||
| } | |||
| public String getDescription() { | |||
| return this.description; | |||
| } | |||
| public void setDescription(String description) { | |||
| this.description = description; | |||
| } | |||
| public String getUrl() { | |||
| return this.url; | |||
| } | |||
| public void setUrl(String url) { | |||
| this.url = url; | |||
| } | |||
| public String getPicUrl() { | |||
| return this.picUrl; | |||
| } | |||
| public void setPicUrl(String picUrl) { | |||
| this.picUrl = picUrl; | |||
| } | |||
| } | |||
| /** | |||
| * 获得文本消息builder | |||
| */ | |||
| public static TextBuilder TEXT() { | |||
| return new TextBuilder(); | |||
| } | |||
| /** | |||
| * 获得图片消息builder | |||
| */ | |||
| public static ImageBuilder IMAGE() { | |||
| return new ImageBuilder(); | |||
| } | |||
| /** | |||
| * 获得语音消息builder | |||
| */ | |||
| public static VoiceBuilder VOICE() { | |||
| return new VoiceBuilder(); | |||
| } | |||
| /** | |||
| * 获得视频消息builder | |||
| */ | |||
| public static VideoBuilder VIDEO() { | |||
| return new VideoBuilder(); | |||
| } | |||
| /** | |||
| * 获得音乐消息builder | |||
| */ | |||
| public static MusicBuilder MUSIC() { | |||
| return new MusicBuilder(); | |||
| } | |||
| /** | |||
| * 获得图文消息builder | |||
| */ | |||
| public static NewsBuilder NEWS() { | |||
| return new NewsBuilder(); | |||
| } | |||
| /** | |||
| * 获得卡券消息builder | |||
| */ | |||
| public static WxCardBuilder WXCARD() { | |||
| return new WxCardBuilder(); | |||
| } | |||
| public String getKfAccount() { | |||
| return this.kfAccount; | |||
| } | |||
| public void setKfAccount(String kfAccount) { | |||
| this.kfAccount = kfAccount; | |||
| } | |||
| } | |||
| @@ -1,14 +1,11 @@ | |||
| package me.chanjar.weixin.mp.bean.kefu.request; | |||
| import java.io.Serializable; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.io.Serializable; | |||
| public class WxMpKfSessionRequest implements Serializable { | |||
| private static final long serialVersionUID = -5451863610674856927L; | |||
| @@ -17,7 +14,7 @@ public class WxMpKfSessionRequest implements Serializable { | |||
| */ | |||
| @SerializedName("kf_account") | |||
| private String kfAccount; | |||
| /** | |||
| * openid 客户openid | |||
| */ | |||
| @@ -31,9 +28,9 @@ public class WxMpKfSessionRequest implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public String toJson() { | |||
| return WxMpGsonBuilder.INSTANCE.create().toJson(this); | |||
| } | |||
| @@ -1,12 +1,10 @@ | |||
| package me.chanjar.weixin.mp.bean.kefu.result; | |||
| import java.io.Serializable; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.Expose; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import java.io.Serializable; | |||
| /** | |||
| * 客服基本信息以及客服在线状态信息 | |||
| @@ -127,7 +125,7 @@ public class WxMpKfInfo implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public String getWxAccount() { | |||
| @@ -1,15 +1,12 @@ | |||
| package me.chanjar.weixin.mp.bean.kefu.result; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.util.List; | |||
| /** | |||
| * | |||
| * | |||
| * @author Binary Wang | |||
| * | |||
| */ | |||
| @@ -19,7 +16,7 @@ public class WxMpKfList { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public List<WxMpKfInfo> getKfList() { | |||
| @@ -28,8 +25,8 @@ public class WxMpKfList { | |||
| public void setKfList(List<WxMpKfInfo> kfList) { | |||
| this.kfList = kfList; | |||
| } | |||
| } | |||
| public static WxMpKfList fromJson(String json) { | |||
| return WxMpGsonBuilder.INSTANCE.create().fromJson(json, WxMpKfList.class); | |||
| } | |||
| @@ -1,9 +1,8 @@ | |||
| package me.chanjar.weixin.mp.bean.kefu.result; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import java.util.List; | |||
| @@ -46,7 +45,7 @@ public class WxMpKfMsgList { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public static WxMpKfMsgList fromJson(String responseContent) { | |||
| @@ -1,9 +1,7 @@ | |||
| package me.chanjar.weixin.mp.bean.kefu.result; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| /** | |||
| * Created by Binary Wang on 2016/7/18. | |||
| @@ -41,8 +39,9 @@ public class WxMpKfMsgRecord { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public String getWorker() { | |||
| return this.worker; | |||
| } | |||
| @@ -1,15 +1,12 @@ | |||
| package me.chanjar.weixin.mp.bean.kefu.result; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.util.List; | |||
| /** | |||
| * | |||
| * | |||
| * @author Binary Wang | |||
| * | |||
| */ | |||
| @@ -19,9 +16,9 @@ public class WxMpKfOnlineList { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public List<WxMpKfInfo> getKfOnlineList() { | |||
| return this.kfOnlineList; | |||
| } | |||
| @@ -1,12 +1,10 @@ | |||
| package me.chanjar.weixin.mp.bean.kefu.result; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| /** | |||
| * | |||
| * | |||
| * @author Binary Wang | |||
| * | |||
| */ | |||
| @@ -39,9 +37,9 @@ public class WxMpKfSession { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public String getKfAccount() { | |||
| return this.kfAccount; | |||
| } | |||
| @@ -1,14 +1,11 @@ | |||
| package me.chanjar.weixin.mp.bean.kefu.result; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| /** | |||
| * | |||
| * | |||
| * @author Binary Wang | |||
| * | |||
| */ | |||
| @@ -27,13 +24,13 @@ public class WxMpKfSessionGetResult { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public static WxMpKfSessionGetResult fromJson(String json) { | |||
| return WxMpGsonBuilder.INSTANCE.create().fromJson(json, WxMpKfSessionGetResult.class); | |||
| } | |||
| public String getKfAccount() { | |||
| return this.kfAccount; | |||
| } | |||
| @@ -1,16 +1,13 @@ | |||
| package me.chanjar.weixin.mp.bean.kefu.result; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.util.List; | |||
| /** | |||
| * | |||
| * | |||
| * @author Binary Wang | |||
| * | |||
| */ | |||
| @@ -23,7 +20,7 @@ public class WxMpKfSessionList { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public static WxMpKfSessionList fromJson(String json) { | |||
| @@ -1,16 +1,13 @@ | |||
| package me.chanjar.weixin.mp.bean.kefu.result; | |||
| import java.util.List; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.google.gson.annotations.SerializedName; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.util.List; | |||
| /** | |||
| * | |||
| * | |||
| * @author Binary Wang | |||
| * | |||
| */ | |||
| @@ -29,7 +26,7 @@ public class WxMpKfSessionWaitCaseList { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public static WxMpKfSessionWaitCaseList fromJson(String json) { | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean.result; | |||
| package me.chanjar.weixin.mp.bean.material; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| @@ -9,7 +9,7 @@ import java.io.Serializable; | |||
| */ | |||
| public class WxMediaImgUploadResult implements Serializable { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = 1996392453428768829L; | |||
| private String url; | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.material; | |||
| import java.io.File; | |||
| import java.util.HashMap; | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.material; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| @@ -7,7 +7,7 @@ import java.io.Serializable; | |||
| public class WxMpMaterialArticleUpdate implements Serializable { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -7611963949517780270L; | |||
| private String mediaId; | |||
| @@ -1,12 +1,10 @@ | |||
| package me.chanjar.weixin.mp.bean.result; | |||
| package me.chanjar.weixin.mp.bean.material; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import java.io.Serializable; | |||
| public class WxMpMaterialCountResult implements Serializable { | |||
| /** | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -5568772662085874138L; | |||
| private int voiceCount; | |||
| private int videoCount; | |||
| @@ -47,8 +45,7 @@ public class WxMpMaterialCountResult implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return "WxMpMaterialCountResult [" + "voiceCount=" + this.voiceCount + ", videoCount=" + this.videoCount | |||
| + ", imageCount=" + this.imageCount + ", newsCount=" + this.newsCount + "]"; | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| @@ -1,14 +1,12 @@ | |||
| package me.chanjar.weixin.mp.bean.result; | |||
| package me.chanjar.weixin.mp.bean.material; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import java.io.Serializable; | |||
| import java.util.Date; | |||
| import java.util.List; | |||
| public class WxMpMaterialFileBatchGetResult implements Serializable { | |||
| /** | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -560388368297267884L; | |||
| private int totalCount; | |||
| private int itemCount; | |||
| @@ -40,7 +38,7 @@ public class WxMpMaterialFileBatchGetResult implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return "WxMpMaterialFileBatchGetResult [" + "totalCount=" + this.totalCount + ", itemCount=" + this.itemCount + ", items=" + this.items + "]"; | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public static class WxMaterialFileBatchGetNewsItem { | |||
| @@ -83,7 +81,7 @@ public class WxMpMaterialFileBatchGetResult implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return "WxMaterialFileBatchGetNewsItem [" + "mediaId=" + this.mediaId + ", updateTime=" + this.updateTime + ", name=" + this.name + ", url=" + this.url + "]"; | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,5 +1,6 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.material; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| import java.io.Serializable; | |||
| @@ -7,11 +8,8 @@ import java.util.ArrayList; | |||
| import java.util.List; | |||
| public class WxMpMaterialNews implements Serializable { | |||
| /** | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -3283203652013494976L; | |||
| private List<WxMpMaterialNewsArticle> articles = new ArrayList<>(); | |||
| public List<WxMpMaterialNewsArticle> getArticles() { | |||
| @@ -33,7 +31,7 @@ public class WxMpMaterialNews implements Serializable { | |||
| /** | |||
| * <pre> | |||
| * 群发图文消息article | |||
| * 1. thumbMediaId (必填) 图文消息缩略图的media_id,可以在基础支持-上传多媒体文件接口中获得 | |||
| * 1. thumbMediaId (必填) 图文消息的封面图片素材id(必须是永久mediaID) | |||
| * 2. author 图文消息的作者 | |||
| * 3. title (必填) 图文消息的标题 | |||
| * 4. contentSourceUrl 在图文消息页面点击“阅读原文”后的页面链接 | |||
| @@ -81,7 +79,6 @@ public class WxMpMaterialNews implements Serializable { | |||
| /** | |||
| * 点击图文消息跳转链接 | |||
| * @return | |||
| */ | |||
| private String url; | |||
| @@ -159,14 +156,12 @@ public class WxMpMaterialNews implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return "WxMpMassNewsArticle [" + "thumbMediaId=" + this.thumbMediaId + "thumbUrl=" + this.thumbUrl + ", author=" + this.author + ", title=" + this.title + | |||
| ", contentSourceUrl=" + this.contentSourceUrl + ", content=" + this.content + ", digest=" + this.digest + | |||
| ", showCoverPic=" + this.showCoverPic +", url=" + this.url + "]"; | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "WxMpMaterialNews [" + "articles=" + this.articles + "]"; | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| @@ -1,17 +1,14 @@ | |||
| package me.chanjar.weixin.mp.bean.result; | |||
| package me.chanjar.weixin.mp.bean.material; | |||
| import me.chanjar.weixin.mp.bean.WxMpMaterialNews; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import java.io.Serializable; | |||
| import java.util.Date; | |||
| import java.util.List; | |||
| public class WxMpMaterialNewsBatchGetResult implements Serializable { | |||
| /** | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -1617952797921001666L; | |||
| private int totalCount; | |||
| private int itemCount; | |||
| private List<WxMaterialNewsBatchGetNewsItem> items; | |||
| @@ -42,7 +39,7 @@ public class WxMpMaterialNewsBatchGetResult implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return "WxMpMaterialNewsBatchGetResult [" + "totalCount=" + this.totalCount + ", itemCount=" + this.itemCount + ", items=" + this.items + "]"; | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public static class WxMaterialNewsBatchGetNewsItem { | |||
| @@ -76,7 +73,7 @@ public class WxMpMaterialNewsBatchGetResult implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return "WxMaterialNewsBatchGetNewsItem [" + "mediaId=" + this.mediaId + ", updateTime=" + this.updateTime + ", content=" + this.content + "]"; | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean.result; | |||
| package me.chanjar.weixin.mp.bean.material; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| @@ -7,7 +7,7 @@ import java.io.Serializable; | |||
| public class WxMpMaterialUploadResult implements Serializable { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -128818731449449537L; | |||
| private String mediaId; | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean.result; | |||
| package me.chanjar.weixin.mp.bean.material; | |||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||
| @@ -7,7 +7,7 @@ import java.io.Serializable; | |||
| public class WxMpMaterialVideoInfoResult implements Serializable { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = 1269131745333792202L; | |||
| private String title; | |||
| @@ -1,22 +1,19 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import java.io.Serializable; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| import org.apache.commons.io.IOUtils; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| package me.chanjar.weixin.mp.bean.message; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
| import me.chanjar.weixin.mp.api.WxMpConfigStorage; | |||
| import me.chanjar.weixin.mp.util.crypto.WxMpCryptUtil; | |||
| import me.chanjar.weixin.mp.util.xml.XStreamTransformer; | |||
| import org.apache.commons.io.IOUtils; | |||
| import java.io.IOException; | |||
| import java.io.InputStream; | |||
| import java.io.Serializable; | |||
| import java.util.ArrayList; | |||
| import java.util.List; | |||
| /** | |||
| * <pre> | |||
| @@ -56,6 +53,9 @@ public class WxMpXmlMessage implements Serializable { | |||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||
| private String content; | |||
| @XStreamAlias("MenuId") | |||
| private Long menuId; | |||
| @XStreamAlias("MsgId") | |||
| private Long msgId; | |||
| @@ -687,6 +687,14 @@ public class WxMpXmlMessage implements Serializable { | |||
| this.sendLocationInfo = sendLocationInfo; | |||
| } | |||
| public Long getMenuId() { | |||
| return this.menuId; | |||
| } | |||
| public void setMenuId(Long menuId) { | |||
| this.menuId = menuId; | |||
| } | |||
| public String getKfAccount() { | |||
| return this.kfAccount; | |||
| } | |||
| @@ -715,7 +723,7 @@ public class WxMpXmlMessage implements Serializable { | |||
| public static class ScanCodeInfo { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| @XStreamAlias("ScanType") | |||
| @@ -755,7 +763,7 @@ public class WxMpXmlMessage implements Serializable { | |||
| public static class SendPicsInfo { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| @XStreamAlias("Count") | |||
| @@ -780,8 +788,7 @@ public class WxMpXmlMessage implements Serializable { | |||
| public static class Item { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, | |||
| ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| @XStreamAlias("PicMd5Sum") | |||
| @@ -823,7 +830,7 @@ public class WxMpXmlMessage implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public String getLocationX() { | |||
| @@ -869,6 +876,6 @@ public class WxMpXmlMessage implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.message; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| @@ -9,7 +9,7 @@ import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | |||
| public class WxMpXmlOutImageMessage extends WxMpXmlOutMessage { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -2684778597067990723L; | |||
| @XStreamAlias("Image") | |||
| @@ -1,10 +1,10 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.message; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
| import me.chanjar.weixin.mp.api.WxMpConfigStorage; | |||
| import me.chanjar.weixin.mp.bean.outxmlbuilder.*; | |||
| import me.chanjar.weixin.mp.builder.outxml.*; | |||
| import me.chanjar.weixin.mp.util.crypto.WxMpCryptUtil; | |||
| import me.chanjar.weixin.mp.util.xml.XStreamTransformer; | |||
| @@ -18,14 +18,14 @@ public abstract class WxMpXmlOutMessage implements Serializable { | |||
| @XStreamAlias("ToUserName") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| protected String toUserName; | |||
| @XStreamAlias("FromUserName") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| protected String fromUserName; | |||
| @XStreamAlias("CreateTime") | |||
| protected Long createTime; | |||
| @XStreamAlias("MsgType") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| protected String msgType; | |||
| @@ -61,7 +61,7 @@ public abstract class WxMpXmlOutMessage implements Serializable { | |||
| public void setMsgType(String msgType) { | |||
| this.msgType = msgType; | |||
| } | |||
| public String toXml() { | |||
| return XStreamTransformer.toXml((Class<WxMpXmlOutMessage>) this.getClass(), this); | |||
| } | |||
| @@ -95,28 +95,28 @@ public abstract class WxMpXmlOutMessage implements Serializable { | |||
| public static VoiceBuilder VOICE() { | |||
| return new VoiceBuilder(); | |||
| } | |||
| /** | |||
| * 获得视频消息builder | |||
| */ | |||
| public static VideoBuilder VIDEO() { | |||
| return new VideoBuilder(); | |||
| } | |||
| /** | |||
| * 获得音乐消息builder | |||
| */ | |||
| public static MusicBuilder MUSIC() { | |||
| return new MusicBuilder(); | |||
| } | |||
| /** | |||
| * 获得图文消息builder | |||
| */ | |||
| public static NewsBuilder NEWS() { | |||
| return new NewsBuilder(); | |||
| } | |||
| /** | |||
| * 获得客服消息builder | |||
| */ | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.message; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| @@ -9,7 +9,7 @@ import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
| public class WxMpXmlOutMusicMessage extends WxMpXmlOutMessage { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -4159937804975448945L; | |||
| @XStreamAlias("Music") | |||
| @@ -34,7 +34,7 @@ public class WxMpXmlOutMusicMessage extends WxMpXmlOutMessage { | |||
| public void setDescription(String description) { | |||
| this.music.setDescription(description); | |||
| } | |||
| public String getThumbMediaId() { | |||
| return this.music.getThumbMediaId(); | |||
| } | |||
| @@ -58,10 +58,10 @@ public class WxMpXmlOutMusicMessage extends WxMpXmlOutMessage { | |||
| public void setHqMusicUrl(String hqMusicUrl) { | |||
| this.music.setHqMusicUrl(hqMusicUrl); | |||
| } | |||
| @XStreamAlias("Music") | |||
| public static class Music { | |||
| @XStreamAlias("Title") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| private String title; | |||
| @@ -73,15 +73,15 @@ public class WxMpXmlOutMusicMessage extends WxMpXmlOutMessage { | |||
| @XStreamAlias("ThumbMediaId") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| private String thumbMediaId; | |||
| @XStreamAlias("MusicUrl") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| private String musicUrl; | |||
| @XStreamAlias("HQMusicUrl") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| private String hqMusicUrl; | |||
| public String getTitle() { | |||
| return this.title; | |||
| } | |||
| @@ -121,7 +121,7 @@ public class WxMpXmlOutMusicMessage extends WxMpXmlOutMessage { | |||
| public void setHqMusicUrl(String hqMusicUrl) { | |||
| this.hqMusicUrl = hqMusicUrl; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.message; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| @@ -12,16 +12,16 @@ import java.util.List; | |||
| public class WxMpXmlOutNewsMessage extends WxMpXmlOutMessage { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -4604402850905714772L; | |||
| @XStreamAlias("ArticleCount") | |||
| protected int articleCount; | |||
| @XStreamAlias("Articles") | |||
| protected final List<Item> articles = new ArrayList<>(); | |||
| public WxMpXmlOutNewsMessage() { | |||
| this.msgType = WxConsts.XML_MSG_NEWS; | |||
| } | |||
| @@ -34,15 +34,15 @@ public class WxMpXmlOutNewsMessage extends WxMpXmlOutMessage { | |||
| this.articles.add(item); | |||
| this.articleCount = this.articles.size(); | |||
| } | |||
| public List<Item> getArticles() { | |||
| return this.articles; | |||
| } | |||
| @XStreamAlias("item") | |||
| public static class Item { | |||
| @XStreamAlias("Title") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| private String Title; | |||
| @@ -54,11 +54,11 @@ public class WxMpXmlOutNewsMessage extends WxMpXmlOutMessage { | |||
| @XStreamAlias("PicUrl") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| private String PicUrl; | |||
| @XStreamAlias("Url") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| private String Url; | |||
| public String getTitle() { | |||
| return this.Title; | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.message; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| @@ -7,9 +7,9 @@ import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
| @XStreamAlias("xml") | |||
| public class WxMpXmlOutTextMessage extends WxMpXmlOutMessage { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -3972786455288763361L; | |||
| @XStreamAlias("Content") | |||
| @@ -19,7 +19,7 @@ public class WxMpXmlOutTextMessage extends WxMpXmlOutMessage { | |||
| public WxMpXmlOutTextMessage() { | |||
| this.msgType = WxConsts.XML_MSG_TEXT; | |||
| } | |||
| public String getContent() { | |||
| return this.content; | |||
| } | |||
| @@ -28,5 +28,5 @@ public class WxMpXmlOutTextMessage extends WxMpXmlOutMessage { | |||
| this.content = content; | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.message; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| @@ -6,13 +6,13 @@ import me.chanjar.weixin.common.api.WxConsts; | |||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
| @XStreamAlias("xml") | |||
| public class WxMpXmlOutTransferCustomerServiceMessage extends WxMpXmlOutMessage { | |||
| public class WxMpXmlOutTransferKefuMessage extends WxMpXmlOutMessage { | |||
| private static final long serialVersionUID = 1850903037285841322L; | |||
| @XStreamAlias("TransInfo") | |||
| protected TransInfo transInfo; | |||
| public WxMpXmlOutTransferCustomerServiceMessage() { | |||
| public WxMpXmlOutTransferKefuMessage() { | |||
| this.msgType = WxConsts.CUSTOM_MSG_TRANSFER_CUSTOMER_SERVICE; | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.message; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| @@ -9,7 +9,7 @@ import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||
| public class WxMpXmlOutVideoMessage extends WxMpXmlOutMessage { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = 1745902309380113978L; | |||
| @XStreamAlias("Video") | |||
| @@ -42,11 +42,11 @@ public class WxMpXmlOutVideoMessage extends WxMpXmlOutMessage { | |||
| public void setDescription(String description) { | |||
| this.video.setDescription(description); | |||
| } | |||
| @XStreamAlias("Video") | |||
| public static class Video { | |||
| @XStreamAlias("MediaId") | |||
| @XStreamConverter(value=XStreamCDataConverter.class) | |||
| private String mediaId; | |||
| @@ -82,7 +82,7 @@ public class WxMpXmlOutVideoMessage extends WxMpXmlOutMessage { | |||
| public void setDescription(String description) { | |||
| this.description = description; | |||
| } | |||
| } | |||
| } | |||
| @@ -1,4 +1,4 @@ | |||
| package me.chanjar.weixin.mp.bean; | |||
| package me.chanjar.weixin.mp.bean.message; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||
| @@ -9,7 +9,7 @@ import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | |||
| public class WxMpXmlOutVoiceMessage extends WxMpXmlOutMessage { | |||
| /** | |||
| * | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = 240367390249860551L; | |||
| @XStreamAlias("Voice") | |||
| @@ -1,33 +0,0 @@ | |||
| package me.chanjar.weixin.mp.bean.outxmlbuilder; | |||
| import me.chanjar.weixin.common.util.StringUtils; | |||
| import me.chanjar.weixin.mp.bean.WxMpXmlOutTransferCustomerServiceMessage; | |||
| /** | |||
| * 客服消息builder | |||
| * <pre> | |||
| * 用法: WxMpCustomMessage m = WxMpXmlOutMessage.TRANSFER_CUSTOMER_SERVICE().content(...).toUser(...).build(); | |||
| * </pre> | |||
| * | |||
| * @author chanjarster | |||
| */ | |||
| public final class TransferCustomerServiceBuilder extends BaseBuilder<TransferCustomerServiceBuilder, WxMpXmlOutTransferCustomerServiceMessage> { | |||
| private String kfAccount; | |||
| public TransferCustomerServiceBuilder kfAccount(String kf) { | |||
| this.kfAccount = kf; | |||
| return this; | |||
| } | |||
| @Override | |||
| public WxMpXmlOutTransferCustomerServiceMessage build() { | |||
| WxMpXmlOutTransferCustomerServiceMessage m = new WxMpXmlOutTransferCustomerServiceMessage(); | |||
| setCommon(m); | |||
| if(StringUtils.isNotBlank(this.kfAccount)){ | |||
| WxMpXmlOutTransferCustomerServiceMessage.TransInfo transInfo = new WxMpXmlOutTransferCustomerServiceMessage.TransInfo(); | |||
| transInfo.setKfAccount(this.kfAccount); | |||
| m.setTransInfo(transInfo); | |||
| } | |||
| return m; | |||
| } | |||
| } | |||
| @@ -1,135 +0,0 @@ | |||
| package me.chanjar.weixin.mp.bean.pay; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import me.chanjar.weixin.common.annotation.Required; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| /** | |||
| * <pre> | |||
| * 企业付款请求对象 | |||
| * 注释中各行每个字段描述对应如下: | |||
| * <li>字段名 | |||
| * <li>变量名 | |||
| * <li>是否必填 | |||
| * <li>类型 | |||
| * <li>示例值 | |||
| * <li>描述 | |||
| * </pre> | |||
| * Created by Binary Wang on 2016/10/19. | |||
| * @author binarywang (https://github.com/binarywang) | |||
| */ | |||
| @XStreamAlias("xml") | |||
| public class WxEntPayQueryRequest { | |||
| /** | |||
| * <pre> | |||
| * Appid | |||
| * appid | |||
| * 是 | |||
| * wxe062425f740d30d8 | |||
| * String(32) | |||
| * 商户号的appid | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("appid") | |||
| private String appid; | |||
| /** | |||
| * <pre> | |||
| * 商户号 | |||
| * mch_id | |||
| * 是 | |||
| * 10000098 | |||
| * String(32) | |||
| * 微信支付分配的商户号 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("mchid") | |||
| private String mchId; | |||
| /** | |||
| * <pre> | |||
| * 随机字符串 | |||
| * nonce_str | |||
| * 是 | |||
| * 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | |||
| * String(32) | |||
| * 随机字符串,不长于32位 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("nonce_str") | |||
| private String nonceStr; | |||
| /** | |||
| * <pre> | |||
| * 签名 | |||
| * sign | |||
| * 是 | |||
| * C380BEC2BFD727A4B6845133519F3AD6 | |||
| * String(32) | |||
| *签名,详见签名算法 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("sign") | |||
| private String sign; | |||
| /** | |||
| * <pre> | |||
| * 商户订单号 | |||
| * partner_trade_no | |||
| * 是 | |||
| * 10000098201411111234567890 | |||
| * String | |||
| * 商户订单号 | |||
| * </pre> | |||
| */ | |||
| @Required | |||
| @XStreamAlias("partner_trade_no") | |||
| private String partnerTradeNo; | |||
| public String getAppid() { | |||
| return appid; | |||
| } | |||
| public void setAppid(String appid) { | |||
| this.appid = appid; | |||
| } | |||
| public String getMchId() { | |||
| return mchId; | |||
| } | |||
| public void setMchId(String mchId) { | |||
| this.mchId = mchId; | |||
| } | |||
| public String getNonceStr() { | |||
| return nonceStr; | |||
| } | |||
| public void setNonceStr(String nonceStr) { | |||
| this.nonceStr = nonceStr; | |||
| } | |||
| public String getSign() { | |||
| return sign; | |||
| } | |||
| public void setSign(String sign) { | |||
| this.sign = sign; | |||
| } | |||
| public String getPartnerTradeNo() { | |||
| return partnerTradeNo; | |||
| } | |||
| public void setPartnerTradeNo(String partnerTradeNo) { | |||
| this.partnerTradeNo = partnerTradeNo; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| } | |||
| } | |||
| @@ -1,190 +0,0 @@ | |||
| package me.chanjar.weixin.mp.bean.pay; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| /** | |||
| * 企业付款返回结果 | |||
| * Created by Binary Wang on 2016/10/02. | |||
| * @author binarywang (https://github.com/binarywang) | |||
| */ | |||
| @XStreamAlias("xml") | |||
| public class WxEntPayResult { | |||
| /** | |||
| * 返回状态码 | |||
| */ | |||
| @XStreamAlias("return_code") | |||
| private String returnCode; | |||
| /** | |||
| * 返回信息 | |||
| */ | |||
| @XStreamAlias("return_msg") | |||
| private String returnMsg; | |||
| //############以下字段在return_code为SUCCESS的时候有返回 | |||
| /** | |||
| * 商户appid | |||
| */ | |||
| @XStreamAlias("mch_appid") | |||
| private String mchAppid; | |||
| /** | |||
| * 商户号 | |||
| */ | |||
| @XStreamAlias("mchid") | |||
| private String mchId; | |||
| /** | |||
| * 设备号 | |||
| */ | |||
| @XStreamAlias("device_info") | |||
| private String deviceInfo; | |||
| /** | |||
| * 随机字符串 | |||
| */ | |||
| @XStreamAlias("nonce_str") | |||
| private String nonceStr; | |||
| /** | |||
| * 业务结果 | |||
| */ | |||
| @XStreamAlias("result_code") | |||
| private String resultCode; | |||
| /** | |||
| * 错误代码 | |||
| */ | |||
| @XStreamAlias("err_code") | |||
| private String errCode; | |||
| /** | |||
| * 错误代码描述 | |||
| */ | |||
| @XStreamAlias("err_code_des") | |||
| private String errCodeDes; | |||
| //############以下字段在return_code 和result_code都为SUCCESS的时候有返回############## | |||
| /** | |||
| * 商户订单号 | |||
| */ | |||
| @XStreamAlias("partner_trade_no") | |||
| private String partnerTradeNo; | |||
| /** | |||
| * 微信订单号 | |||
| */ | |||
| @XStreamAlias("payment_no") | |||
| private String paymentNo; | |||
| /** | |||
| * 微信支付成功时间 | |||
| */ | |||
| @XStreamAlias("payment_time") | |||
| private String paymentTime; | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| } | |||
| public String getReturnCode() { | |||
| return returnCode; | |||
| } | |||
| public void setReturnCode(String returnCode) { | |||
| this.returnCode = returnCode; | |||
| } | |||
| public String getReturnMsg() { | |||
| return returnMsg; | |||
| } | |||
| public void setReturnMsg(String returnMsg) { | |||
| this.returnMsg = returnMsg; | |||
| } | |||
| public String getMchAppid() { | |||
| return mchAppid; | |||
| } | |||
| public void setMchAppid(String mchAppid) { | |||
| this.mchAppid = mchAppid; | |||
| } | |||
| public String getMchId() { | |||
| return mchId; | |||
| } | |||
| public void setMchId(String mchId) { | |||
| this.mchId = mchId; | |||
| } | |||
| public String getDeviceInfo() { | |||
| return deviceInfo; | |||
| } | |||
| public void setDeviceInfo(String deviceInfo) { | |||
| this.deviceInfo = deviceInfo; | |||
| } | |||
| public String getNonceStr() { | |||
| return nonceStr; | |||
| } | |||
| public void setNonceStr(String nonceStr) { | |||
| this.nonceStr = nonceStr; | |||
| } | |||
| public String getResultCode() { | |||
| return resultCode; | |||
| } | |||
| public void setResultCode(String resultCode) { | |||
| this.resultCode = resultCode; | |||
| } | |||
| public String getErrCode() { | |||
| return errCode; | |||
| } | |||
| public void setErrCode(String errCode) { | |||
| this.errCode = errCode; | |||
| } | |||
| public String getErrCodeDes() { | |||
| return errCodeDes; | |||
| } | |||
| public void setErrCodeDes(String errCodeDes) { | |||
| this.errCodeDes = errCodeDes; | |||
| } | |||
| public String getPartnerTradeNo() { | |||
| return partnerTradeNo; | |||
| } | |||
| public void setPartnerTradeNo(String partnerTradeNo) { | |||
| this.partnerTradeNo = partnerTradeNo; | |||
| } | |||
| public String getPaymentNo() { | |||
| return paymentNo; | |||
| } | |||
| public void setPaymentNo(String paymentNo) { | |||
| this.paymentNo = paymentNo; | |||
| } | |||
| public String getPaymentTime() { | |||
| return paymentTime; | |||
| } | |||
| public void setPaymentTime(String paymentTime) { | |||
| this.paymentTime = paymentTime; | |||
| } | |||
| } | |||
| @@ -1,283 +0,0 @@ | |||
| package me.chanjar.weixin.mp.bean.pay; | |||
| import java.io.Serializable; | |||
| /** | |||
| * pre> 订单支付状态回调 | |||
| * | |||
| * 支付结果通知(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7) | |||
| * | |||
| * /pre> | |||
| * | |||
| * @author ukid | |||
| */ | |||
| public class WxMpPayCallback implements Serializable { | |||
| /** | |||
| * | |||
| */ | |||
| private static final long serialVersionUID = -4143804055690843641L; | |||
| private String return_code; | |||
| private String return_msg; | |||
| private String appid; | |||
| private String mch_id; | |||
| private String device_info; | |||
| private String nonce_str; | |||
| private String sign; | |||
| private String result_code; | |||
| private String err_code; | |||
| private String err_code_des; | |||
| private String openid; | |||
| private String is_subscribe; | |||
| private String trade_type; | |||
| private String bank_type; | |||
| private String total_fee; | |||
| private String fee_type; | |||
| private String cash_fee; | |||
| private String cash_fee_type; | |||
| private String coupon_fee; | |||
| private String coupon_count; | |||
| private String coupon_batch_id_$n; | |||
| private String coupon_id_$n; | |||
| private String coupon_fee_$n; | |||
| private String transaction_id; | |||
| private String out_trade_no; | |||
| private String attach; | |||
| private String time_end; | |||
| public String getReturn_code() { | |||
| return this.return_code; | |||
| } | |||
| public String getReturn_msg() { | |||
| return this.return_msg; | |||
| } | |||
| public String getAppid() { | |||
| return this.appid; | |||
| } | |||
| public String getMch_id() { | |||
| return this.mch_id; | |||
| } | |||
| public String getDevice_info() { | |||
| return this.device_info; | |||
| } | |||
| public String getNonce_str() { | |||
| return this.nonce_str; | |||
| } | |||
| public String getSign() { | |||
| return this.sign; | |||
| } | |||
| public String getResult_code() { | |||
| return this.result_code; | |||
| } | |||
| public String getErr_code() { | |||
| return this.err_code; | |||
| } | |||
| public String getErr_code_des() { | |||
| return this.err_code_des; | |||
| } | |||
| public String getOpenid() { | |||
| return this.openid; | |||
| } | |||
| public String getIs_subscribe() { | |||
| return this.is_subscribe; | |||
| } | |||
| public String getTrade_type() { | |||
| return this.trade_type; | |||
| } | |||
| public String getBank_type() { | |||
| return this.bank_type; | |||
| } | |||
| public String getTotal_fee() { | |||
| return this.total_fee; | |||
| } | |||
| public String getFee_type() { | |||
| return this.fee_type; | |||
| } | |||
| public String getCash_fee() { | |||
| return this.cash_fee; | |||
| } | |||
| public String getCash_fee_type() { | |||
| return this.cash_fee_type; | |||
| } | |||
| public String getCoupon_fee() { | |||
| return this.coupon_fee; | |||
| } | |||
| public String getCoupon_count() { | |||
| return this.coupon_count; | |||
| } | |||
| public String getCoupon_batch_id_$n() { | |||
| return this.coupon_batch_id_$n; | |||
| } | |||
| public String getCoupon_id_$n() { | |||
| return this.coupon_id_$n; | |||
| } | |||
| public String getCoupon_fee_$n() { | |||
| return this.coupon_fee_$n; | |||
| } | |||
| public String getTransaction_id() { | |||
| return this.transaction_id; | |||
| } | |||
| public String getOut_trade_no() { | |||
| return this.out_trade_no; | |||
| } | |||
| public String getAttach() { | |||
| return this.attach; | |||
| } | |||
| public String getTime_end() { | |||
| return this.time_end; | |||
| } | |||
| public void setReturn_code(String return_code) { | |||
| this.return_code = return_code; | |||
| } | |||
| public void setReturn_msg(String return_msg) { | |||
| this.return_msg = return_msg; | |||
| } | |||
| public void setAppid(String appid) { | |||
| this.appid = appid; | |||
| } | |||
| public void setMch_id(String mch_id) { | |||
| this.mch_id = mch_id; | |||
| } | |||
| public void setDevice_info(String device_info) { | |||
| this.device_info = device_info; | |||
| } | |||
| public void setNonce_str(String nonce_str) { | |||
| this.nonce_str = nonce_str; | |||
| } | |||
| public void setSign(String sign) { | |||
| this.sign = sign; | |||
| } | |||
| public void setResult_code(String result_code) { | |||
| this.result_code = result_code; | |||
| } | |||
| public void setErr_code(String err_code) { | |||
| this.err_code = err_code; | |||
| } | |||
| public void setErr_code_des(String err_code_des) { | |||
| this.err_code_des = err_code_des; | |||
| } | |||
| public void setOpenid(String openid) { | |||
| this.openid = openid; | |||
| } | |||
| public void setIs_subscribe(String is_subscribe) { | |||
| this.is_subscribe = is_subscribe; | |||
| } | |||
| public void setTrade_type(String trade_type) { | |||
| this.trade_type = trade_type; | |||
| } | |||
| public void setBank_type(String bank_type) { | |||
| this.bank_type = bank_type; | |||
| } | |||
| public void setTotal_fee(String total_fee) { | |||
| this.total_fee = total_fee; | |||
| } | |||
| public void setFee_type(String fee_type) { | |||
| this.fee_type = fee_type; | |||
| } | |||
| public void setCash_fee(String cash_fee) { | |||
| this.cash_fee = cash_fee; | |||
| } | |||
| public void setCash_fee_type(String cash_fee_type) { | |||
| this.cash_fee_type = cash_fee_type; | |||
| } | |||
| public void setCoupon_fee(String coupon_fee) { | |||
| this.coupon_fee = coupon_fee; | |||
| } | |||
| public void setCoupon_count(String coupon_count) { | |||
| this.coupon_count = coupon_count; | |||
| } | |||
| public void setCoupon_batch_id_$n(String coupon_batch_id_$n) { | |||
| this.coupon_batch_id_$n = coupon_batch_id_$n; | |||
| } | |||
| public void setCoupon_id_$n(String coupon_id_$n) { | |||
| this.coupon_id_$n = coupon_id_$n; | |||
| } | |||
| public void setCoupon_fee_$n(String coupon_fee_$n) { | |||
| this.coupon_fee_$n = coupon_fee_$n; | |||
| } | |||
| public void setTransaction_id(String transaction_id) { | |||
| this.transaction_id = transaction_id; | |||
| } | |||
| public void setOut_trade_no(String out_trade_no) { | |||
| this.out_trade_no = out_trade_no; | |||
| } | |||
| public void setAttach(String attach) { | |||
| this.attach = attach; | |||
| } | |||
| public void setTime_end(String time_end) { | |||
| this.time_end = time_end; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return "WxMpPayCallback [return_code=" + this.return_code + ", return_msg=" | |||
| + this.return_msg + ", appid=" + this.appid + ", mch_id=" + this.mch_id | |||
| + ", device_info=" + this.device_info + ", nonce_str=" + this.nonce_str | |||
| + ", sign=" + this.sign + ", result_code=" + this.result_code | |||
| + ", err_code=" + this.err_code + ", err_code_des=" + this.err_code_des | |||
| + ", openid=" + this.openid + ", is_subscribe=" + this.is_subscribe | |||
| + ", trade_type=" + this.trade_type + ", bank_type=" + this.bank_type | |||
| + ", total_fee=" + this.total_fee + ", fee_type=" + this.fee_type | |||
| + ", cash_fee=" + this.cash_fee + ", cash_fee_type=" + this.cash_fee_type | |||
| + ", coupon_fee=" + this.coupon_fee + ", coupon_count=" | |||
| + this.coupon_count + ", coupon_batch_id_$n=" + this.coupon_batch_id_$n | |||
| + ", coupon_id_$n=" + this.coupon_id_$n + ", coupon_fee_$n=" | |||
| + this.coupon_fee_$n + ", transaction_id=" + this.transaction_id | |||
| + ", out_trade_no=" + this.out_trade_no + ", attach=" + this.attach | |||
| + ", time_end=" + this.time_end + "]"; | |||
| } | |||
| } | |||
| @@ -1,259 +1,267 @@ | |||
| package me.chanjar.weixin.mp.bean.pay; | |||
| import java.io.Serializable; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import java.io.Serializable; | |||
| /** | |||
| * <pre> | |||
| * 查询订单支付状态返回的结果 | |||
| * | |||
| * 查询订单(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_2) | |||
| * | |||
| * </pre> | |||
| * 订单支付状态回调 | |||
| * 支付结果通知(详见http://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7) | |||
| * /pre> | |||
| * | |||
| * @author ukid | |||
| */ | |||
| public class WxMpPayResult implements Serializable { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| } | |||
| private static final long serialVersionUID = -570934170727777190L; | |||
| public class WxPayJsSDKCallback implements Serializable { | |||
| private static final long serialVersionUID = -4143804055690843641L; | |||
| private String return_code; | |||
| private String return_msg; | |||
| private String appid; | |||
| private String mch_id; | |||
| private String device_info; | |||
| private String nonce_str; | |||
| private String sign; | |||
| private String result_code; | |||
| private String err_code; | |||
| private String err_code_des; | |||
| private String trade_state; | |||
| private String trade_state_desc; | |||
| private String device_info; | |||
| private String openid; | |||
| private String is_subscribe; | |||
| private String trade_type; | |||
| private String bank_type; | |||
| private String total_fee; | |||
| private String coupon_fee; | |||
| private String fee_type; | |||
| private String cash_fee; | |||
| private String cash_fee_type; | |||
| private String coupon_fee; | |||
| private String coupon_count; | |||
| private String coupon_batch_id_$n; | |||
| private String coupon_id_$n; | |||
| private String coupon_fee_$n; | |||
| private String transaction_id; | |||
| private String out_trade_no; | |||
| private String attach; | |||
| private String time_end; | |||
| /** | |||
| * 现金支付金额 cash_fee 是 Int 100 现金支付金额订单现金支付金额,详见支付金额 | |||
| */ | |||
| private String cash_fee; | |||
| /** | |||
| * 现金支付货币类型 cash_fee_type 否 | |||
| * | |||
| */ | |||
| private String cash_fee_type; | |||
| public String getReturn_code() { | |||
| return this.return_code; | |||
| } | |||
| public void setReturn_code(String return_code) { | |||
| this.return_code = return_code; | |||
| } | |||
| public String getReturn_msg() { | |||
| return this.return_msg; | |||
| } | |||
| public void setReturn_msg(String return_msg) { | |||
| this.return_msg = return_msg; | |||
| } | |||
| public String getAppid() { | |||
| return this.appid; | |||
| } | |||
| public void setAppid(String appid) { | |||
| this.appid = appid; | |||
| } | |||
| public String getMch_id() { | |||
| return this.mch_id; | |||
| } | |||
| public void setMch_id(String mch_id) { | |||
| this.mch_id = mch_id; | |||
| } | |||
| public String getDevice_info() { | |||
| return this.device_info; | |||
| } | |||
| public void setDevice_info(String device_info) { | |||
| this.device_info = device_info; | |||
| } | |||
| public String getNonce_str() { | |||
| return this.nonce_str; | |||
| } | |||
| public void setNonce_str(String nonce_str) { | |||
| this.nonce_str = nonce_str; | |||
| } | |||
| public String getSign() { | |||
| return this.sign; | |||
| } | |||
| public void setSign(String sign) { | |||
| this.sign = sign; | |||
| } | |||
| public String getResult_code() { | |||
| return this.result_code; | |||
| } | |||
| public void setResult_code(String result_code) { | |||
| this.result_code = result_code; | |||
| } | |||
| public String getErr_code() { | |||
| return this.err_code; | |||
| } | |||
| public String getErr_code_des() { | |||
| return this.err_code_des; | |||
| public void setErr_code(String err_code) { | |||
| this.err_code = err_code; | |||
| } | |||
| public String getTrade_state() { | |||
| return this.trade_state; | |||
| public String getErr_code_des() { | |||
| return this.err_code_des; | |||
| } | |||
| public String getDevice_info() { | |||
| return this.device_info; | |||
| public void setErr_code_des(String err_code_des) { | |||
| this.err_code_des = err_code_des; | |||
| } | |||
| public String getOpenid() { | |||
| return this.openid; | |||
| } | |||
| public void setOpenid(String openid) { | |||
| this.openid = openid; | |||
| } | |||
| public String getIs_subscribe() { | |||
| return this.is_subscribe; | |||
| } | |||
| public void setIs_subscribe(String is_subscribe) { | |||
| this.is_subscribe = is_subscribe; | |||
| } | |||
| public String getTrade_type() { | |||
| return this.trade_type; | |||
| } | |||
| public void setTrade_type(String trade_type) { | |||
| this.trade_type = trade_type; | |||
| } | |||
| public String getBank_type() { | |||
| return this.bank_type; | |||
| } | |||
| public void setBank_type(String bank_type) { | |||
| this.bank_type = bank_type; | |||
| } | |||
| public String getTotal_fee() { | |||
| return this.total_fee; | |||
| } | |||
| public String getCoupon_fee() { | |||
| return this.coupon_fee; | |||
| public void setTotal_fee(String total_fee) { | |||
| this.total_fee = total_fee; | |||
| } | |||
| public String getFee_type() { | |||
| return this.fee_type; | |||
| } | |||
| public String getTransaction_id() { | |||
| return this.transaction_id; | |||
| } | |||
| public String getOut_trade_no() { | |||
| return this.out_trade_no; | |||
| } | |||
| public String getAttach() { | |||
| return this.attach; | |||
| } | |||
| public String getTime_end() { | |||
| return this.time_end; | |||
| public void setFee_type(String fee_type) { | |||
| this.fee_type = fee_type; | |||
| } | |||
| public void setReturn_code(String return_code) { | |||
| this.return_code = return_code; | |||
| public String getCash_fee() { | |||
| return this.cash_fee; | |||
| } | |||
| public void setReturn_msg(String return_msg) { | |||
| this.return_msg = return_msg; | |||
| public void setCash_fee(String cash_fee) { | |||
| this.cash_fee = cash_fee; | |||
| } | |||
| public void setAppid(String appid) { | |||
| this.appid = appid; | |||
| public String getCash_fee_type() { | |||
| return this.cash_fee_type; | |||
| } | |||
| public void setMch_id(String mch_id) { | |||
| this.mch_id = mch_id; | |||
| public void setCash_fee_type(String cash_fee_type) { | |||
| this.cash_fee_type = cash_fee_type; | |||
| } | |||
| public void setNonce_str(String nonce_str) { | |||
| this.nonce_str = nonce_str; | |||
| public String getCoupon_fee() { | |||
| return this.coupon_fee; | |||
| } | |||
| public void setSign(String sign) { | |||
| this.sign = sign; | |||
| public void setCoupon_fee(String coupon_fee) { | |||
| this.coupon_fee = coupon_fee; | |||
| } | |||
| public void setResult_code(String result_code) { | |||
| this.result_code = result_code; | |||
| public String getCoupon_count() { | |||
| return this.coupon_count; | |||
| } | |||
| public void setErr_code(String err_code) { | |||
| this.err_code = err_code; | |||
| public void setCoupon_count(String coupon_count) { | |||
| this.coupon_count = coupon_count; | |||
| } | |||
| public void setErr_code_des(String err_code_des) { | |||
| this.err_code_des = err_code_des; | |||
| public String getCoupon_batch_id_$n() { | |||
| return this.coupon_batch_id_$n; | |||
| } | |||
| public void setTrade_state(String trade_state) { | |||
| this.trade_state = trade_state; | |||
| public void setCoupon_batch_id_$n(String coupon_batch_id_$n) { | |||
| this.coupon_batch_id_$n = coupon_batch_id_$n; | |||
| } | |||
| public void setDevice_info(String device_info) { | |||
| this.device_info = device_info; | |||
| public String getCoupon_id_$n() { | |||
| return this.coupon_id_$n; | |||
| } | |||
| public void setOpenid(String openid) { | |||
| this.openid = openid; | |||
| public void setCoupon_id_$n(String coupon_id_$n) { | |||
| this.coupon_id_$n = coupon_id_$n; | |||
| } | |||
| public void setIs_subscribe(String is_subscribe) { | |||
| this.is_subscribe = is_subscribe; | |||
| public String getCoupon_fee_$n() { | |||
| return this.coupon_fee_$n; | |||
| } | |||
| public void setTrade_type(String trade_type) { | |||
| this.trade_type = trade_type; | |||
| public void setCoupon_fee_$n(String coupon_fee_$n) { | |||
| this.coupon_fee_$n = coupon_fee_$n; | |||
| } | |||
| public void setBank_type(String bank_type) { | |||
| this.bank_type = bank_type; | |||
| } | |||
| public void setTotal_fee(String total_fee) { | |||
| this.total_fee = total_fee; | |||
| } | |||
| public void setCoupon_fee(String coupon_fee) { | |||
| this.coupon_fee = coupon_fee; | |||
| } | |||
| public void setFee_type(String fee_type) { | |||
| this.fee_type = fee_type; | |||
| public String getTransaction_id() { | |||
| return this.transaction_id; | |||
| } | |||
| public void setTransaction_id(String transaction_id) { | |||
| this.transaction_id = transaction_id; | |||
| } | |||
| public void setOut_trade_no(String out_trade_no) { | |||
| this.out_trade_no = out_trade_no; | |||
| } | |||
| public void setAttach(String attach) { | |||
| this.attach = attach; | |||
| public String getOut_trade_no() { | |||
| return this.out_trade_no; | |||
| } | |||
| public void setTime_end(String time_end) { | |||
| this.time_end = time_end; | |||
| public void setOut_trade_no(String out_trade_no) { | |||
| this.out_trade_no = out_trade_no; | |||
| } | |||
| public String getTrade_state_desc() { | |||
| return this.trade_state_desc; | |||
| public String getAttach() { | |||
| return this.attach; | |||
| } | |||
| public void setTrade_state_desc(String trade_state_desc) { | |||
| this.trade_state_desc = trade_state_desc; | |||
| public void setAttach(String attach) { | |||
| this.attach = attach; | |||
| } | |||
| public String getCash_fee() { | |||
| return this.cash_fee; | |||
| public String getTime_end() { | |||
| return this.time_end; | |||
| } | |||
| public void setCash_fee(String cash_fee) { | |||
| this.cash_fee = cash_fee; | |||
| public void setTime_end(String time_end) { | |||
| this.time_end = time_end; | |||
| } | |||
| public String getCash_fee_type() { | |||
| return this.cash_fee_type; | |||
| @Override | |||
| public String toString() { | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| public void setCash_fee_type(String cash_fee_type) { | |||
| this.cash_fee_type = cash_fee_type; | |||
| } | |||
| } | |||
| @@ -1,105 +0,0 @@ | |||
| package me.chanjar.weixin.mp.bean.pay; | |||
| import java.io.Serializable; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| /** | |||
| * 向微信用户个人发现金红包返回结果 | |||
| * https://pay.weixin.qq.com/wiki/doc/api/cash_coupon.php?chapter=13_5 | |||
| * @author kane | |||
| * | |||
| */ | |||
| @XStreamAlias("xml") | |||
| public class WxRedpackResult implements Serializable { | |||
| private static final long serialVersionUID = -4837415036337132073L; | |||
| @XStreamAlias("return_code") | |||
| private String returnCode; | |||
| @XStreamAlias("return_msg") | |||
| private String returnMsg; | |||
| @XStreamAlias("sign") | |||
| private String sign; | |||
| @XStreamAlias("result_code") | |||
| private String resultCode; | |||
| @XStreamAlias("err_code") | |||
| private String errCode; | |||
| @XStreamAlias("err_code_des") | |||
| private String errCodeDes; | |||
| @XStreamAlias("mch_billno") | |||
| private String mchBillno; | |||
| @XStreamAlias("mch_id") | |||
| private String mchId; | |||
| @XStreamAlias("wxappid") | |||
| private String wxappid; | |||
| @XStreamAlias("re_openid") | |||
| private String reOpenid; | |||
| @XStreamAlias("total_amount") | |||
| private int totalAmount; | |||
| @XStreamAlias("send_time") | |||
| private String sendTime; | |||
| @XStreamAlias("send_listid") | |||
| private String sendListid; | |||
| public String getErrCode() { | |||
| return this.errCode; | |||
| } | |||
| public String getErrCodeDes() { | |||
| return this.errCodeDes; | |||
| } | |||
| public String getReturnCode() { | |||
| return this.returnCode; | |||
| } | |||
| public String getReturnMsg() { | |||
| return this.returnMsg; | |||
| } | |||
| public String getSign() { | |||
| return this.sign; | |||
| } | |||
| public String getResultCode() { | |||
| return this.resultCode; | |||
| } | |||
| public String getMchBillno() { | |||
| return this.mchBillno; | |||
| } | |||
| public String getMchId() { | |||
| return this.mchId; | |||
| } | |||
| public String getWxappid() { | |||
| return this.wxappid; | |||
| } | |||
| public String getReOpenid() { | |||
| return this.reOpenid; | |||
| } | |||
| public int getTotalAmount() { | |||
| return this.totalAmount; | |||
| } | |||
| public String getSendTime() { | |||
| return this.sendTime; | |||
| } | |||
| public String getSendListid() { | |||
| return this.sendListid; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| } | |||
| } | |||
| @@ -0,0 +1,74 @@ | |||
| package me.chanjar.weixin.mp.bean.pay.request; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import me.chanjar.weixin.common.annotation.Required; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| /** | |||
| * <pre> | |||
| * 企业付款请求对象 | |||
| * 注释中各行每个字段描述对应如下: | |||
| * <li>字段名 | |||
| * <li>变量名 | |||
| * <li>是否必填 | |||
| * <li>类型 | |||
| * <li>示例值 | |||
| * <li>描述 | |||
| * </pre> | |||
| * Created by Binary Wang on 2016/10/19. | |||
| * @author binarywang (https://github.com/binarywang) | |||
| */ | |||
| @XStreamAlias("xml") | |||
| public class WxEntPayQueryRequest extends WxPayBaseRequest { | |||
| /** | |||
| * <pre> | |||
| * 商户号 | |||
| * mch_id | |||
| * 是 | |||
| * 10000098 | |||
| * String(32) | |||
| * 微信支付分配的商户号 | |||
| * </pre> | |||
| */ | |||
| @SuppressWarnings("hiding") | |||
| @XStreamAlias("mchid") | |||
| private String mchId; | |||
| /** | |||
| * <pre> | |||
| * 商户订单号 | |||
| * partner_trade_no | |||
| * 是 | |||
| * 10000098201411111234567890 | |||
| * String | |||
| * 商户订单号 | |||
| * </pre> | |||
| */ | |||
| @Required | |||
| @XStreamAlias("partner_trade_no") | |||
| private String partnerTradeNo; | |||
| @Override | |||
| public String getMchId() { | |||
| return this.mchId; | |||
| } | |||
| @Override | |||
| public void setMchId(String mchId) { | |||
| this.mchId = mchId; | |||
| } | |||
| public String getPartnerTradeNo() { | |||
| return this.partnerTradeNo; | |||
| } | |||
| public void setPartnerTradeNo(String partnerTradeNo) { | |||
| this.partnerTradeNo = partnerTradeNo; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| @@ -1,9 +1,7 @@ | |||
| package me.chanjar.weixin.mp.bean.pay; | |||
| import org.apache.commons.lang3.builder.ToStringBuilder; | |||
| import org.apache.commons.lang3.builder.ToStringStyle; | |||
| package me.chanjar.weixin.mp.bean.pay.request; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| /** | |||
| * <pre> | |||
| @@ -181,7 +179,7 @@ public class WxEntPayRequest { | |||
| private String spbillCreateIp; | |||
| public String getMchAppid() { | |||
| return mchAppid; | |||
| return this.mchAppid; | |||
| } | |||
| public void setMchAppid(String mchAppid) { | |||
| @@ -189,7 +187,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public String getMchId() { | |||
| return mchId; | |||
| return this.mchId; | |||
| } | |||
| public void setMchId(String mchId) { | |||
| @@ -197,7 +195,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public String getDeviceInfo() { | |||
| return deviceInfo; | |||
| return this.deviceInfo; | |||
| } | |||
| public void setDeviceInfo(String deviceInfo) { | |||
| @@ -205,7 +203,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public String getNonceStr() { | |||
| return nonceStr; | |||
| return this.nonceStr; | |||
| } | |||
| public void setNonceStr(String nonceStr) { | |||
| @@ -213,7 +211,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public String getSign() { | |||
| return sign; | |||
| return this.sign; | |||
| } | |||
| public void setSign(String sign) { | |||
| @@ -221,7 +219,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public String getPartnerTradeNo() { | |||
| return partnerTradeNo; | |||
| return this.partnerTradeNo; | |||
| } | |||
| public void setPartnerTradeNo(String partnerTradeNo) { | |||
| @@ -229,7 +227,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public String getOpenid() { | |||
| return openid; | |||
| return this.openid; | |||
| } | |||
| public void setOpenid(String openid) { | |||
| @@ -237,7 +235,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public String getCheckName() { | |||
| return checkName; | |||
| return this.checkName; | |||
| } | |||
| public void setCheckName(String checkName) { | |||
| @@ -245,7 +243,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public String getReUserName() { | |||
| return reUserName; | |||
| return this.reUserName; | |||
| } | |||
| public void setReUserName(String reUserName) { | |||
| @@ -253,7 +251,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public Integer getAmount() { | |||
| return amount; | |||
| return this.amount; | |||
| } | |||
| public void setAmount(Integer amount) { | |||
| @@ -261,7 +259,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public String getDescription() { | |||
| return description; | |||
| return this.description; | |||
| } | |||
| public void setDescription(String description) { | |||
| @@ -269,7 +267,7 @@ public class WxEntPayRequest { | |||
| } | |||
| public String getSpbillCreateIp() { | |||
| return spbillCreateIp; | |||
| return this.spbillCreateIp; | |||
| } | |||
| public void setSpbillCreateIp(String spbillCreateIp) { | |||
| @@ -278,7 +276,7 @@ public class WxEntPayRequest { | |||
| @Override | |||
| public String toString() { | |||
| return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE); | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||
| @@ -0,0 +1,106 @@ | |||
| package me.chanjar.weixin.mp.bean.pay.request; | |||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||
| /** | |||
| * <pre> | |||
| * Created by Binary Wang on 2016-10-24. | |||
| * 微信支付请求对象共用的参数存放类 | |||
| * 注释中各行每个字段描述对应如下: | |||
| * <li>字段名 | |||
| * <li>变量名 | |||
| * <li>是否必填 | |||
| * <li>类型 | |||
| * <li>示例值 | |||
| * <li>描述 | |||
| * </pre> | |||
| * @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a> | |||
| */ | |||
| public abstract class WxPayBaseRequest { | |||
| /** | |||
| * <pre> | |||
| * 公众账号ID | |||
| * appid | |||
| * 是 | |||
| * String(32) | |||
| * wxd678efh567hg6787 | |||
| * 微信分配的公众账号ID(企业号corpid即为此appId) | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("appid") | |||
| protected String appid; | |||
| /** | |||
| * <pre> | |||
| * 商户号 | |||
| * mch_id | |||
| * 是 | |||
| * String(32) | |||
| * 1230000109 | |||
| * 微信支付分配的商户号 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("mch_id") | |||
| protected String mchId; | |||
| /** | |||
| * <pre> | |||
| * 随机字符串 | |||
| * nonce_str | |||
| * 是 | |||
| * String(32) | |||
| * 5K8264ILTKCH16CQ2502SI8ZNMTM67VS | |||
| * 随机字符串,不长于32位。推荐随机数生成算法 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("nonce_str") | |||
| protected String nonceStr; | |||
| /** | |||
| * <pre> | |||
| * 签名 | |||
| * sign | |||
| * 是 | |||
| * String(32) | |||
| * C380BEC2BFD727A4B6845133519F3AD6 | |||
| * 签名,详见签名生成算法 | |||
| * </pre> | |||
| */ | |||
| @XStreamAlias("sign") | |||
| protected String sign; | |||
| public String getAppid() { | |||
| return this.appid; | |||
| } | |||
| public void setAppid(String appid) { | |||
| this.appid = appid; | |||
| } | |||
| public String getMchId() { | |||
| return this.mchId; | |||
| } | |||
| public void setMchId(String mchId) { | |||
| this.mchId = mchId; | |||
| } | |||
| public String getNonceStr() { | |||
| return this.nonceStr; | |||
| } | |||
| public void setNonceStr(String nonceStr) { | |||
| this.nonceStr = nonceStr; | |||
| } | |||
| public String getSign() { | |||
| return this.sign; | |||
| } | |||
| public void setSign(String sign) { | |||
| this.sign = sign; | |||
| } | |||
| @Override | |||
| public String toString() { | |||
| return ToStringUtils.toSimpleString(this); | |||
| } | |||
| } | |||