@@ -0,0 +1,201 @@ | |||||
Apache License | |||||
Version 2.0, January 2004 | |||||
http://www.apache.org/licenses/ | |||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |||||
1. Definitions. | |||||
"License" shall mean the terms and conditions for use, reproduction, | |||||
and distribution as defined by Sections 1 through 9 of this document. | |||||
"Licensor" shall mean the copyright owner or entity authorized by | |||||
the copyright owner that is granting the License. | |||||
"Legal Entity" shall mean the union of the acting entity and all | |||||
other entities that control, are controlled by, or are under common | |||||
control with that entity. For the purposes of this definition, | |||||
"control" means (i) the power, direct or indirect, to cause the | |||||
direction or management of such entity, whether by contract or | |||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the | |||||
outstanding shares, or (iii) beneficial ownership of such entity. | |||||
"You" (or "Your") shall mean an individual or Legal Entity | |||||
exercising permissions granted by this License. | |||||
"Source" form shall mean the preferred form for making modifications, | |||||
including but not limited to software source code, documentation | |||||
source, and configuration files. | |||||
"Object" form shall mean any form resulting from mechanical | |||||
transformation or translation of a Source form, including but | |||||
not limited to compiled object code, generated documentation, | |||||
and conversions to other media types. | |||||
"Work" shall mean the work of authorship, whether in Source or | |||||
Object form, made available under the License, as indicated by a | |||||
copyright notice that is included in or attached to the work | |||||
(an example is provided in the Appendix below). | |||||
"Derivative Works" shall mean any work, whether in Source or Object | |||||
form, that is based on (or derived from) the Work and for which the | |||||
editorial revisions, annotations, elaborations, or other modifications | |||||
represent, as a whole, an original work of authorship. For the purposes | |||||
of this License, Derivative Works shall not include works that remain | |||||
separable from, or merely link (or bind by name) to the interfaces of, | |||||
the Work and Derivative Works thereof. | |||||
"Contribution" shall mean any work of authorship, including | |||||
the original version of the Work and any modifications or additions | |||||
to that Work or Derivative Works thereof, that is intentionally | |||||
submitted to Licensor for inclusion in the Work by the copyright owner | |||||
or by an individual or Legal Entity authorized to submit on behalf of | |||||
the copyright owner. For the purposes of this definition, "submitted" | |||||
means any form of electronic, verbal, or written communication sent | |||||
to the Licensor or its representatives, including but not limited to | |||||
communication on electronic mailing lists, source code control systems, | |||||
and issue tracking systems that are managed by, or on behalf of, the | |||||
Licensor for the purpose of discussing and improving the Work, but | |||||
excluding communication that is conspicuously marked or otherwise | |||||
designated in writing by the copyright owner as "Not a Contribution." | |||||
"Contributor" shall mean Licensor and any individual or Legal Entity | |||||
on behalf of whom a Contribution has been received by Licensor and | |||||
subsequently incorporated within the Work. | |||||
2. Grant of Copyright License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
copyright license to reproduce, prepare Derivative Works of, | |||||
publicly display, publicly perform, sublicense, and distribute the | |||||
Work and such Derivative Works in Source or Object form. | |||||
3. Grant of Patent License. Subject to the terms and conditions of | |||||
this License, each Contributor hereby grants to You a perpetual, | |||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |||||
(except as stated in this section) patent license to make, have made, | |||||
use, offer to sell, sell, import, and otherwise transfer the Work, | |||||
where such license applies only to those patent claims licensable | |||||
by such Contributor that are necessarily infringed by their | |||||
Contribution(s) alone or by combination of their Contribution(s) | |||||
with the Work to which such Contribution(s) was submitted. If You | |||||
institute patent litigation against any entity (including a | |||||
cross-claim or counterclaim in a lawsuit) alleging that the Work | |||||
or a Contribution incorporated within the Work constitutes direct | |||||
or contributory patent infringement, then any patent licenses | |||||
granted to You under this License for that Work shall terminate | |||||
as of the date such litigation is filed. | |||||
4. Redistribution. You may reproduce and distribute copies of the | |||||
Work or Derivative Works thereof in any medium, with or without | |||||
modifications, and in Source or Object form, provided that You | |||||
meet the following conditions: | |||||
(a) You must give any other recipients of the Work or | |||||
Derivative Works a copy of this License; and | |||||
(b) You must cause any modified files to carry prominent notices | |||||
stating that You changed the files; and | |||||
(c) You must retain, in the Source form of any Derivative Works | |||||
that You distribute, all copyright, patent, trademark, and | |||||
attribution notices from the Source form of the Work, | |||||
excluding those notices that do not pertain to any part of | |||||
the Derivative Works; and | |||||
(d) If the Work includes a "NOTICE" text file as part of its | |||||
distribution, then any Derivative Works that You distribute must | |||||
include a readable copy of the attribution notices contained | |||||
within such NOTICE file, excluding those notices that do not | |||||
pertain to any part of the Derivative Works, in at least one | |||||
of the following places: within a NOTICE text file distributed | |||||
as part of the Derivative Works; within the Source form or | |||||
documentation, if provided along with the Derivative Works; or, | |||||
within a display generated by the Derivative Works, if and | |||||
wherever such third-party notices normally appear. The contents | |||||
of the NOTICE file are for informational purposes only and | |||||
do not modify the License. You may add Your own attribution | |||||
notices within Derivative Works that You distribute, alongside | |||||
or as an addendum to the NOTICE text from the Work, provided | |||||
that such additional attribution notices cannot be construed | |||||
as modifying the License. | |||||
You may add Your own copyright statement to Your modifications and | |||||
may provide additional or different license terms and conditions | |||||
for use, reproduction, or distribution of Your modifications, or | |||||
for any such Derivative Works as a whole, provided Your use, | |||||
reproduction, and distribution of the Work otherwise complies with | |||||
the conditions stated in this License. | |||||
5. Submission of Contributions. Unless You explicitly state otherwise, | |||||
any Contribution intentionally submitted for inclusion in the Work | |||||
by You to the Licensor shall be under the terms and conditions of | |||||
this License, without any additional terms or conditions. | |||||
Notwithstanding the above, nothing herein shall supersede or modify | |||||
the terms of any separate license agreement you may have executed | |||||
with Licensor regarding such Contributions. | |||||
6. Trademarks. This License does not grant permission to use the trade | |||||
names, trademarks, service marks, or product names of the Licensor, | |||||
except as required for reasonable and customary use in describing the | |||||
origin of the Work and reproducing the content of the NOTICE file. | |||||
7. Disclaimer of Warranty. Unless required by applicable law or | |||||
agreed to in writing, Licensor provides the Work (and each | |||||
Contributor provides its Contributions) on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |||||
implied, including, without limitation, any warranties or conditions | |||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |||||
PARTICULAR PURPOSE. You are solely responsible for determining the | |||||
appropriateness of using or redistributing the Work and assume any | |||||
risks associated with Your exercise of permissions under this License. | |||||
8. Limitation of Liability. In no event and under no legal theory, | |||||
whether in tort (including negligence), contract, or otherwise, | |||||
unless required by applicable law (such as deliberate and grossly | |||||
negligent acts) or agreed to in writing, shall any Contributor be | |||||
liable to You for damages, including any direct, indirect, special, | |||||
incidental, or consequential damages of any character arising as a | |||||
result of this License or out of the use or inability to use the | |||||
Work (including but not limited to damages for loss of goodwill, | |||||
work stoppage, computer failure or malfunction, or any and all | |||||
other commercial damages or losses), even if such Contributor | |||||
has been advised of the possibility of such damages. | |||||
9. Accepting Warranty or Additional Liability. While redistributing | |||||
the Work or Derivative Works thereof, You may choose to offer, | |||||
and charge a fee for, acceptance of support, warranty, indemnity, | |||||
or other liability obligations and/or rights consistent with this | |||||
License. However, in accepting such obligations, You may act only | |||||
on Your own behalf and on Your sole responsibility, not on behalf | |||||
of any other Contributor, and only if You agree to indemnify, | |||||
defend, and hold each Contributor harmless for any liability | |||||
incurred by, or claims asserted against, such Contributor by reason | |||||
of your accepting any such warranty or additional liability. | |||||
END OF TERMS AND CONDITIONS | |||||
APPENDIX: How to apply the Apache License to your work. | |||||
To apply the Apache License to your work, attach the following | |||||
boilerplate notice, with the fields enclosed by brackets "{}" | |||||
replaced with your own identifying information. (Don't include | |||||
the brackets!) The text should be enclosed in the appropriate | |||||
comment syntax for the file format. We also recommend that a | |||||
file or class name and description of purpose be included on the | |||||
same "printed page" as the copyright notice for easier | |||||
identification within third-party archives. | |||||
Copyright {yyyy} {name of copyright owner} | |||||
Licensed under the Apache License, Version 2.0 (the "License"); | |||||
you may not use this file except in compliance with the License. | |||||
You may obtain a copy of the License at | |||||
http://www.apache.org/licenses/LICENSE-2.0 | |||||
Unless required by applicable law or agreed to in writing, software | |||||
distributed under the License is distributed on an "AS IS" BASIS, | |||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
See the License for the specific language governing permissions and | |||||
limitations under the License. |
@@ -19,7 +19,7 @@ weixin-java-tools | |||||
<dependency> | <dependency> | ||||
<groupId>me.chanjar</groupId> | <groupId>me.chanjar</groupId> | ||||
<artifactId>weixin-java-mp</artifactId> | <artifactId>weixin-java-mp</artifactId> | ||||
<version>1.0.6</version> | |||||
<version>1.0.7</version> | |||||
</dependency> | </dependency> | ||||
``` | ``` | ||||
@@ -29,10 +29,24 @@ weixin-java-tools | |||||
<dependency> | <dependency> | ||||
<groupId>me.chanjar</groupId> | <groupId>me.chanjar</groupId> | ||||
<artifactId>weixin-java-cp</artifactId> | <artifactId>weixin-java-cp</artifactId> | ||||
<version>1.0.6</version> | |||||
<version>1.0.7</version> | |||||
</dependency> | </dependency> | ||||
``` | ``` | ||||
## SNAPSHOT版 | |||||
本项目的BUG修复和新特性一般会先发布在*-SNAPSHOT版里供大家预览,如果要使用*-SNAPSHOT版,则需要在你的pom.xml中添加这段: | |||||
```xml | |||||
<repositories> | |||||
<repository> | |||||
<snapshots /> | |||||
<id>sonatype snapshots</id> | |||||
<url>https://oss.sonatype.org/content/repositories/snapshots/</url> | |||||
</repository> | |||||
</repositories> | |||||
``` | |||||
## 升级指南 | ## 升级指南 | ||||
原``1.0.0~1.0.2``版本用户无法平滑升级到``1.0.3``。需要做的是: | 原``1.0.0~1.0.2``版本用户无法平滑升级到``1.0.3``。需要做的是: | ||||
@@ -1,229 +1,232 @@ | |||||
<?xml version="1.0"?> | <?xml version="1.0"?> | ||||
<project | <project | ||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-parent</artifactId> | |||||
<version>1.0.6</version> | |||||
<packaging>pom</packaging> | |||||
<name>WeiXin Java Tools - Parent</name> | |||||
<description>微信公众号、企业号上级POM</description> | |||||
<url>https://github.com/chanjarster/weixin-java-tools</url> | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-parent</artifactId> | |||||
<version>1.1.0-SNAPSHOT</version> | |||||
<packaging>pom</packaging> | |||||
<name>WeiXin Java Tools - Parent</name> | |||||
<description>微信公众号、企业号上级POM</description> | |||||
<url>https://github.com/chanjarster/weixin-java-tools</url> | |||||
<licenses> | |||||
<license> | |||||
<name>The Apache License, Version 2.0</name> | |||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> | |||||
</license> | |||||
</licenses> | |||||
<licenses> | |||||
<license> | |||||
<name>The Apache License, Version 2.0</name> | |||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> | |||||
</license> | |||||
</licenses> | |||||
<developers> | |||||
<developer> | |||||
<name>Daniel Qian</name> | |||||
<email>chanjarster@gmail.com</email> | |||||
</developer> | |||||
</developers> | |||||
<developers> | |||||
<developer> | |||||
<name>Daniel Qian</name> | |||||
<email>chanjarster@gmail.com</email> | |||||
</developer> | |||||
</developers> | |||||
<scm> | |||||
<connection>scm:git:https://github.com/chanjarster/weixin-java-tools.git</connection> | |||||
<developerConnection>scm:git:git@github.com:chanjarster/weixin-java-tools.git</developerConnection> | |||||
<url>https://github.com/chanjarster/weixin-java-tools</url> | |||||
</scm> | |||||
<scm> | |||||
<connection>scm:git:https://github.com/chanjarster/weixin-java-tools.git</connection> | |||||
<developerConnection>scm:git:git@github.com:chanjarster/weixin-java-tools.git</developerConnection> | |||||
<url>https://github.com/chanjarster/weixin-java-tools</url> | |||||
</scm> | |||||
<modules> | |||||
<module>weixin-java-common</module> | |||||
<module>weixin-java-cp</module> | |||||
<module>weixin-java-mp</module> | |||||
</modules> | |||||
<modules> | |||||
<module>weixin-java-common</module> | |||||
<module>weixin-java-cp</module> | |||||
<module>weixin-java-mp</module> | |||||
</modules> | |||||
<properties> | |||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||||
<downloadJavadocs>true</downloadJavadocs> | |||||
<downloadSources>true</downloadSources> | |||||
<httpclient.version>4.3.5</httpclient.version> | |||||
<slf4j.version>1.7.10</slf4j.version> | |||||
<logback.version>1.1.2</logback.version> | |||||
</properties> | |||||
<properties> | |||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||||
<downloadJavadocs>true</downloadJavadocs> | |||||
<downloadSources>true</downloadSources> | |||||
<httpclient.version>4.3.5</httpclient.version> | |||||
</properties> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>org.slf4j</groupId> | |||||
<artifactId>slf4j-api</artifactId> | |||||
<version>${slf4j.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>ch.qos.logback</groupId> | |||||
<artifactId>logback-classic</artifactId> | |||||
<version>${logback.version}</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.apache.httpcomponents</groupId> | |||||
<artifactId>fluent-hc</artifactId> | |||||
<version>${httpclient.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.apache.httpcomponents</groupId> | |||||
<artifactId>httpmime</artifactId> | |||||
<version>${httpclient.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.code.gson</groupId> | |||||
<artifactId>gson</artifactId> | |||||
<version>2.2.2</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>commons-codec</groupId> | |||||
<artifactId>commons-codec</artifactId> | |||||
<version>1.9</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>commons-io</groupId> | |||||
<artifactId>commons-io</artifactId> | |||||
<version>2.4</version> | |||||
</dependency> | |||||
</dependencies> | |||||
<dependencyManagement> | |||||
<dependencies> | <dependencies> | ||||
<dependency> | |||||
<groupId>org.apache.httpcomponents</groupId> | |||||
<artifactId>fluent-hc</artifactId> | |||||
<version>${httpclient.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.apache.httpcomponents</groupId> | |||||
<artifactId>httpmime</artifactId> | |||||
<version>${httpclient.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>javax.xml.bind</groupId> | |||||
<artifactId>jaxb-api</artifactId> | |||||
<version>2.2.7</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.sun.xml.bind</groupId> | |||||
<artifactId>jaxb-impl</artifactId> | |||||
<version>2.2.7</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.code.gson</groupId> | |||||
<artifactId>gson</artifactId> | |||||
<version>2.2.2</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>commons-codec</groupId> | |||||
<artifactId>commons-codec</artifactId> | |||||
<version>1.9</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>commons-io</groupId> | |||||
<artifactId>commons-io</artifactId> | |||||
<version>2.4</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<version>4.11</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.inject</groupId> | |||||
<artifactId>guice</artifactId> | |||||
<version>3.0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.testng</groupId> | |||||
<artifactId>testng</artifactId> | |||||
<version>6.8.7</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-all</artifactId> | |||||
<version>1.9.5</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-server</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-servlet</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | </dependencies> | ||||
</dependencyManagement> | |||||
<dependencyManagement> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<version>4.11</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.inject</groupId> | |||||
<artifactId>guice</artifactId> | |||||
<version>3.0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.testng</groupId> | |||||
<artifactId>testng</artifactId> | |||||
<version>6.8.7</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-all</artifactId> | |||||
<version>1.9.5</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-server</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-servlet</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
</dependencyManagement> | |||||
<distributionManagement> | |||||
<snapshotRepository> | |||||
<id>ossrh</id> | |||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url> | |||||
</snapshotRepository> | |||||
<repository> | |||||
<id>ossrh</id> | |||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> | |||||
</repository> | |||||
</distributionManagement> | |||||
<profiles> | |||||
<profile> | |||||
<id>release</id> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-source-plugin</artifactId> | |||||
<version>2.2.1</version> | |||||
<executions> | |||||
<execution> | |||||
<id>attach-sources</id> | |||||
<goals> | |||||
<goal>jar-no-fork</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-javadoc-plugin</artifactId> | |||||
<version>2.9.1</version> | |||||
<executions> | |||||
<execution> | |||||
<id>attach-javadocs</id> | |||||
<goals> | |||||
<goal>jar</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
<configuration> | |||||
<charset>UTF-8</charset> | |||||
<locale>zh_CN</locale> | |||||
</configuration> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-gpg-plugin</artifactId> | |||||
<version>1.5</version> | |||||
<executions> | |||||
<execution> | |||||
<id>sign-artifacts</id> | |||||
<phase>verify</phase> | |||||
<goals> | |||||
<goal>sign</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</profile> | |||||
</profiles> | |||||
<build> | |||||
<pluginManagement> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-surefire-plugin</artifactId> | |||||
<version>2.17</version> | |||||
</plugin> | |||||
</plugins> | |||||
</pluginManagement> | |||||
<distributionManagement> | |||||
<snapshotRepository> | |||||
<id>ossrh</id> | |||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url> | |||||
</snapshotRepository> | |||||
<repository> | |||||
<id>ossrh</id> | |||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> | |||||
</repository> | |||||
</distributionManagement> | |||||
<profiles> | |||||
<profile> | |||||
<id>release</id> | |||||
<build> | |||||
<plugins> | <plugins> | ||||
<plugin> | |||||
<groupId>org.sonatype.plugins</groupId> | |||||
<artifactId>nexus-staging-maven-plugin</artifactId> | |||||
<version>1.6.3</version> | |||||
<extensions>true</extensions> | |||||
<configuration> | |||||
<serverId>ossrh</serverId> | |||||
<nexusUrl>https://oss.sonatype.org/</nexusUrl> | |||||
<autoReleaseAfterClose>false</autoReleaseAfterClose> | |||||
</configuration> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-release-plugin</artifactId> | |||||
<version>2.5</version> | |||||
<configuration> | |||||
<autoVersionSubmodules>true</autoVersionSubmodules> | |||||
<useReleaseProfile>false</useReleaseProfile> | |||||
<releaseProfiles>release</releaseProfiles> | |||||
<goals>deploy</goals> | |||||
</configuration> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-source-plugin</artifactId> | |||||
<version>2.2.1</version> | |||||
<executions> | |||||
<execution> | |||||
<id>attach-sources</id> | |||||
<goals> | |||||
<goal>jar-no-fork</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-javadoc-plugin</artifactId> | |||||
<version>2.9.1</version> | |||||
<executions> | |||||
<execution> | |||||
<id>attach-javadocs</id> | |||||
<goals> | |||||
<goal>jar</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
<configuration> | |||||
<charset>UTF-8</charset> | |||||
<locale>zh_CN</locale> | |||||
</configuration> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-gpg-plugin</artifactId> | |||||
<version>1.5</version> | |||||
<executions> | |||||
<execution> | |||||
<id>sign-artifacts</id> | |||||
<phase>verify</phase> | |||||
<goals> | |||||
<goal>sign</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
</plugins> | </plugins> | ||||
</build> | |||||
</build> | |||||
</profile> | |||||
</profiles> | |||||
<build> | |||||
<pluginManagement> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-surefire-plugin</artifactId> | |||||
<version>2.17</version> | |||||
</plugin> | |||||
</plugins> | |||||
</pluginManagement> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.sonatype.plugins</groupId> | |||||
<artifactId>nexus-staging-maven-plugin</artifactId> | |||||
<version>1.6.3</version> | |||||
<extensions>true</extensions> | |||||
<configuration> | |||||
<serverId>ossrh</serverId> | |||||
<nexusUrl>https://oss.sonatype.org/</nexusUrl> | |||||
<autoReleaseAfterClose>false</autoReleaseAfterClose> | |||||
</configuration> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-release-plugin</artifactId> | |||||
<version>2.5</version> | |||||
<configuration> | |||||
<autoVersionSubmodules>true</autoVersionSubmodules> | |||||
<useReleaseProfile>false</useReleaseProfile> | |||||
<releaseProfiles>release</releaseProfiles> | |||||
<goals>deploy</goals> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</project> | </project> |
@@ -1,229 +0,0 @@ | |||||
<?xml version="1.0"?> | |||||
<project | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-parent</artifactId> | |||||
<version>1.0.5-SNAPSHOT</version> | |||||
<packaging>pom</packaging> | |||||
<name>WeiXin Java Tools - Parent</name> | |||||
<description>微信公众号、企业号上级POM</description> | |||||
<url>https://github.com/chanjarster/weixin-java-tools</url> | |||||
<licenses> | |||||
<license> | |||||
<name>The Apache License, Version 2.0</name> | |||||
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> | |||||
</license> | |||||
</licenses> | |||||
<developers> | |||||
<developer> | |||||
<name>Daniel Qian</name> | |||||
<email>chanjarster@gmail.com</email> | |||||
</developer> | |||||
</developers> | |||||
<scm> | |||||
<connection>scm:git:https://github.com/chanjarster/weixin-java-tools.git</connection> | |||||
<developerConnection>scm:git:git@github.com:chanjarster/weixin-java-tools.git</developerConnection> | |||||
<url>https://github.com/chanjarster/weixin-java-tools</url> | |||||
</scm> | |||||
<modules> | |||||
<module>weixin-java-common</module> | |||||
<module>weixin-java-cp</module> | |||||
<module>weixin-java-mp</module> | |||||
</modules> | |||||
<properties> | |||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||||
<downloadJavadocs>true</downloadJavadocs> | |||||
<downloadSources>true</downloadSources> | |||||
<httpclient.version>4.3.5</httpclient.version> | |||||
</properties> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>org.apache.httpcomponents</groupId> | |||||
<artifactId>fluent-hc</artifactId> | |||||
<version>${httpclient.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.apache.httpcomponents</groupId> | |||||
<artifactId>httpmime</artifactId> | |||||
<version>${httpclient.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>javax.xml.bind</groupId> | |||||
<artifactId>jaxb-api</artifactId> | |||||
<version>2.2.7</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.sun.xml.bind</groupId> | |||||
<artifactId>jaxb-impl</artifactId> | |||||
<version>2.2.7</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.code.gson</groupId> | |||||
<artifactId>gson</artifactId> | |||||
<version>2.2.2</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>commons-codec</groupId> | |||||
<artifactId>commons-codec</artifactId> | |||||
<version>1.9</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>commons-io</groupId> | |||||
<artifactId>commons-io</artifactId> | |||||
<version>2.4</version> | |||||
</dependency> | |||||
</dependencies> | |||||
<dependencyManagement> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<version>4.11</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.inject</groupId> | |||||
<artifactId>guice</artifactId> | |||||
<version>3.0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.testng</groupId> | |||||
<artifactId>testng</artifactId> | |||||
<version>6.8.7</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-all</artifactId> | |||||
<version>1.9.5</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-server</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-servlet</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
</dependencyManagement> | |||||
<distributionManagement> | |||||
<snapshotRepository> | |||||
<id>ossrh</id> | |||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url> | |||||
</snapshotRepository> | |||||
<repository> | |||||
<id>ossrh</id> | |||||
<url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> | |||||
</repository> | |||||
</distributionManagement> | |||||
<profiles> | |||||
<profile> | |||||
<id>release</id> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-source-plugin</artifactId> | |||||
<version>2.2.1</version> | |||||
<executions> | |||||
<execution> | |||||
<id>attach-sources</id> | |||||
<goals> | |||||
<goal>jar-no-fork</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-javadoc-plugin</artifactId> | |||||
<version>2.9.1</version> | |||||
<executions> | |||||
<execution> | |||||
<id>attach-javadocs</id> | |||||
<goals> | |||||
<goal>jar</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
<configuration> | |||||
<charset>UTF-8</charset> | |||||
<locale>zh_CN</locale> | |||||
</configuration> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-gpg-plugin</artifactId> | |||||
<version>1.5</version> | |||||
<executions> | |||||
<execution> | |||||
<id>sign-artifacts</id> | |||||
<phase>verify</phase> | |||||
<goals> | |||||
<goal>sign</goal> | |||||
</goals> | |||||
</execution> | |||||
</executions> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</profile> | |||||
</profiles> | |||||
<build> | |||||
<pluginManagement> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-surefire-plugin</artifactId> | |||||
<version>2.17</version> | |||||
</plugin> | |||||
</plugins> | |||||
</pluginManagement> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.sonatype.plugins</groupId> | |||||
<artifactId>nexus-staging-maven-plugin</artifactId> | |||||
<version>1.6.3</version> | |||||
<extensions>true</extensions> | |||||
<configuration> | |||||
<serverId>ossrh</serverId> | |||||
<nexusUrl>https://oss.sonatype.org/</nexusUrl> | |||||
<autoReleaseAfterClose>false</autoReleaseAfterClose> | |||||
</configuration> | |||||
</plugin> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-release-plugin</artifactId> | |||||
<version>2.5</version> | |||||
<configuration> | |||||
<autoVersionSubmodules>true</autoVersionSubmodules> | |||||
<useReleaseProfile>false</useReleaseProfile> | |||||
<releaseProfiles>release</releaseProfiles> | |||||
<goals>deploy</goals> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</project> |
@@ -1,68 +1,73 @@ | |||||
<?xml version="1.0"?> | <?xml version="1.0"?> | ||||
<project | <project | ||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<parent> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-parent</artifactId> | |||||
<version>1.0.6</version> | |||||
</parent> | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<parent> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-parent</artifactId> | |||||
<version>1.1.0-SNAPSHOT</version> | |||||
</parent> | |||||
<artifactId>weixin-java-common</artifactId> | |||||
<name>WeiXin Java Tools - Common</name> | |||||
<description>微信公众号、企业号Java SDK Common</description> | |||||
<artifactId>weixin-java-common</artifactId> | |||||
<name>WeiXin Java Tools - Common</name> | |||||
<description>微信公众号、企业号Java SDK Common</description> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.testng</groupId> | |||||
<artifactId>testng</artifactId> | |||||
<version>6.8.7</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-all</artifactId> | |||||
<version>1.9.5</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.inject</groupId> | |||||
<artifactId>guice</artifactId> | |||||
<version>3.0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-server</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-servlet</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>com.thoughtworks.xstream</groupId> | |||||
<artifactId>xstream</artifactId> | |||||
<version>1.4.7</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.testng</groupId> | |||||
<artifactId>testng</artifactId> | |||||
<version>6.8.7</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-all</artifactId> | |||||
<version>1.9.5</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.inject</groupId> | |||||
<artifactId>guice</artifactId> | |||||
<version>3.0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-server</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-servlet</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-surefire-plugin</artifactId> | |||||
<configuration> | |||||
<suiteXmlFiles> | |||||
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> | |||||
</suiteXmlFiles> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-surefire-plugin</artifactId> | |||||
<configuration> | |||||
<suiteXmlFiles> | |||||
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> | |||||
</suiteXmlFiles> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</project> | </project> |
@@ -1,68 +0,0 @@ | |||||
<?xml version="1.0"?> | |||||
<project | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<parent> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-parent</artifactId> | |||||
<version>1.0.5-SNAPSHOT</version> | |||||
</parent> | |||||
<artifactId>weixin-java-common</artifactId> | |||||
<name>WeiXin Java Tools - Common</name> | |||||
<description>微信公众号、企业号Java SDK Common</description> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.testng</groupId> | |||||
<artifactId>testng</artifactId> | |||||
<version>6.8.7</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-all</artifactId> | |||||
<version>1.9.5</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.inject</groupId> | |||||
<artifactId>guice</artifactId> | |||||
<version>3.0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-server</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-servlet</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-surefire-plugin</artifactId> | |||||
<configuration> | |||||
<suiteXmlFiles> | |||||
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> | |||||
</suiteXmlFiles> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</project> |
@@ -0,0 +1,31 @@ | |||||
/* | |||||
* Licensed to the Apache Software Foundation (ASF) under one or more | |||||
* contributor license agreements. See the NOTICE file distributed with | |||||
* this work for additional information regarding copyright ownership. | |||||
* The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
* (the "License"); you may not use this file except in compliance with | |||||
* the License. You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package me.chanjar.weixin.common.session; | |||||
/** | |||||
* Manifest constants for the <code>org.apache.catalina.session</code> | |||||
* package. | |||||
* | |||||
* @author Craig R. McClanahan | |||||
*/ | |||||
public class Constants { | |||||
public static final String Package = "me.chanjar.weixin.common.session"; | |||||
} |
@@ -0,0 +1,68 @@ | |||||
package me.chanjar.weixin.common.session; | |||||
public interface InternalSession { | |||||
/** | |||||
* Return the <code>HttpSession</code> for which this object | |||||
* is the facade. | |||||
*/ | |||||
WxSession getSession(); | |||||
/** | |||||
* Set the <code>isValid</code> flag for this session. | |||||
* | |||||
* @param isValid The new value for the <code>isValid</code> flag | |||||
*/ | |||||
public void setValid(boolean isValid); | |||||
/** | |||||
* Return the <code>isValid</code> flag for this session. | |||||
*/ | |||||
boolean isValid(); | |||||
/** | |||||
* Return the session identifier for this session. | |||||
*/ | |||||
String getIdInternal(); | |||||
/** | |||||
* Perform the internal processing required to invalidate this session, | |||||
* without triggering an exception if the session has already expired. | |||||
*/ | |||||
void expire(); | |||||
/** | |||||
* Update the accessed time information for this session. This method | |||||
* should be called by the context when a request comes in for a particular | |||||
* session, even if the application does not reference it. | |||||
*/ | |||||
void access(); | |||||
/** | |||||
* End the access. | |||||
*/ | |||||
void endAccess(); | |||||
/** | |||||
* Set the creation time for this session. This method is called by the | |||||
* Manager when an existing Session instance is reused. | |||||
* | |||||
* @param time The new creation time | |||||
*/ | |||||
void setCreationTime(long time); | |||||
/** | |||||
* Set the default maximum inactive interval (in seconds) | |||||
* for Sessions created by this Manager. | |||||
* | |||||
* @param interval The new default value | |||||
*/ | |||||
void setMaxInactiveInterval(int interval); | |||||
/** | |||||
* Set the session identifier for this session. | |||||
* | |||||
* @param id The new session identifier | |||||
*/ | |||||
void setId(String id); | |||||
} |
@@ -0,0 +1,113 @@ | |||||
package me.chanjar.weixin.common.session; | |||||
public interface InternalSessionManager { | |||||
/** | |||||
* Return the active Session, associated with this Manager, with the | |||||
* specified session id (if any); otherwise return <code>null</code>. | |||||
* | |||||
* @param id The session id for the session to be returned | |||||
* | |||||
* @exception IllegalStateException if a new session cannot be | |||||
* instantiated for any reason | |||||
* @exception java.io.IOException if an input/output error occurs while | |||||
* processing this request | |||||
*/ | |||||
InternalSession findSession(String id); | |||||
/** | |||||
* Construct and return a new session object, based on the default | |||||
* settings specified by this Manager's properties. The session | |||||
* id specified will be used as the session id. | |||||
* If a new session cannot be created for any reason, return | |||||
* <code>null</code>. | |||||
* | |||||
* @param sessionId The session id which should be used to create the | |||||
* new session; if <code>null</code>, a new session id will be | |||||
* generated | |||||
* @exception IllegalStateException if a new session cannot be | |||||
* instantiated for any reason | |||||
*/ | |||||
public InternalSession createSession(String sessionId); | |||||
/** | |||||
* Remove this Session from the active Sessions for this Manager. | |||||
* | |||||
* @param session Session to be removed | |||||
*/ | |||||
public void remove(InternalSession session); | |||||
/** | |||||
* Remove this Session from the active Sessions for this Manager. | |||||
* | |||||
* @param session Session to be removed | |||||
* @param update Should the expiration statistics be updated | |||||
*/ | |||||
public void remove(InternalSession session, boolean update); | |||||
/** | |||||
* Add this Session to the set of active Sessions for this Manager. | |||||
* | |||||
* @param session Session to be added | |||||
*/ | |||||
void add(InternalSession session); | |||||
/** | |||||
* Returns the number of active sessions | |||||
* | |||||
* @return number of sessions active | |||||
*/ | |||||
int getActiveSessions(); | |||||
/** | |||||
* Get a session from the recycled ones or create a new empty one. | |||||
* The PersistentManager manager does not need to create session data | |||||
* because it reads it from the Store. | |||||
*/ | |||||
InternalSession createEmptySession(); | |||||
InternalSession[] findSessions(); | |||||
/** | |||||
* Implements the Manager interface, direct call to processExpires | |||||
*/ | |||||
public void backgroundProcess(); | |||||
/** | |||||
* Set the default maximum inactive interval (in seconds) | |||||
* for Sessions created by this Manager. | |||||
* | |||||
* @param interval The new default value | |||||
*/ | |||||
void setMaxInactiveInterval(int interval); | |||||
/** | |||||
* <pre> | |||||
* Set the manager checks frequency. | |||||
* 设置每尝试多少次清理过期session,才真的会执行一次清理动作 | |||||
* 要和{@link #setBackgroundProcessorDelay(int)}联合起来看 | |||||
* 如果把这个数字设置为6(默认),那么就是说manager要等待 6 * backgroundProcessorDay的时间才会清理过期session | |||||
* </pre> | |||||
* @param processExpiresFrequency the new manager checks frequency | |||||
*/ | |||||
void setProcessExpiresFrequency(int processExpiresFrequency); | |||||
/** | |||||
* <pre> | |||||
* Set the manager background processor delay | |||||
* 设置manager sleep几秒,尝试执行一次background操作(清理过期session) | |||||
* </pre> | |||||
* @param backgroundProcessorDelay | |||||
*/ | |||||
void setBackgroundProcessorDelay(int backgroundProcessorDelay); | |||||
/** | |||||
* Set the maximum number of active Sessions allowed, or -1 for | |||||
* no limit. | |||||
* 设置最大活跃session数,默认无限 | |||||
* @param max The new maximum number of sessions | |||||
*/ | |||||
void setMaxActiveSessions(int max); | |||||
} |
@@ -0,0 +1,80 @@ | |||||
# Licensed to the Apache Software Foundation (ASF) under one or more | |||||
# contributor license agreements. See the NOTICE file distributed with | |||||
# this work for additional information regarding copyright ownership. | |||||
# The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
# (the "License"); you may not use this file except in compliance with | |||||
# the License. You may obtain a copy of the License at | |||||
# | |||||
# http://www.apache.org/licenses/LICENSE-2.0 | |||||
# | |||||
# Unless required by applicable law or agreed to in writing, software | |||||
# distributed under the License is distributed on an "AS IS" BASIS, | |||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
# See the License for the specific language governing permissions and | |||||
# limitations under the License. | |||||
applicationSession.session.ise=invalid session state | |||||
applicationSession.value.iae=null value | |||||
fileStore.saving=Saving Session {0} to file {1} | |||||
fileStore.loading=Loading Session {0} from file {1} | |||||
fileStore.removing=Removing Session {0} at file {1} | |||||
fileStore.deleteFailed=Unable to delete file [{0}] which is preventing the creation of the session storage location | |||||
fileStore.createFailed=Unable to create directory [{0}] for the storage of session data | |||||
JDBCStore.close=Exception closing database connection {0} | |||||
JDBCStore.saving=Saving Session {0} to database {1} | |||||
JDBCStore.loading=Loading Session {0} from database {1} | |||||
JDBCStore.removing=Removing Session {0} at database {1} | |||||
JDBCStore.SQLException=SQL Error {0} | |||||
serverSession.value.iae=null value | |||||
sessionManagerImpl.createRandom=Created random number generator for session ID generation in {0}ms. | |||||
sessionManagerImpl.createSession.tmase=createSession: Too many active sessions | |||||
sessionManagerImpl.sessionTimeout=Invalid session timeout setting {0} | |||||
sessionManagerImpl.getSession.ise=getSession: Session id cannot be null | |||||
sessionManagerImpl.expireException=processsExpire: Exception during session expiration | |||||
sessionManagerImpl.loading=Loading persisted sessions from {0} | |||||
sessionManagerImpl.loading.cnfe=ClassNotFoundException while loading persisted sessions: {0} | |||||
sessionManagerImpl.loading.ioe=IOException while loading persisted sessions: {0} | |||||
sessionManagerImpl.unloading=Saving persisted sessions to {0} | |||||
sessionManagerImpl.unloading.debug=Unloading persisted sessions | |||||
sessionManagerImpl.unloading.ioe=IOException while saving persisted sessions: {0} | |||||
sessionManagerImpl.unloading.nosessions=No persisted sessions to unload | |||||
sessionManagerImpl.managerLoad=Exception loading sessions from persistent storage | |||||
sessionManagerImpl.managerUnload=Exception unloading sessions to persistent storage | |||||
sessionManagerImpl.createSession.ise=createSession: Session id cannot be null | |||||
sessionImpl.attributeEvent=Session attribute event listener threw exception | |||||
sessionImpl.bindingEvent=Session binding event listener threw exception | |||||
sessionImpl.invalidate.ise=invalidate: Session already invalidated | |||||
sessionImpl.isNew.ise=isNew: Session already invalidated | |||||
sessionImpl.getAttribute.ise=getAttribute: Session already invalidated | |||||
sessionImpl.getAttributeNames.ise=getAttributeNames: Session already invalidated | |||||
sessionImpl.getCreationTime.ise=getCreationTime: Session already invalidated | |||||
sessionImpl.getThisAccessedTime.ise=getThisAccessedTime: Session already invalidated | |||||
sessionImpl.getLastAccessedTime.ise=getLastAccessedTime: Session already invalidated | |||||
sessionImpl.getId.ise=getId: Session already invalidated | |||||
sessionImpl.getMaxInactiveInterval.ise=getMaxInactiveInterval: Session already invalidated | |||||
sessionImpl.getValueNames.ise=getValueNames: Session already invalidated | |||||
sessionImpl.logoutfail=Exception logging out user when expiring session | |||||
sessionImpl.notSerializable=Cannot serialize session attribute {0} for session {1} | |||||
sessionImpl.removeAttribute.ise=removeAttribute: Session already invalidated | |||||
sessionImpl.sessionEvent=Session event listener threw exception | |||||
sessionImpl.setAttribute.iae=setAttribute: Non-serializable attribute {0} | |||||
sessionImpl.setAttribute.ise=setAttribute: Session [{0}] has already been invalidated | |||||
sessionImpl.setAttribute.namenull=setAttribute: name parameter cannot be null | |||||
sessionImpl.sessionCreated=Created Session id = {0} | |||||
persistentManager.loading=Loading {0} persisted sessions | |||||
persistentManager.unloading=Saving {0} persisted sessions | |||||
persistentManager.expiring=Expiring {0} sessions before saving them | |||||
persistentManager.deserializeError=Error deserializing Session {0}: {1} | |||||
persistentManager.serializeError=Error serializing Session {0}: {1} | |||||
persistentManager.swapMaxIdle=Swapping session {0} to Store, idle for {1} seconds | |||||
persistentManager.backupMaxIdle=Backing up session {0} to Store, idle for {1} seconds | |||||
persistentManager.backupException=Exception occurred when backing up Session {0}: {1} | |||||
persistentManager.tooManyActive=Too many active sessions, {0}, looking for idle sessions to swap out | |||||
persistentManager.swapTooManyActive=Swapping out session {0}, idle for {1} seconds too many sessions active | |||||
persistentManager.processSwaps=Checking for sessions to swap out, {0} active sessions in memory | |||||
persistentManager.activeSession=Session {0} has been idle for {1} seconds | |||||
persistentManager.swapIn=Swapping session {0} in from Store | |||||
persistentManager.swapInException=Exception in the Store during swapIn: {0} | |||||
persistentManager.swapInInvalid=Swapped session {0} is invalid | |||||
persistentManager.storeKeysException=Unable to determine the list of session IDs for sessions in the session store, assuming that the store is empty | |||||
persistentManager.storeSizeException=Unable to determine the number of sessions in the session store, assuming that the store is empty |
@@ -0,0 +1,346 @@ | |||||
package me.chanjar.weixin.common.session; | |||||
import me.chanjar.weixin.common.util.res.StringManager; | |||||
import java.util.*; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import java.util.concurrent.atomic.AtomicInteger; | |||||
public class StandardSession implements WxSession, InternalSession { | |||||
/** | |||||
* The string manager for this package. | |||||
*/ | |||||
protected static final StringManager sm = | |||||
StringManager.getManager(Constants.Package); | |||||
// ------------------------------ WxSession | |||||
protected Map<String, Object> attributes = new ConcurrentHashMap<String, Object>(); | |||||
@Override | |||||
public Object getAttribute(String name) { | |||||
if (!isValidInternal()) | |||||
throw new IllegalStateException | |||||
(sm.getString("sessionImpl.getAttribute.ise")); | |||||
if (name == null) return null; | |||||
return (attributes.get(name)); | |||||
} | |||||
@Override | |||||
public Enumeration<String> getAttributeNames() { | |||||
if (!isValidInternal()) | |||||
throw new IllegalStateException | |||||
(sm.getString("sessionImpl.getAttributeNames.ise")); | |||||
Set<String> names = new HashSet<String>(); | |||||
names.addAll(attributes.keySet()); | |||||
return Collections.enumeration(names); | |||||
} | |||||
@Override | |||||
public void setAttribute(String name, Object value) { | |||||
// Name cannot be null | |||||
if (name == null) | |||||
throw new IllegalArgumentException | |||||
(sm.getString("sessionImpl.setAttribute.namenull")); | |||||
// Null value is the same as removeAttribute() | |||||
if (value == null) { | |||||
removeAttribute(name); | |||||
return; | |||||
} | |||||
// Validate our current state | |||||
if (!isValidInternal()) | |||||
throw new IllegalStateException(sm.getString( | |||||
"sessionImpl.setAttribute.ise", getIdInternal())); | |||||
attributes.put(name, value); | |||||
} | |||||
@Override | |||||
public void removeAttribute(String name) { | |||||
removeAttributeInternal(name); | |||||
} | |||||
@Override | |||||
public void invalidate() { | |||||
if (!isValidInternal()) | |||||
throw new IllegalStateException | |||||
(sm.getString("sessionImpl.invalidate.ise")); | |||||
// Cause this session to expire | |||||
expire(); | |||||
} | |||||
// ------------------------------ InternalSession | |||||
/** | |||||
* The session identifier of this Session. | |||||
*/ | |||||
protected String id = null; | |||||
/** | |||||
* Flag indicating whether this session is valid or not. | |||||
*/ | |||||
protected volatile boolean isValid = false; | |||||
/** | |||||
* We are currently processing a session expiration, so bypass | |||||
* certain IllegalStateException tests. NOTE: This value is not | |||||
* included in the serialized version of this object. | |||||
*/ | |||||
protected transient volatile boolean expiring = false; | |||||
/** | |||||
* The Manager with which this Session is associated. | |||||
*/ | |||||
protected transient InternalSessionManager manager = null; | |||||
/** | |||||
* Type array. | |||||
*/ | |||||
protected static final String EMPTY_ARRAY[] = new String[0]; | |||||
/** | |||||
* The time this session was created, in milliseconds since midnight, | |||||
* January 1, 1970 GMT. | |||||
*/ | |||||
protected long creationTime = 0L; | |||||
/** | |||||
* The current accessed time for this session. | |||||
*/ | |||||
protected volatile long thisAccessedTime = creationTime; | |||||
/** | |||||
* The default maximum inactive interval for Sessions created by | |||||
* this Manager. | |||||
*/ | |||||
protected int maxInactiveInterval = 30 * 60; | |||||
/** | |||||
* The facade associated with this session. NOTE: This value is not | |||||
* included in the serialized version of this object. | |||||
*/ | |||||
protected transient StandardSessionFacade facade = null; | |||||
/** | |||||
* The access count for this session. | |||||
*/ | |||||
protected transient AtomicInteger accessCount = null; | |||||
public StandardSession(InternalSessionManager manager) { | |||||
this.manager = manager; | |||||
this.accessCount = new AtomicInteger(); | |||||
} | |||||
@Override | |||||
public WxSession getSession() { | |||||
if (facade == null){ | |||||
facade = new StandardSessionFacade(this); | |||||
} | |||||
return (facade); | |||||
} | |||||
/** | |||||
* Return the <code>isValid</code> flag for this session without any expiration | |||||
* check. | |||||
*/ | |||||
protected boolean isValidInternal() { | |||||
return this.isValid; | |||||
} | |||||
/** | |||||
* Set the <code>isValid</code> flag for this session. | |||||
* | |||||
* @param isValid The new value for the <code>isValid</code> flag | |||||
*/ | |||||
@Override | |||||
public void setValid(boolean isValid) { | |||||
this.isValid = isValid; | |||||
} | |||||
@Override | |||||
public boolean isValid() { | |||||
if (!this.isValid) { | |||||
return false; | |||||
} | |||||
if (this.expiring) { | |||||
return true; | |||||
} | |||||
if (accessCount.get() > 0) { | |||||
return true; | |||||
} | |||||
if (maxInactiveInterval > 0) { | |||||
long timeNow = System.currentTimeMillis(); | |||||
int timeIdle; | |||||
timeIdle = (int) ((timeNow - thisAccessedTime) / 1000L); | |||||
if (timeIdle >= maxInactiveInterval) { | |||||
expire(); | |||||
} | |||||
} | |||||
return this.isValid; | |||||
} | |||||
@Override | |||||
public String getIdInternal() { | |||||
return (this.id); | |||||
} | |||||
protected void removeAttributeInternal(String name) { | |||||
// Avoid NPE | |||||
if (name == null) return; | |||||
// Remove this attribute from our collection | |||||
attributes.remove(name); | |||||
} | |||||
@Override | |||||
public void expire() { | |||||
// Check to see if session has already been invalidated. | |||||
// Do not check expiring at this point as expire should not return until | |||||
// isValid is false | |||||
if (!isValid) | |||||
return; | |||||
synchronized (this) { | |||||
// Check again, now we are inside the sync so this code only runs once | |||||
// Double check locking - isValid needs to be volatile | |||||
// The check of expiring is to ensure that an infinite loop is not | |||||
// entered as per bug 56339 | |||||
if (expiring || !isValid) | |||||
return; | |||||
if (manager == null) | |||||
return; | |||||
// Mark this session as "being expired" | |||||
expiring = true; | |||||
accessCount.set(0); | |||||
// Remove this session from our manager's active sessions | |||||
manager.remove(this, true); | |||||
// We have completed expire of this session | |||||
setValid(false); | |||||
expiring = false; | |||||
// Unbind any objects associated with this session | |||||
String keys[] = keys(); | |||||
for (int i = 0; i < keys.length; i++) { | |||||
removeAttributeInternal(keys[i]); | |||||
} | |||||
} | |||||
} | |||||
@Override | |||||
public void access() { | |||||
this.thisAccessedTime = System.currentTimeMillis(); | |||||
accessCount.incrementAndGet(); | |||||
} | |||||
@Override | |||||
public void endAccess() { | |||||
this.thisAccessedTime = System.currentTimeMillis(); | |||||
accessCount.decrementAndGet(); | |||||
} | |||||
@Override | |||||
public void setCreationTime(long time) { | |||||
this.creationTime = time; | |||||
this.thisAccessedTime = time; | |||||
} | |||||
@Override | |||||
public void setMaxInactiveInterval(int interval) { | |||||
this.maxInactiveInterval = interval; | |||||
} | |||||
@Override | |||||
public void setId(String id) { | |||||
if ((this.id != null) && (manager != null)) | |||||
manager.remove(this); | |||||
this.id = id; | |||||
if (manager != null) | |||||
manager.add(this); | |||||
} | |||||
/** | |||||
* Return the names of all currently defined session attributes | |||||
* as an array of Strings. If there are no defined attributes, a | |||||
* zero-length array is returned. | |||||
*/ | |||||
protected String[] keys() { | |||||
return attributes.keySet().toArray(EMPTY_ARRAY); | |||||
} | |||||
@Override | |||||
public boolean equals(Object o) { | |||||
if (this == o) return true; | |||||
if (!(o instanceof StandardSession)) return false; | |||||
StandardSession session = (StandardSession) o; | |||||
if (creationTime != session.creationTime) return false; | |||||
if (expiring != session.expiring) return false; | |||||
if (isValid != session.isValid) return false; | |||||
if (maxInactiveInterval != session.maxInactiveInterval) return false; | |||||
if (thisAccessedTime != session.thisAccessedTime) return false; | |||||
if (!accessCount.equals(session.accessCount)) return false; | |||||
if (!attributes.equals(session.attributes)) return false; | |||||
if (!facade.equals(session.facade)) return false; | |||||
if (!id.equals(session.id)) return false; | |||||
if (!manager.equals(session.manager)) return false; | |||||
return true; | |||||
} | |||||
@Override | |||||
public int hashCode() { | |||||
int result = attributes.hashCode(); | |||||
result = 31 * result + id.hashCode(); | |||||
result = 31 * result + (isValid ? 1 : 0); | |||||
result = 31 * result + (expiring ? 1 : 0); | |||||
result = 31 * result + manager.hashCode(); | |||||
result = 31 * result + (int) (creationTime ^ (creationTime >>> 32)); | |||||
result = 31 * result + (int) (thisAccessedTime ^ (thisAccessedTime >>> 32)); | |||||
result = 31 * result + maxInactiveInterval; | |||||
result = 31 * result + facade.hashCode(); | |||||
result = 31 * result + accessCount.hashCode(); | |||||
return result; | |||||
} | |||||
} |
@@ -0,0 +1,45 @@ | |||||
package me.chanjar.weixin.common.session; | |||||
import java.util.Enumeration; | |||||
public class StandardSessionFacade implements WxSession { | |||||
/** | |||||
* Wrapped session object. | |||||
*/ | |||||
private WxSession session = null; | |||||
public StandardSessionFacade(StandardSession session) { | |||||
this.session = session; | |||||
} | |||||
public InternalSession getInternalSession() { | |||||
return (InternalSession) session; | |||||
} | |||||
@Override | |||||
public Object getAttribute(String name) { | |||||
return session.getAttribute(name); | |||||
} | |||||
@Override | |||||
public Enumeration<String> getAttributeNames() { | |||||
return session.getAttributeNames(); | |||||
} | |||||
@Override | |||||
public void setAttribute(String name, Object value) { | |||||
session.setAttribute(name, value); | |||||
} | |||||
@Override | |||||
public void removeAttribute(String name) { | |||||
session.removeAttribute(name); | |||||
} | |||||
@Override | |||||
public void invalidate() { | |||||
session.invalidate(); | |||||
} | |||||
} |
@@ -0,0 +1,321 @@ | |||||
package me.chanjar.weixin.common.session; | |||||
import me.chanjar.weixin.common.util.res.StringManager; | |||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import java.util.Map; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import java.util.concurrent.atomic.AtomicBoolean; | |||||
/** | |||||
* 基于内存的session manager | |||||
*/ | |||||
public class StandardSessionManager implements WxSessionManager, InternalSessionManager { | |||||
protected final Logger log = LoggerFactory.getLogger(StandardSessionManager.class); | |||||
protected static final StringManager sm = | |||||
StringManager.getManager(Constants.Package); | |||||
/** | |||||
* The set of currently active Sessions for this Manager, keyed by | |||||
* session identifier. | |||||
*/ | |||||
protected Map<String, InternalSession> sessions = new ConcurrentHashMap<String, InternalSession>(); | |||||
@Override | |||||
public WxSession getSession(String sessionId) { | |||||
return getSession(sessionId, true); | |||||
} | |||||
@Override | |||||
public WxSession getSession(String sessionId, boolean create) { | |||||
if (sessionId == null) { | |||||
throw new IllegalStateException | |||||
(sm.getString("sessionManagerImpl.getSession.ise")); | |||||
} | |||||
InternalSession session = findSession(sessionId); | |||||
if ((session != null) && !session.isValid()) { | |||||
session = null; | |||||
} | |||||
if (session != null) { | |||||
session.access(); | |||||
return session.getSession(); | |||||
} | |||||
// Create a new session if requested and the response is not committed | |||||
if (!create) { | |||||
return (null); | |||||
} | |||||
session = createSession(sessionId); | |||||
if (session == null) { | |||||
return null; | |||||
} | |||||
session.access(); | |||||
return session.getSession(); | |||||
} | |||||
// -------------------------------------- InternalSessionManager | |||||
/** | |||||
* The descriptive name of this Manager implementation (for logging). | |||||
*/ | |||||
private static final String name = "SessionManagerImpl"; | |||||
/** | |||||
* The maximum number of active Sessions allowed, or -1 for no limit. | |||||
*/ | |||||
protected int maxActiveSessions = -1; | |||||
/** | |||||
* Number of session creations that failed due to maxActiveSessions. | |||||
*/ | |||||
protected int rejectedSessions = 0; | |||||
/** | |||||
* The default maximum inactive interval for Sessions created by | |||||
* this Manager. | |||||
*/ | |||||
protected int maxInactiveInterval = 30 * 60; | |||||
// Number of sessions created by this manager | |||||
protected long sessionCounter=0; | |||||
protected volatile int maxActive=0; | |||||
private final Object maxActiveUpdateLock = new Object(); | |||||
/** | |||||
* Processing time during session expiration. | |||||
*/ | |||||
protected long processingTime = 0; | |||||
/** | |||||
* Iteration count for background processing. | |||||
*/ | |||||
private int count = 0; | |||||
/** | |||||
* Frequency of the session expiration, and related manager operations. | |||||
* Manager operations will be done once for the specified amount of | |||||
* backgrondProcess calls (ie, the lower the amount, the most often the | |||||
* checks will occur). | |||||
*/ | |||||
protected int processExpiresFrequency = 6; | |||||
/** | |||||
* background processor delay in seconds | |||||
*/ | |||||
protected int backgroundProcessorDelay = 10; | |||||
/** | |||||
* 后台清理线程是否已经开启 | |||||
*/ | |||||
private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false); | |||||
@Override | |||||
public void remove(InternalSession session) { | |||||
remove(session, false); | |||||
} | |||||
@Override | |||||
public void remove(InternalSession session, boolean update) { | |||||
if (session.getIdInternal() != null) { | |||||
sessions.remove(session.getIdInternal()); | |||||
} | |||||
} | |||||
@Override | |||||
public InternalSession findSession(String id) { | |||||
if (id == null) | |||||
return (null); | |||||
return sessions.get(id); | |||||
} | |||||
@Override | |||||
public InternalSession createSession(String sessionId) { | |||||
if (sessionId == null) { | |||||
throw new IllegalStateException | |||||
(sm.getString("sessionManagerImpl.createSession.ise")); | |||||
} | |||||
if ((maxActiveSessions >= 0) && | |||||
(getActiveSessions() >= maxActiveSessions)) { | |||||
rejectedSessions++; | |||||
throw new TooManyActiveSessionsException( | |||||
sm.getString("sessionManagerImpl.createSession.tmase"), | |||||
maxActiveSessions); | |||||
} | |||||
// Recycle or create a Session instance | |||||
InternalSession session = createEmptySession(); | |||||
// Initialize the properties of the new session and return it | |||||
session.setValid(true); | |||||
session.setCreationTime(System.currentTimeMillis()); | |||||
session.setMaxInactiveInterval(this.maxInactiveInterval); | |||||
String id = sessionId; | |||||
session.setId(id); | |||||
sessionCounter++; | |||||
return (session); | |||||
} | |||||
@Override | |||||
public int getActiveSessions() { | |||||
return sessions.size(); | |||||
} | |||||
@Override | |||||
public InternalSession createEmptySession() { | |||||
return (getNewSession()); | |||||
} | |||||
/** | |||||
* Get new session class to be used in the doLoad() method. | |||||
*/ | |||||
protected InternalSession getNewSession() { | |||||
return new StandardSession(this); | |||||
} | |||||
@Override | |||||
public void add(InternalSession session) { | |||||
// 当第一次有session创建的时候,开启session清理线程 | |||||
if (!backgroundProcessStarted.getAndSet(true)) { | |||||
Thread t = new Thread(new Runnable() { | |||||
@Override | |||||
public void run() { | |||||
while (true) { | |||||
try { | |||||
// 每秒清理一次 | |||||
Thread.sleep(backgroundProcessorDelay * 1000l); | |||||
backgroundProcess(); | |||||
} catch (InterruptedException e) { | |||||
log.error("SessionManagerImpl.backgroundProcess error", e); | |||||
} | |||||
} | |||||
} | |||||
}); | |||||
t.setDaemon(true); | |||||
t.start(); | |||||
} | |||||
sessions.put(session.getIdInternal(), session); | |||||
int size = getActiveSessions(); | |||||
if( size > maxActive ) { | |||||
synchronized(maxActiveUpdateLock) { | |||||
if( size > maxActive ) { | |||||
maxActive = size; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* Return the set of active Sessions associated with this Manager. | |||||
* If this Manager has no active Sessions, a zero-length array is returned. | |||||
*/ | |||||
@Override | |||||
public InternalSession[] findSessions() { | |||||
return sessions.values().toArray(new InternalSession[0]); | |||||
} | |||||
@Override | |||||
public void backgroundProcess() { | |||||
count = (count + 1) % processExpiresFrequency; | |||||
if (count == 0) | |||||
processExpires(); | |||||
} | |||||
/** | |||||
* Invalidate all sessions that have expired. | |||||
*/ | |||||
public void processExpires() { | |||||
long timeNow = System.currentTimeMillis(); | |||||
InternalSession sessions[] = findSessions(); | |||||
int expireHere = 0 ; | |||||
if(log.isDebugEnabled()) | |||||
log.debug("Start expire sessions {} at {} sessioncount {}", getName(), timeNow, sessions.length); | |||||
for (int i = 0; i < sessions.length; i++) { | |||||
if (sessions[i]!=null && !sessions[i].isValid()) { | |||||
expireHere++; | |||||
} | |||||
} | |||||
long timeEnd = System.currentTimeMillis(); | |||||
if(log.isDebugEnabled()) | |||||
log.debug("End expire sessions {} processingTime {} expired sessions: {}", getName(), timeEnd - timeNow, expireHere); | |||||
processingTime += ( timeEnd - timeNow ); | |||||
} | |||||
@Override | |||||
public void setMaxInactiveInterval(int interval) { | |||||
this.maxInactiveInterval = interval; | |||||
} | |||||
/** | |||||
* Set the manager checks frequency. | |||||
* | |||||
* @param processExpiresFrequency the new manager checks frequency | |||||
*/ | |||||
@Override | |||||
public void setProcessExpiresFrequency(int processExpiresFrequency) { | |||||
if (processExpiresFrequency <= 0) { | |||||
return; | |||||
} | |||||
this.processExpiresFrequency = processExpiresFrequency; | |||||
} | |||||
@Override | |||||
public void setBackgroundProcessorDelay(int backgroundProcessorDelay) { | |||||
this.backgroundProcessorDelay = backgroundProcessorDelay; | |||||
} | |||||
/** | |||||
* Return the descriptive short name of this Manager implementation. | |||||
*/ | |||||
public String getName() { | |||||
return (name); | |||||
} | |||||
/** | |||||
* Set the maximum number of active Sessions allowed, or -1 for | |||||
* no limit. | |||||
* | |||||
* @param max The new maximum number of sessions | |||||
*/ | |||||
@Override | |||||
public void setMaxActiveSessions(int max) { | |||||
this.maxActiveSessions = max; | |||||
} | |||||
} |
@@ -0,0 +1,57 @@ | |||||
/* | |||||
* Licensed to the Apache Software Foundation (ASF) under one or more | |||||
* contributor license agreements. See the NOTICE file distributed with | |||||
* this work for additional information regarding copyright ownership. | |||||
* The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
* (the "License"); you may not use this file except in compliance with | |||||
* the License. You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package me.chanjar.weixin.common.session; | |||||
/** | |||||
* An exception that indicates the maximum number of active sessions has been | |||||
* reached and the server is refusing to create any new sessions. | |||||
*/ | |||||
public class TooManyActiveSessionsException | |||||
extends IllegalStateException | |||||
{ | |||||
private static final long serialVersionUID = 1L; | |||||
/** | |||||
* The maximum number of active sessions the server will tolerate. | |||||
*/ | |||||
private final int maxActiveSessions; | |||||
/** | |||||
* Creates a new TooManyActiveSessionsException. | |||||
* | |||||
* @param message A description for the exception. | |||||
* @param maxActive The maximum number of active sessions allowed by the | |||||
* session manager. | |||||
*/ | |||||
public TooManyActiveSessionsException(String message, | |||||
int maxActive) | |||||
{ | |||||
super(message); | |||||
maxActiveSessions = maxActive; | |||||
} | |||||
/** | |||||
* Gets the maximum number of sessions allowed by the session manager. | |||||
* | |||||
* @return The maximum number of sessions allowed by the session manager. | |||||
*/ | |||||
public int getMaxActiveSessions() | |||||
{ | |||||
return maxActiveSessions; | |||||
} | |||||
} |
@@ -0,0 +1,17 @@ | |||||
package me.chanjar.weixin.common.session; | |||||
import java.util.Enumeration; | |||||
public interface WxSession { | |||||
public Object getAttribute(String name); | |||||
public Enumeration<String> getAttributeNames(); | |||||
public void setAttribute(String name, Object value); | |||||
public void removeAttribute(String name); | |||||
public void invalidate(); | |||||
} |
@@ -0,0 +1,21 @@ | |||||
package me.chanjar.weixin.common.session; | |||||
public interface WxSessionManager { | |||||
/** | |||||
* 获取某个sessionId对应的session,如果sessionId没有对应的session,则新建一个并返回。 | |||||
* @param sessionId | |||||
* @return | |||||
*/ | |||||
public WxSession getSession(String sessionId); | |||||
/** | |||||
* 获取某个sessionId对应的session,如果sessionId没有对应的session,若create为true则新建一个,否则返回null。 | |||||
* @param sessionId | |||||
* @param create | |||||
* @return | |||||
*/ | |||||
public WxSession getSession(String sessionId, boolean create); | |||||
} |
@@ -0,0 +1,27 @@ | |||||
package me.chanjar.weixin.common.util; | |||||
/** | |||||
* <pre> | |||||
* 消息重复检查器 | |||||
* 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次 | |||||
* </pre> | |||||
*/ | |||||
public interface WxMessageDuplicateChecker { | |||||
/** | |||||
* <h2>公众号的排重方式</h2> | |||||
* | |||||
* <p>普通消息:关于重试的消息排重,推荐使用msgid排重。<a href="http://mp.weixin.qq.com/wiki/10/79502792eef98d6e0c6e1739da387346.html">文档参考</a>。</p> | |||||
* <p>事件消息:关于重试的消息排重,推荐使用FromUserName + CreateTime 排重。<a href="http://mp.weixin.qq.com/wiki/2/5baf56ce4947d35003b86a9805634b1e.html">文档参考</a></p> | |||||
* | |||||
* <h2>企业号的排重方式</h2> | |||||
* | |||||
* 官方文档完全没有写,参照公众号的方式排重。 | |||||
* | |||||
* <p>或者可以采取更简单的方式,如果有MsgId就用MsgId排重,如果没有就用FromUserName+CreateTime排重</p> | |||||
* @param messageId messageId需要根据上面讲的方式构造 | |||||
* @return 如果是重复消息,返回true,否则返回false | |||||
*/ | |||||
public boolean isDuplicate(String messageId); | |||||
} |
@@ -0,0 +1,98 @@ | |||||
package me.chanjar.weixin.common.util; | |||||
import java.util.Map; | |||||
import java.util.concurrent.ConcurrentHashMap; | |||||
import java.util.concurrent.atomic.AtomicBoolean; | |||||
/** | |||||
* <pre> | |||||
* 默认消息重复检查器 | |||||
* 将每个消息id保存在内存里,每隔5秒清理已经过期的消息id,每个消息id的过期时间是15秒 | |||||
* </pre> | |||||
*/ | |||||
public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChecker { | |||||
/** | |||||
* 一个消息ID在内存的过期时间:15秒 | |||||
*/ | |||||
private final Long timeToLive; | |||||
/** | |||||
* 每隔多少周期检查消息ID是否过期:5秒 | |||||
*/ | |||||
private final Long clearPeriod; | |||||
/** | |||||
* 消息id->消息时间戳的map | |||||
*/ | |||||
private final ConcurrentHashMap<String, Long> msgId2Timestamp = new ConcurrentHashMap<String, Long>(); | |||||
/** | |||||
* 后台清理线程是否已经开启 | |||||
*/ | |||||
private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false); | |||||
/** | |||||
* WxMsgIdInMemoryDuplicateChecker构造函数 | |||||
* <pre> | |||||
* 一个消息ID在内存的过期时间:15秒 | |||||
* 每隔多少周期检查消息ID是否过期:5秒 | |||||
* </pre> | |||||
*/ | |||||
public WxMessageInMemoryDuplicateChecker() { | |||||
this.timeToLive = 15 * 1000l; | |||||
this.clearPeriod = 5 * 1000l; | |||||
} | |||||
/** | |||||
* WxMsgIdInMemoryDuplicateChecker构造函数 | |||||
* @param timeToLive 一个消息ID在内存的过期时间:毫秒 | |||||
* @param clearPeriod 每隔多少周期检查消息ID是否过期:毫秒 | |||||
*/ | |||||
public WxMessageInMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) { | |||||
this.timeToLive = timeToLive; | |||||
this.clearPeriod = clearPeriod; | |||||
} | |||||
protected void checkBackgroundProcessStarted() { | |||||
if (backgroundProcessStarted.getAndSet(true)) { | |||||
return; | |||||
} | |||||
Thread t = new Thread(new Runnable() { | |||||
@Override | |||||
public void run() { | |||||
try { | |||||
while (true) { | |||||
Thread.sleep(clearPeriod); | |||||
Long now = System.currentTimeMillis(); | |||||
for (Map.Entry<String, Long> entry : msgId2Timestamp.entrySet()) { | |||||
if (now - entry.getValue() > timeToLive) { | |||||
msgId2Timestamp.entrySet().remove(entry); | |||||
} | |||||
} | |||||
} | |||||
} catch (InterruptedException e) { | |||||
e.printStackTrace(); | |||||
} | |||||
} | |||||
}); | |||||
t.setDaemon(true); | |||||
t.start(); | |||||
} | |||||
@Override | |||||
public boolean isDuplicate(String messageId) { | |||||
if (messageId == null) { | |||||
return false; | |||||
} | |||||
checkBackgroundProcessStarted(); | |||||
Long timestamp = msgId2Timestamp.putIfAbsent(messageId, System.currentTimeMillis()); | |||||
if (timestamp == null) { | |||||
// 第一次接收到这个消息 | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
} |
@@ -10,27 +10,49 @@ import java.util.Arrays; | |||||
public class SHA1 { | public class SHA1 { | ||||
/** | /** | ||||
* 生成SHA1签名 | |||||
* 串接arr参数,生成sha1 digest | |||||
* | |||||
* @param arr | * @param arr | ||||
* @return | * @return | ||||
*/ | */ | ||||
public static String gen(String... arr) throws NoSuchAlgorithmException { | public static String gen(String... arr) throws NoSuchAlgorithmException { | ||||
Arrays.sort(arr); | Arrays.sort(arr); | ||||
StringBuilder sb = new StringBuilder(); | StringBuilder sb = new StringBuilder(); | ||||
for(String a : arr) { | |||||
for (String a : arr) { | |||||
sb.append(a); | sb.append(a); | ||||
} | } | ||||
return genStr(sb.toString()); | |||||
} | |||||
/** | |||||
* 用&串接arr参数,生成sha1 digest | |||||
* | |||||
* @param arr | |||||
* @return | |||||
*/ | |||||
public static String genWithAmple(String... arr) throws NoSuchAlgorithmException { | |||||
Arrays.sort(arr); | |||||
StringBuilder sb = new StringBuilder(); | |||||
for (int i = 0; i < arr.length; i++) { | |||||
String a = arr[i]; | |||||
sb.append(a); | |||||
if (i != arr.length - 1) { | |||||
sb.append('&'); | |||||
} | |||||
} | |||||
return genStr(sb.toString()); | |||||
} | |||||
public static String genStr(String str) throws NoSuchAlgorithmException { | |||||
MessageDigest sha1 = MessageDigest.getInstance("SHA1"); | MessageDigest sha1 = MessageDigest.getInstance("SHA1"); | ||||
sha1.update(sb.toString().getBytes()); | |||||
sha1.update(str.getBytes()); | |||||
byte[] output = sha1.digest(); | byte[] output = sha1.digest(); | ||||
return bytesToHex(output); | return bytesToHex(output); | ||||
} | } | ||||
protected static String bytesToHex(byte[] b) { | protected static String bytesToHex(byte[] b) { | ||||
char hexDigit[] = {'0', '1', '2', '3', '4', '5', '6', '7', | |||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; | |||||
char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7', | |||||
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; | |||||
StringBuffer buf = new StringBuffer(); | StringBuffer buf = new StringBuffer(); | ||||
for (int j = 0; j < b.length; j++) { | for (int j = 0; j < b.length; j++) { | ||||
buf.append(hexDigit[(b[j] >> 4) & 0x0f]); | buf.append(hexDigit[(b[j] >> 4) & 0x0f]); | ||||
@@ -0,0 +1,251 @@ | |||||
/* | |||||
* Licensed to the Apache Software Foundation (ASF) under one or more | |||||
* contributor license agreements. See the NOTICE file distributed with | |||||
* this work for additional information regarding copyright ownership. | |||||
* The ASF licenses this file to You under the Apache License, Version 2.0 | |||||
* (the "License"); you may not use this file except in compliance with | |||||
* the License. You may obtain a copy of the License at | |||||
* | |||||
* http://www.apache.org/licenses/LICENSE-2.0 | |||||
* | |||||
* Unless required by applicable law or agreed to in writing, software | |||||
* distributed under the License is distributed on an "AS IS" BASIS, | |||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||||
* See the License for the specific language governing permissions and | |||||
* limitations under the License. | |||||
*/ | |||||
package me.chanjar.weixin.common.util.res; | |||||
import java.text.MessageFormat; | |||||
import java.util.Enumeration; | |||||
import java.util.Hashtable; | |||||
import java.util.LinkedHashMap; | |||||
import java.util.Locale; | |||||
import java.util.Map; | |||||
import java.util.MissingResourceException; | |||||
import java.util.ResourceBundle; | |||||
/** | |||||
* An internationalization / localization helper class which reduces | |||||
* the bother of handling ResourceBundles and takes care of the | |||||
* common cases of message formating which otherwise require the | |||||
* creation of Object arrays and such. | |||||
* | |||||
* <p>The StringManager operates on a package basis. One StringManager | |||||
* per package can be created and accessed via the getManager method | |||||
* call. | |||||
* | |||||
* <p>The StringManager will look for a ResourceBundle named by | |||||
* the package name given plus the suffix of "LocalStrings". In | |||||
* practice, this means that the localized information will be contained | |||||
* in a LocalStrings.properties file located in the package | |||||
* directory of the classpath. | |||||
* | |||||
* <p>Please see the documentation for java.util.ResourceBundle for | |||||
* more information. | |||||
* | |||||
* @author James Duncan Davidson [duncan@eng.sun.com] | |||||
* @author James Todd [gonzo@eng.sun.com] | |||||
* @author Mel Martinez [mmartinez@g1440.com] | |||||
* @see java.util.ResourceBundle | |||||
*/ | |||||
public class StringManager { | |||||
private static int LOCALE_CACHE_SIZE = 10; | |||||
/** | |||||
* The ResourceBundle for this StringManager. | |||||
*/ | |||||
private final ResourceBundle bundle; | |||||
private final Locale locale; | |||||
/** | |||||
* Creates a new StringManager for a given package. This is a | |||||
* private method and all access to it is arbitrated by the | |||||
* static getManager method call so that only one StringManager | |||||
* per package will be created. | |||||
* | |||||
* @param packageName Name of package to create StringManager for. | |||||
*/ | |||||
private StringManager(String packageName, Locale locale) { | |||||
String bundleName = packageName + ".LocalStrings"; | |||||
ResourceBundle bnd = null; | |||||
try { | |||||
bnd = ResourceBundle.getBundle(bundleName, locale); | |||||
} catch( MissingResourceException ex ) { | |||||
// Try from the current loader (that's the case for trusted apps) | |||||
// Should only be required if using a TC5 style classloader structure | |||||
// where common != shared != server | |||||
ClassLoader cl = Thread.currentThread().getContextClassLoader(); | |||||
if( cl != null ) { | |||||
try { | |||||
bnd = ResourceBundle.getBundle(bundleName, locale, cl); | |||||
} catch(MissingResourceException ex2) { | |||||
// Ignore | |||||
} | |||||
} | |||||
} | |||||
bundle = bnd; | |||||
// Get the actual locale, which may be different from the requested one | |||||
if (bundle != null) { | |||||
Locale bundleLocale = bundle.getLocale(); | |||||
if (bundleLocale.equals(Locale.ROOT)) { | |||||
this.locale = Locale.ENGLISH; | |||||
} else { | |||||
this.locale = bundleLocale; | |||||
} | |||||
} else { | |||||
this.locale = null; | |||||
} | |||||
} | |||||
/** | |||||
Get a string from the underlying resource bundle or return | |||||
null if the String is not found. | |||||
@param key to desired resource String | |||||
@return resource String matching <i>key</i> from underlying | |||||
bundle or null if not found. | |||||
@throws IllegalArgumentException if <i>key</i> is null. | |||||
*/ | |||||
public String getString(String key) { | |||||
if(key == null){ | |||||
String msg = "key may not have a null value"; | |||||
throw new IllegalArgumentException(msg); | |||||
} | |||||
String str = null; | |||||
try { | |||||
// Avoid NPE if bundle is null and treat it like an MRE | |||||
if (bundle != null) { | |||||
str = bundle.getString(key); | |||||
} | |||||
} catch(MissingResourceException mre) { | |||||
//bad: shouldn't mask an exception the following way: | |||||
// str = "[cannot find message associated with key '" + key + | |||||
// "' due to " + mre + "]"; | |||||
// because it hides the fact that the String was missing | |||||
// from the calling code. | |||||
//good: could just throw the exception (or wrap it in another) | |||||
// but that would probably cause much havoc on existing | |||||
// code. | |||||
//better: consistent with container pattern to | |||||
// simply return null. Calling code can then do | |||||
// a null check. | |||||
str = null; | |||||
} | |||||
return str; | |||||
} | |||||
/** | |||||
* Get a string from the underlying resource bundle and format | |||||
* it with the given set of arguments. | |||||
* | |||||
* @param key | |||||
* @param args | |||||
*/ | |||||
public String getString(final String key, final Object... args) { | |||||
String value = getString(key); | |||||
if (value == null) { | |||||
value = key; | |||||
} | |||||
MessageFormat mf = new MessageFormat(value); | |||||
mf.setLocale(locale); | |||||
return mf.format(args, new StringBuffer(), null).toString(); | |||||
} | |||||
/** | |||||
* Identify the Locale this StringManager is associated with | |||||
*/ | |||||
public Locale getLocale() { | |||||
return locale; | |||||
} | |||||
// -------------------------------------------------------------- | |||||
// STATIC SUPPORT METHODS | |||||
// -------------------------------------------------------------- | |||||
private static final Map<String, Map<Locale,StringManager>> managers = | |||||
new Hashtable<String, Map<Locale,StringManager>>(); | |||||
/** | |||||
* Get the StringManager for a particular package. If a manager for | |||||
* a package already exists, it will be reused, else a new | |||||
* StringManager will be created and returned. | |||||
* | |||||
* @param packageName The package name | |||||
*/ | |||||
public static final synchronized StringManager getManager( | |||||
String packageName) { | |||||
return getManager(packageName, Locale.getDefault()); | |||||
} | |||||
/** | |||||
* Get the StringManager for a particular package and Locale. If a manager | |||||
* for a package/Locale combination already exists, it will be reused, else | |||||
* a new StringManager will be created and returned. | |||||
* | |||||
* @param packageName The package name | |||||
* @param locale The Locale | |||||
*/ | |||||
public static final synchronized StringManager getManager( | |||||
String packageName, Locale locale) { | |||||
Map<Locale,StringManager> map = managers.get(packageName); | |||||
if (map == null) { | |||||
/* | |||||
* Don't want the HashMap to be expanded beyond LOCALE_CACHE_SIZE. | |||||
* Expansion occurs when size() exceeds capacity. Therefore keep | |||||
* size at or below capacity. | |||||
* removeEldestEntry() executes after insertion therefore the test | |||||
* for removal needs to use one less than the maximum desired size | |||||
* | |||||
*/ | |||||
map = new LinkedHashMap<Locale,StringManager>(LOCALE_CACHE_SIZE, 1, true) { | |||||
private static final long serialVersionUID = 1L; | |||||
@Override | |||||
protected boolean removeEldestEntry( | |||||
Map.Entry<Locale,StringManager> eldest) { | |||||
if (size() > (LOCALE_CACHE_SIZE - 1)) { | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
}; | |||||
managers.put(packageName, map); | |||||
} | |||||
StringManager mgr = map.get(locale); | |||||
if (mgr == null) { | |||||
mgr = new StringManager(packageName, locale); | |||||
map.put(locale, mgr); | |||||
} | |||||
return mgr; | |||||
} | |||||
/** | |||||
* Retrieve the StringManager for a list of Locales. The first StringManager | |||||
* found will be returned. | |||||
* | |||||
* @param requestedLocales the list of Locales | |||||
* | |||||
* @return the found StringManager or the default StringManager | |||||
*/ | |||||
public static StringManager getManager(String packageName, | |||||
Enumeration<Locale> requestedLocales) { | |||||
while (requestedLocales.hasMoreElements()) { | |||||
Locale locale = requestedLocales.nextElement(); | |||||
StringManager result = getManager(packageName, locale); | |||||
if (result.getLocale().equals(locale)) { | |||||
return result; | |||||
} | |||||
} | |||||
// Return the default | |||||
return getManager(packageName); | |||||
} | |||||
} |
@@ -1,24 +0,0 @@ | |||||
package me.chanjar.weixin.common.util.xml; | |||||
import javax.xml.bind.annotation.adapters.XmlAdapter; | |||||
/** | |||||
* | |||||
* http://stackoverflow.com/questions/14193944/jaxb-marshalling-unmarshalling-with-cdata | |||||
* | |||||
* @author Daniel Qian | |||||
* | |||||
*/ | |||||
public class AdapterCDATA extends XmlAdapter<String, String> { | |||||
@Override | |||||
public String marshal(String arg0) throws Exception { | |||||
return "<![CDATA[" + arg0 + "]]>"; | |||||
} | |||||
@Override | |||||
public String unmarshal(String arg0) throws Exception { | |||||
return arg0; | |||||
} | |||||
} |
@@ -1,21 +0,0 @@ | |||||
package me.chanjar.weixin.common.util.xml; | |||||
import javax.xml.bind.annotation.adapters.XmlAdapter; | |||||
/** | |||||
* @author Daniel Qian | |||||
*/ | |||||
public class MediaIdMarshaller extends XmlAdapter<String, String> { | |||||
@Override | |||||
public String marshal(String arg0) throws Exception { | |||||
return "<MediaId><![CDATA[" + arg0 + "]]></MediaId>"; | |||||
} | |||||
@Override | |||||
public String unmarshal(String arg0) throws Exception { | |||||
// do nothing | |||||
return arg0; | |||||
} | |||||
} |
@@ -0,0 +1,12 @@ | |||||
package me.chanjar.weixin.common.util.xml; | |||||
import com.thoughtworks.xstream.converters.basic.StringConverter; | |||||
public class XStreamCDataConverter extends StringConverter { | |||||
@Override | |||||
public String toString(Object obj) { | |||||
return "<![CDATA[" + super.toString(obj) + "]]>"; | |||||
} | |||||
} |
@@ -0,0 +1,50 @@ | |||||
package me.chanjar.weixin.common.util.xml; | |||||
import com.thoughtworks.xstream.XStream; | |||||
import com.thoughtworks.xstream.converters.basic.DoubleConverter; | |||||
import com.thoughtworks.xstream.converters.basic.FloatConverter; | |||||
import com.thoughtworks.xstream.converters.basic.IntConverter; | |||||
import com.thoughtworks.xstream.core.util.QuickWriter; | |||||
import com.thoughtworks.xstream.io.HierarchicalStreamWriter; | |||||
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; | |||||
import com.thoughtworks.xstream.io.xml.XppDriver; | |||||
import com.thoughtworks.xstream.security.NoTypePermission; | |||||
import com.thoughtworks.xstream.security.NullPermission; | |||||
import com.thoughtworks.xstream.security.PrimitiveTypePermission; | |||||
import java.io.Writer; | |||||
public class XStreamInitializer { | |||||
public static XStream getInstance() { | |||||
XStream xstream = new XStream(new XppDriver() { | |||||
@Override | |||||
public HierarchicalStreamWriter createWriter(Writer out) { | |||||
return new PrettyPrintWriter(out, getNameCoder()) { | |||||
protected String PREFIX_CDATA = "<![CDATA["; | |||||
protected String SUFFIX_CDATA = "]]>"; | |||||
protected String PREFIX_MEDIA_ID = "<MediaId>"; | |||||
protected String SUFFIX_MEDIA_ID = "</MediaId>"; | |||||
@Override | |||||
protected void writeText(QuickWriter writer, String text) { | |||||
if (text.startsWith(PREFIX_CDATA) && text.endsWith(SUFFIX_CDATA)) { | |||||
writer.write(text); | |||||
} else if (text.startsWith(PREFIX_MEDIA_ID) && text.endsWith(SUFFIX_MEDIA_ID)) { | |||||
writer.write(text); | |||||
} else { | |||||
super.writeText(writer, text); | |||||
} | |||||
} | |||||
}; | |||||
} | |||||
}); | |||||
xstream.ignoreUnknownElements(); | |||||
xstream.setMode(XStream.NO_REFERENCES); | |||||
xstream.addPermission(NullPermission.NULL); | |||||
xstream.addPermission(PrimitiveTypePermission.PRIMITIVES); | |||||
return xstream; | |||||
} | |||||
} |
@@ -0,0 +1,8 @@ | |||||
package me.chanjar.weixin.common.util.xml; | |||||
public class XStreamMediaIdConverter extends XStreamCDataConverter { | |||||
@Override | |||||
public String toString(Object obj) { | |||||
return "<MediaId>" + super.toString(obj) + "</MediaId>"; | |||||
} | |||||
} |
@@ -0,0 +1,132 @@ | |||||
package me.chanjar.weixin.common.session; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.DataProvider; | |||||
import org.testng.annotations.Test; | |||||
@Test | |||||
public class SessionTest { | |||||
@DataProvider | |||||
public Object[][] getSessionManager() { | |||||
return new Object[][] { | |||||
new Object[] { new StandardSessionManager() } | |||||
}; | |||||
} | |||||
@Test(dataProvider = "getSessionManager", expectedExceptions = IllegalStateException.class) | |||||
public void testInvalidate(WxSessionManager sessionManager) { | |||||
WxSession session = sessionManager.getSession("abc"); | |||||
session.invalidate(); | |||||
session.getAttributeNames(); | |||||
} | |||||
@Test(dataProvider = "getSessionManager") | |||||
public void testInvalidate2(InternalSessionManager sessionManager) { | |||||
Assert.assertEquals(sessionManager.getActiveSessions(), 0); | |||||
WxSession session = ((WxSessionManager) sessionManager).getSession("abc"); | |||||
Assert.assertEquals(sessionManager.getActiveSessions(), 1); | |||||
session.invalidate(); | |||||
Assert.assertEquals(sessionManager.getActiveSessions(), 0); | |||||
} | |||||
@Test(dataProvider = "getSessionManager") | |||||
public void testGetSession(WxSessionManager sessionManager) { | |||||
WxSession session1 = sessionManager.getSession("abc"); | |||||
WxSession session2 = sessionManager.getSession("abc"); | |||||
Assert.assertEquals(session1, session2); | |||||
Assert.assertTrue(session1 == session2); | |||||
WxSession abc1 = sessionManager.getSession("abc1"); | |||||
Assert.assertNotEquals(session1, abc1); | |||||
WxSession abc1b = sessionManager.getSession("abc1", false); | |||||
Assert.assertEquals(abc1, abc1b); | |||||
WxSession def = sessionManager.getSession("def", false); | |||||
Assert.assertNull(def); | |||||
} | |||||
@Test(dataProvider = "getSessionManager") | |||||
public void testInvalidateAngGet(WxSessionManager sessionManager) { | |||||
WxSession session1 = sessionManager.getSession("abc"); | |||||
session1.invalidate(); | |||||
WxSession session2 = sessionManager.getSession("abc"); | |||||
Assert.assertNotEquals(session1, session2); | |||||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||||
Assert.assertEquals(ism.getActiveSessions(), 1); | |||||
} | |||||
@Test(dataProvider = "getSessionManager") | |||||
public void testBackgroundProcess(WxSessionManager sessionManager) throws InterruptedException { | |||||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||||
ism.setMaxInactiveInterval(1); | |||||
ism.setProcessExpiresFrequency(1); | |||||
ism.setBackgroundProcessorDelay(1); | |||||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||||
InternalSession abc = ism.createSession("abc"); | |||||
abc.endAccess(); | |||||
Thread.sleep(2000l); | |||||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||||
} | |||||
@Test(dataProvider = "getSessionManager") | |||||
public void testBackgroundProcess2(WxSessionManager sessionManager) throws InterruptedException { | |||||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||||
ism.setMaxInactiveInterval(100); | |||||
ism.setProcessExpiresFrequency(1); | |||||
ism.setBackgroundProcessorDelay(1); | |||||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||||
InternalSession abc = ism.createSession("abc"); | |||||
abc.setMaxInactiveInterval(1); | |||||
abc.endAccess(); | |||||
Thread.sleep(2000l); | |||||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||||
} | |||||
@Test(dataProvider = "getSessionManager") | |||||
public void testMaxActive(WxSessionManager sessionManager) throws InterruptedException { | |||||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||||
ism.setMaxActiveSessions(2); | |||||
ism.createSession("abc"); | |||||
ism.createSession("abc"); | |||||
ism.createSession("def"); | |||||
} | |||||
@Test(dataProvider = "getSessionManager", expectedExceptions = TooManyActiveSessionsException.class) | |||||
public void testMaxActive2(WxSessionManager sessionManager) throws InterruptedException { | |||||
InternalSessionManager ism = (InternalSessionManager) sessionManager; | |||||
ism.setMaxActiveSessions(2); | |||||
ism.createSession("abc"); | |||||
ism.createSession("abc"); | |||||
ism.createSession("def"); | |||||
ism.createSession("xyz"); | |||||
} | |||||
} |
@@ -0,0 +1,35 @@ | |||||
package me.chanjar.weixin.common.util; | |||||
import org.testng.Assert; | |||||
import org.testng.annotations.Test; | |||||
@Test | |||||
public class WxMessageInMemoryDuplicateCheckerTest { | |||||
public void test() throws InterruptedException { | |||||
Long[] msgIds = new Long[] { 1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l }; | |||||
WxMessageInMemoryDuplicateChecker checker = new WxMessageInMemoryDuplicateChecker(2000l, 1000l); | |||||
// 第一次检查 | |||||
for (Long msgId : msgIds) { | |||||
boolean result = checker.isDuplicate(String.valueOf(msgId)); | |||||
Assert.assertFalse(result); | |||||
} | |||||
// 过1秒再检查 | |||||
Thread.sleep(1000l); | |||||
for (Long msgId : msgIds) { | |||||
boolean result = checker.isDuplicate(String.valueOf(msgId)); | |||||
Assert.assertTrue(result); | |||||
} | |||||
// 过1.5秒再检查 | |||||
Thread.sleep(1500l); | |||||
for (Long msgId : msgIds) { | |||||
boolean result = checker.isDuplicate(String.valueOf(msgId)); | |||||
Assert.assertFalse(result); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,16 @@ | |||||
<configuration> | |||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | |||||
<!-- encoders are assigned the type | |||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> | |||||
<encoder> | |||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | |||||
</encoder> | |||||
</appender> | |||||
<root level="info"> | |||||
<appender-ref ref="STDOUT" /> | |||||
</root> | |||||
<logger name="me.chanjar.weixin.common" level="debug" /> | |||||
</configuration> |
@@ -1,12 +1,14 @@ | |||||
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > | <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > | ||||
<suite name="Weixin-java-tool-suite" verbose="1"> | <suite name="Weixin-java-tool-suite" verbose="1"> | ||||
<test name="Bean_Test"> | |||||
<classes> | |||||
<class name="me.chanjar.weixin.common.bean.WxAccessTokenTest" /> | |||||
<class name="me.chanjar.weixin.common.bean.WxErrorTest" /> | |||||
<class name="me.chanjar.weixin.common.bean.WxMenuTest" /> | |||||
<class name="me.chanjar.weixin.common.util.crypto.WxCryptUtilTest" /> | |||||
</classes> | |||||
</test> | |||||
<test name="Bean_Test"> | |||||
<classes> | |||||
<class name="me.chanjar.weixin.common.bean.WxAccessTokenTest"/> | |||||
<class name="me.chanjar.weixin.common.bean.WxErrorTest"/> | |||||
<class name="me.chanjar.weixin.common.bean.WxMenuTest"/> | |||||
<class name="me.chanjar.weixin.common.util.crypto.WxCryptUtilTest"/> | |||||
<class name="me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateCheckerTest"/> | |||||
<class name="me.chanjar.weixin.common.session.SessionTest" /> | |||||
</classes> | |||||
</test> | |||||
</suite> | </suite> |
@@ -6,7 +6,7 @@ | |||||
<parent> | <parent> | ||||
<groupId>me.chanjar</groupId> | <groupId>me.chanjar</groupId> | ||||
<artifactId>weixin-java-parent</artifactId> | <artifactId>weixin-java-parent</artifactId> | ||||
<version>1.0.6</version> | |||||
<version>1.1.0-SNAPSHOT</version> | |||||
</parent> | </parent> | ||||
<artifactId>weixin-java-cp</artifactId> | <artifactId>weixin-java-cp</artifactId> | ||||
@@ -1,73 +0,0 @@ | |||||
<?xml version="1.0"?> | |||||
<project | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<parent> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-parent</artifactId> | |||||
<version>1.0.5-SNAPSHOT</version> | |||||
</parent> | |||||
<artifactId>weixin-java-cp</artifactId> | |||||
<name>WeiXin Java Tools - CP</name> | |||||
<description>微信企业号Java SDK</description> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-common</artifactId> | |||||
<version>${project.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.testng</groupId> | |||||
<artifactId>testng</artifactId> | |||||
<version>6.8.7</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-all</artifactId> | |||||
<version>1.9.5</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.inject</groupId> | |||||
<artifactId>guice</artifactId> | |||||
<version>3.0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-server</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-servlet</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-surefire-plugin</artifactId> | |||||
<configuration> | |||||
<suiteXmlFiles> | |||||
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> | |||||
</suiteXmlFiles> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</project> |
@@ -9,12 +9,20 @@ import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
*/ | */ | ||||
public interface WxCpConfigStorage { | public interface WxCpConfigStorage { | ||||
public String getAccessToken(); | |||||
public boolean isAccessTokenExpired(); | |||||
/** | |||||
* 强制将access token过期掉 | |||||
*/ | |||||
public void expireAccessToken(); | |||||
public void updateAccessToken(WxAccessToken accessToken); | public void updateAccessToken(WxAccessToken accessToken); | ||||
public void updateAccessToken(String accessToken, int expiresIn); | public void updateAccessToken(String accessToken, int expiresIn); | ||||
public String getAccessToken(); | |||||
public String getCorpId(); | public String getCorpId(); | ||||
public String getCorpSecret(); | public String getCorpSecret(); | ||||
@@ -25,7 +33,7 @@ public interface WxCpConfigStorage { | |||||
public String getAesKey(); | public String getAesKey(); | ||||
public int getExpiresIn(); | |||||
public long getExpiresTime(); | |||||
public String getOauth2redirectUri(); | public String getOauth2redirectUri(); | ||||
@@ -16,7 +16,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||||
protected String accessToken; | protected String accessToken; | ||||
protected String aesKey; | protected String aesKey; | ||||
protected String agentId; | protected String agentId; | ||||
protected int expiresIn; | |||||
protected long expiresTime; | |||||
protected String oauth2redirectUri; | protected String oauth2redirectUri; | ||||
@@ -25,17 +25,25 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||||
protected String http_proxy_username; | protected String http_proxy_username; | ||||
protected String http_proxy_password; | protected String http_proxy_password; | ||||
public void updateAccessToken(WxAccessToken accessToken) { | |||||
public String getAccessToken() { | |||||
return this.accessToken; | |||||
} | |||||
public boolean isAccessTokenExpired() { | |||||
return System.currentTimeMillis() > this.expiresTime; | |||||
} | |||||
public void expireAccessToken() { | |||||
this.expiresTime = 0; | |||||
} | |||||
public synchronized void updateAccessToken(WxAccessToken accessToken) { | |||||
updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | ||||
} | } | ||||
public void updateAccessToken(String accessToken, int expiresIn) { | |||||
public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { | |||||
this.accessToken = accessToken; | this.accessToken = accessToken; | ||||
this.expiresIn = expiresIn; | |||||
} | |||||
public String getAccessToken() { | |||||
return this.accessToken; | |||||
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||||
} | } | ||||
public String getCorpId() { | public String getCorpId() { | ||||
@@ -50,8 +58,8 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||||
return this.token; | return this.token; | ||||
} | } | ||||
public int getExpiresIn() { | |||||
return this.expiresIn; | |||||
public long getExpiresTime() { | |||||
return this.expiresTime; | |||||
} | } | ||||
public void setCorpId(String corpId) { | public void setCorpId(String corpId) { | ||||
@@ -78,8 +86,8 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||||
this.accessToken = accessToken; | this.accessToken = accessToken; | ||||
} | } | ||||
public void setExpiresIn(int expiresIn) { | |||||
this.expiresIn = expiresIn; | |||||
public void setExpiresTime(long expiresTime) { | |||||
this.expiresTime = expiresTime; | |||||
} | } | ||||
public String getAgentId() { | public String getAgentId() { | ||||
@@ -140,7 +148,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||||
", accessToken='" + accessToken + '\'' + | ", accessToken='" + accessToken + '\'' + | ||||
", aesKey='" + aesKey + '\'' + | ", aesKey='" + aesKey + '\'' + | ||||
", agentId='" + agentId + '\'' + | ", agentId='" + agentId + '\'' + | ||||
", expiresIn=" + expiresIn + | |||||
", expiresTime=" + expiresTime + | |||||
", http_proxy_host='" + http_proxy_host + '\'' + | ", http_proxy_host='" + http_proxy_host + '\'' + | ||||
", http_proxy_port=" + http_proxy_port + | ", http_proxy_port=" + http_proxy_port + | ||||
", http_proxy_username='" + http_proxy_username + '\'' + | ", http_proxy_username='" + http_proxy_username + '\'' + | ||||
@@ -1,24 +1,29 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import java.util.Map; | |||||
import me.chanjar.weixin.common.session.WxSession; | |||||
import me.chanjar.weixin.common.session.WxSessionManager; | |||||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | ||||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | ||||
import java.util.Map; | |||||
/** | /** | ||||
* 处理微信推送消息的处理器接口 | * 处理微信推送消息的处理器接口 | ||||
* @author Daniel Qian | |||||
* | * | ||||
* @author Daniel Qian | |||||
*/ | */ | ||||
public interface WxCpMessageHandler { | public interface WxCpMessageHandler { | ||||
/** | /** | ||||
* | |||||
* @param wxMessage | * @param wxMessage | ||||
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 | |||||
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 | |||||
* @param wxCpService | * @param wxCpService | ||||
* @param sessionManager | |||||
* @return xml格式的消息,如果在异步规则里处理的话,可以返回null | * @return xml格式的消息,如果在异步规则里处理的话,可以返回null | ||||
*/ | */ | ||||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService); | |||||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, | |||||
Map<String, Object> context, | |||||
WxCpService wxCpService, | |||||
WxSessionManager sessionManager); | |||||
} | } |
@@ -1,23 +1,29 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import java.util.Map; | |||||
import me.chanjar.weixin.common.session.WxSessionManager; | |||||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | ||||
import java.util.Map; | |||||
/** | /** | ||||
* 微信消息拦截器,可以用来做验证 | * 微信消息拦截器,可以用来做验证 | ||||
* @author Daniel Qian | |||||
* | * | ||||
* @author Daniel Qian | |||||
*/ | */ | ||||
public interface WxCpMessageInterceptor { | public interface WxCpMessageInterceptor { | ||||
/** | /** | ||||
* 拦截微信消息 | * 拦截微信消息 | ||||
* | |||||
* @param wxMessage | * @param wxMessage | ||||
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 | |||||
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 | |||||
* @param wxCpService | * @param wxCpService | ||||
* @return true代表OK,false代表不OK | |||||
* @param sessionManager | |||||
* @return true代表OK,false代表不OK | |||||
*/ | */ | ||||
public boolean intercept(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService); | |||||
public boolean intercept(WxCpXmlMessage wxMessage, | |||||
Map<String, Object> context, | |||||
WxCpService wxCpService, | |||||
WxSessionManager sessionManager); | |||||
} | } |
@@ -1,16 +1,23 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import me.chanjar.weixin.common.session.*; | |||||
import me.chanjar.weixin.common.util.WxMessageDuplicateChecker; | |||||
import me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker; | |||||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.concurrent.ExecutionException; | |||||
import java.util.concurrent.ExecutorService; | import java.util.concurrent.ExecutorService; | ||||
import java.util.concurrent.Executors; | import java.util.concurrent.Executors; | ||||
import java.util.concurrent.Future; | |||||
import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | |||||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||||
/** | /** | ||||
* <pre> | * <pre> | ||||
* 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理 | * 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理 | ||||
@@ -41,22 +48,58 @@ import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | |||||
*/ | */ | ||||
public class WxCpMessageRouter { | public class WxCpMessageRouter { | ||||
private static final int DEFAULT_THREAD_POOL_SIZE = 20; | |||||
protected final Logger log = LoggerFactory.getLogger(WxCpMessageRouter.class); | |||||
private final List<Rule> rules = new ArrayList<Rule>(); | |||||
private static final int DEFAULT_THREAD_POOL_SIZE = 100; | |||||
private final ExecutorService executorService; | |||||
private final List<Rule> rules = new ArrayList<Rule>(); | |||||
private final WxCpService wxCpService; | private final WxCpService wxCpService; | ||||
private ExecutorService executorService; | |||||
private WxMessageDuplicateChecker messageDuplicateChecker; | |||||
private WxSessionManager sessionManager; | |||||
public WxCpMessageRouter(WxCpService wxCpService) { | public WxCpMessageRouter(WxCpService wxCpService) { | ||||
this.wxCpService = wxCpService; | this.wxCpService = wxCpService; | ||||
this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | ||||
this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); | |||||
this.sessionManager = new StandardSessionManager(); | |||||
} | } | ||||
public WxCpMessageRouter(WxCpService wxMpService, int threadPoolSize) { | |||||
this.wxCpService = wxMpService; | |||||
this.executorService = Executors.newFixedThreadPool(threadPoolSize); | |||||
/** | |||||
* <pre> | |||||
* 设置自定义的 {@link ExecutorService} | |||||
* 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100) | |||||
* </pre> | |||||
* @param executorService | |||||
*/ | |||||
public void setExecutorService(ExecutorService executorService) { | |||||
this.executorService = executorService; | |||||
} | |||||
/** | |||||
* <pre> | |||||
* 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker} | |||||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker} | |||||
* </pre> | |||||
* @param messageDuplicateChecker | |||||
*/ | |||||
public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) { | |||||
this.messageDuplicateChecker = messageDuplicateChecker; | |||||
} | |||||
/** | |||||
* <pre> | |||||
* 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager} | |||||
* 如果不调用该方法,默认使用 {@linke SessionManagerImpl} | |||||
* </pre> | |||||
* @param sessionManager | |||||
*/ | |||||
public void setSessionManager(WxSessionManager sessionManager) { | |||||
this.sessionManager = sessionManager; | |||||
} | } | ||||
/** | /** | ||||
@@ -64,7 +107,7 @@ public class WxCpMessageRouter { | |||||
* @return | * @return | ||||
*/ | */ | ||||
public Rule rule() { | public Rule rule() { | ||||
return new Rule(this, wxCpService); | |||||
return new Rule(this, wxCpService, sessionManager); | |||||
} | } | ||||
/** | /** | ||||
@@ -72,6 +115,11 @@ public class WxCpMessageRouter { | |||||
* @param wxMessage | * @param wxMessage | ||||
*/ | */ | ||||
public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { | public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { | ||||
if (isDuplicateMessage(wxMessage)) { | |||||
// 如果是重复消息,那么就不做处理 | |||||
return null; | |||||
} | |||||
final List<Rule> matchRules = new ArrayList<Rule>(); | final List<Rule> matchRules = new ArrayList<Rule>(); | ||||
// 收集匹配的规则 | // 收集匹配的规则 | ||||
for (final Rule rule : rules) { | for (final Rule rule : rules) { | ||||
@@ -88,27 +136,84 @@ public class WxCpMessageRouter { | |||||
} | } | ||||
WxCpXmlOutMessage res = null; | WxCpXmlOutMessage res = null; | ||||
final List<Future> futures = new ArrayList<Future>(); | |||||
for (final Rule rule : matchRules) { | for (final Rule rule : matchRules) { | ||||
// 返回最后一个非异步的rule的执行结果 | // 返回最后一个非异步的rule的执行结果 | ||||
if(rule.async) { | if(rule.async) { | ||||
executorService.submit(new Runnable() { | |||||
public void run() { | |||||
rule.service(wxMessage); | |||||
} | |||||
}); | |||||
futures.add( | |||||
executorService.submit(new Runnable() { | |||||
public void run() { | |||||
rule.service(wxMessage); | |||||
} | |||||
}) | |||||
); | |||||
} else { | } else { | ||||
res = rule.service(wxMessage); | res = rule.service(wxMessage); | ||||
// 在同步操作结束,session访问结束 | |||||
log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUserName()); | |||||
sessionEndAccess(wxMessage); | |||||
} | } | ||||
} | } | ||||
if (futures.size() > 0) { | |||||
executorService.submit(new Runnable() { | |||||
@Override | |||||
public void run() { | |||||
for (Future future : futures) { | |||||
try { | |||||
future.get(); | |||||
log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUserName()); | |||||
// 异步操作结束,session访问结束 | |||||
sessionEndAccess(wxMessage); | |||||
} catch (InterruptedException e) { | |||||
log.error("Error happened when wait task finish", e); | |||||
} catch (ExecutionException e) { | |||||
log.error("Error happened when wait task finish", e); | |||||
} | |||||
} | |||||
} | |||||
}); | |||||
} | |||||
return res; | return res; | ||||
} | } | ||||
protected boolean isDuplicateMessage(WxCpXmlMessage wxMessage) { | |||||
String messageId = ""; | |||||
if (wxMessage.getMsgId() == null) { | |||||
messageId = wxMessage.getFromUserName() + "-" + String.valueOf(wxMessage.getCreateTime()); | |||||
} else { | |||||
messageId = String.valueOf(wxMessage.getMsgId()); | |||||
} | |||||
if (messageDuplicateChecker.isDuplicate(messageId)) { | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
/** | |||||
* 对session的访问结束 | |||||
* @param wxMessage | |||||
*/ | |||||
protected void sessionEndAccess(WxCpXmlMessage wxMessage) { | |||||
InternalSession session = ((InternalSessionManager)sessionManager).findSession(wxMessage.getFromUserName()); | |||||
if (session != null) { | |||||
session.endAccess(); | |||||
} | |||||
} | |||||
public static class Rule { | public static class Rule { | ||||
private final WxCpMessageRouter routerBuilder; | private final WxCpMessageRouter routerBuilder; | ||||
private final WxCpService wxCpService; | private final WxCpService wxCpService; | ||||
private final WxSessionManager sessionManager; | |||||
private boolean async = true; | private boolean async = true; | ||||
private String fromUser; | private String fromUser; | ||||
@@ -131,9 +236,10 @@ public class WxCpMessageRouter { | |||||
private List<WxCpMessageInterceptor> interceptors = new ArrayList<WxCpMessageInterceptor>(); | private List<WxCpMessageInterceptor> interceptors = new ArrayList<WxCpMessageInterceptor>(); | ||||
protected Rule(WxCpMessageRouter routerBuilder, WxCpService wxCpService) { | |||||
protected Rule(WxCpMessageRouter routerBuilder, WxCpService wxCpService, WxSessionManager sessionManager) { | |||||
this.routerBuilder = routerBuilder; | this.routerBuilder = routerBuilder; | ||||
this.wxCpService = wxCpService; | this.wxCpService = wxCpService; | ||||
this.sessionManager = sessionManager; | |||||
} | } | ||||
/** | /** | ||||
@@ -311,7 +417,7 @@ public class WxCpMessageRouter { | |||||
Map<String, Object> context = new HashMap<String, Object>(); | Map<String, Object> context = new HashMap<String, Object>(); | ||||
// 如果拦截器不通过 | // 如果拦截器不通过 | ||||
for (WxCpMessageInterceptor interceptor : this.interceptors) { | for (WxCpMessageInterceptor interceptor : this.interceptors) { | ||||
if (!interceptor.intercept(wxMessage, context, wxCpService)) { | |||||
if (!interceptor.intercept(wxMessage, context, wxCpService, sessionManager)) { | |||||
return null; | return null; | ||||
} | } | ||||
} | } | ||||
@@ -320,7 +426,7 @@ public class WxCpMessageRouter { | |||||
WxCpXmlOutMessage res = null; | WxCpXmlOutMessage res = null; | ||||
for (WxCpMessageHandler handler : this.handlers) { | for (WxCpMessageHandler handler : this.handlers) { | ||||
// 返回最后handler的结果 | // 返回最后handler的结果 | ||||
res = handler.handle(wxMessage, context, wxCpService); | |||||
res = handler.handle(wxMessage, context, wxCpService, sessionManager); | |||||
} | } | ||||
return res; | return res; | ||||
} | } | ||||
@@ -1,17 +1,20 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import java.io.File; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.common.bean.WxMenu; | import me.chanjar.weixin.common.bean.WxMenu; | ||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import me.chanjar.weixin.common.session.WxSession; | |||||
import me.chanjar.weixin.common.session.WxSessionManager; | |||||
import me.chanjar.weixin.common.util.http.RequestExecutor; | import me.chanjar.weixin.common.util.http.RequestExecutor; | ||||
import me.chanjar.weixin.cp.bean.*; | |||||
import me.chanjar.weixin.cp.bean.WxCpDepart; | import me.chanjar.weixin.cp.bean.WxCpDepart; | ||||
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.bean.WxCpUser; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import java.io.File; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.util.List; | |||||
/** | /** | ||||
* 微信API的Service | * 微信API的Service | ||||
@@ -42,6 +45,14 @@ public interface WxCpService { | |||||
*/ | */ | ||||
public void userAuthenticated(String userId) throws WxErrorException; | public void userAuthenticated(String userId) throws WxErrorException; | ||||
/** | |||||
* 获取access_token, 不强制刷新access_token | |||||
* @see #getAccessToken(boolean) | |||||
* @return | |||||
* @throws WxErrorException | |||||
*/ | |||||
public String getAccessToken() throws WxErrorException; | |||||
/** | /** | ||||
* <pre> | * <pre> | ||||
* 获取access_token,本方法线程安全 | * 获取access_token,本方法线程安全 | ||||
@@ -50,10 +61,11 @@ public interface WxCpService { | |||||
* 程序员在非必要情况下尽量不要主动调用此方法 | * 程序员在非必要情况下尽量不要主动调用此方法 | ||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | ||||
* </pre> | * </pre> | ||||
* | |||||
* @param forceRefresh 强制刷新 | |||||
* @return | |||||
* @throws me.chanjar.weixin.common.exception.WxErrorException | * @throws me.chanjar.weixin.common.exception.WxErrorException | ||||
*/ | */ | ||||
public void accessTokenRefresh() throws WxErrorException; | |||||
public String getAccessToken(boolean forceRefresh) throws WxErrorException; | |||||
/** | /** | ||||
* <pre> | * <pre> | ||||
@@ -343,4 +355,46 @@ public interface WxCpService { | |||||
* @param wxConfigProvider | * @param wxConfigProvider | ||||
*/ | */ | ||||
public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider); | public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider); | ||||
/** | |||||
* <pre> | |||||
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试 | |||||
* 默认:1000ms | |||||
* </pre> | |||||
* @param retrySleepMillis | |||||
*/ | |||||
void setRetrySleepMillis(int retrySleepMillis); | |||||
/** | |||||
* <pre> | |||||
* 设置当微信系统响应系统繁忙时,最大重试次数 | |||||
* 默认:5次 | |||||
* </pre> | |||||
* @param maxRetryTimes | |||||
*/ | |||||
void setMaxRetryTimes(int maxRetryTimes); | |||||
/** | |||||
* 获取某个sessionId对应的session,如果sessionId没有对应的session,则新建一个并返回。 | |||||
* @param id id可以为任意字符串,建议使用FromUserName作为id | |||||
* @return | |||||
*/ | |||||
WxSession getSession(String id); | |||||
/** | |||||
* 获取某个sessionId对应的session,如果sessionId没有对应的session,若create为true则新建一个,否则返回null。 | |||||
* @param id id可以为任意字符串,建议使用FromUserName作为id | |||||
* @param create | |||||
* @return | |||||
*/ | |||||
WxSession getSession(String id, boolean create); | |||||
/** | |||||
* <pre> | |||||
* 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法, | |||||
* WxCpService默认使用的是{@link me.chanjar.weixin.common.session.StandardSessionManager} | |||||
* </pre> | |||||
* @param sessionManager | |||||
*/ | |||||
void setSessionManager(WxSessionManager sessionManager); | |||||
} | } |
@@ -12,6 +12,9 @@ import me.chanjar.weixin.common.bean.WxMenu; | |||||
import me.chanjar.weixin.common.bean.result.WxError; | import me.chanjar.weixin.common.bean.result.WxError; | ||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | import me.chanjar.weixin.common.exception.WxErrorException; | ||||
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.StringUtils; | import me.chanjar.weixin.common.util.StringUtils; | ||||
import me.chanjar.weixin.common.util.crypto.SHA1; | import me.chanjar.weixin.common.util.crypto.SHA1; | ||||
import me.chanjar.weixin.common.util.fs.FileUtils; | import me.chanjar.weixin.common.util.fs.FileUtils; | ||||
@@ -34,6 +37,8 @@ import org.apache.http.impl.client.BasicCredentialsProvider; | |||||
import org.apache.http.impl.client.BasicResponseHandler; | import org.apache.http.impl.client.BasicResponseHandler; | ||||
import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||
import org.apache.http.impl.client.HttpClients; | import org.apache.http.impl.client.HttpClients; | ||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
@@ -41,25 +46,28 @@ import java.io.InputStream; | |||||
import java.io.StringReader; | import java.io.StringReader; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.UUID; | import java.util.UUID; | ||||
import java.util.concurrent.atomic.AtomicBoolean; | |||||
public class WxCpServiceImpl implements WxCpService { | public class WxCpServiceImpl implements WxCpService { | ||||
protected final Logger log = LoggerFactory.getLogger(WxCpServiceImpl.class); | |||||
/** | /** | ||||
* 全局的是否正在刷新Access Token的flag | |||||
* true: 正在刷新 | |||||
* false: 没有刷新 | |||||
* 全局的是否正在刷新access token的锁 | |||||
*/ | */ | ||||
protected static final AtomicBoolean GLOBAL_ACCESS_TOKEN_REFRESH_FLAG = new AtomicBoolean(false); | |||||
protected static final Object GLOBAL_ACCESS_TOKEN_REFRESH_LOCK = new Object(); | |||||
protected WxCpConfigStorage wxCpConfigStorage; | protected WxCpConfigStorage wxCpConfigStorage; | ||||
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>(); | |||||
protected CloseableHttpClient httpClient; | protected CloseableHttpClient httpClient; | ||||
protected HttpHost httpProxy; | protected HttpHost httpProxy; | ||||
private int retrySleepMillis = 1000; | |||||
private int maxRetryTimes = 5; | |||||
protected WxSessionManager sessionManager = new StandardSessionManager(); | |||||
public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { | ||||
try { | try { | ||||
return SHA1.gen(wxCpConfigStorage.getToken(), timestamp, nonce, data).equals(msgSignature); | return SHA1.gen(wxCpConfigStorage.getToken(), timestamp, nonce, data).equals(msgSignature); | ||||
@@ -73,45 +81,44 @@ public class WxCpServiceImpl implements WxCpService { | |||||
execute(new SimpleGetRequestExecutor(), url, null); | execute(new SimpleGetRequestExecutor(), url, null); | ||||
} | } | ||||
public void accessTokenRefresh() throws WxErrorException { | |||||
if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) { | |||||
try { | |||||
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
+ "&corpid=" + wxCpConfigStorage.getCorpId() | |||||
+ "&corpsecret=" + wxCpConfigStorage.getCorpSecret(); | |||||
try { | |||||
HttpGet httpGet = new HttpGet(url); | |||||
if (httpProxy != null) { | |||||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||||
httpGet.setConfig(config); | |||||
} | |||||
CloseableHttpClient httpclient = getHttpclient(); | |||||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||||
String resultContent = new BasicResponseHandler().handleResponse(response); | |||||
WxError error = WxError.fromJson(resultContent); | |||||
if (error.getErrorCode() != 0) { | |||||
throw new WxErrorException(error); | |||||
public String getAccessToken() throws WxErrorException { | |||||
return getAccessToken(false); | |||||
} | |||||
public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||||
if (forceRefresh) { | |||||
wxCpConfigStorage.expireAccessToken(); | |||||
} | |||||
if (wxCpConfigStorage.isAccessTokenExpired()) { | |||||
synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) { | |||||
if (wxCpConfigStorage.isAccessTokenExpired()) { | |||||
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
+ "&corpid=" + wxCpConfigStorage.getCorpId() | |||||
+ "&corpsecret=" + wxCpConfigStorage.getCorpSecret(); | |||||
try { | |||||
HttpGet httpGet = new HttpGet(url); | |||||
if (httpProxy != null) { | |||||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||||
httpGet.setConfig(config); | |||||
} | |||||
CloseableHttpClient httpclient = getHttpclient(); | |||||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||||
String resultContent = new BasicResponseHandler().handleResponse(response); | |||||
WxError error = WxError.fromJson(resultContent); | |||||
if (error.getErrorCode() != 0) { | |||||
throw new WxErrorException(error); | |||||
} | |||||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
wxCpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
} catch (ClientProtocolException e) { | |||||
throw new RuntimeException(e); | |||||
} catch (IOException e) { | |||||
throw new RuntimeException(e); | |||||
} | } | ||||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
wxCpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
} catch (ClientProtocolException e) { | |||||
throw new RuntimeException(e); | |||||
} catch (IOException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} finally { | |||||
GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.set(false); | |||||
} | |||||
} else { | |||||
// 每隔100ms检查一下是否刷新完毕了 | |||||
while (GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.get()) { | |||||
try { | |||||
Thread.sleep(100); | |||||
} catch (InterruptedException e) { | |||||
} | } | ||||
} | } | ||||
// 刷新完毕了,就没他什么事儿了 | |||||
} | } | ||||
return wxCpConfigStorage.getAccessToken(); | |||||
} | } | ||||
public void messageSend(WxCpMessage message) throws WxErrorException { | public void messageSend(WxCpMessage message) throws WxErrorException { | ||||
@@ -369,10 +376,34 @@ public class WxCpServiceImpl implements WxCpService { | |||||
* @throws WxErrorException | * @throws WxErrorException | ||||
*/ | */ | ||||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | ||||
if (StringUtils.isBlank(wxCpConfigStorage.getAccessToken())) { | |||||
accessTokenRefresh(); | |||||
} | |||||
String accessToken = wxCpConfigStorage.getAccessToken(); | |||||
int retryTimes = 0; | |||||
do { | |||||
try { | |||||
return executeInternal(executor, uri, data); | |||||
} catch (WxErrorException e) { | |||||
WxError error = e.getError(); | |||||
/** | |||||
* -1 系统繁忙, 1000ms后重试 | |||||
*/ | |||||
if (error.getErrorCode() == -1) { | |||||
int sleepMillis = retrySleepMillis * (1 << retryTimes); | |||||
try { | |||||
log.debug("微信系统繁忙,{}ms 后重试(第{}次)", sleepMillis, retryTimes + 1); | |||||
Thread.sleep(sleepMillis); | |||||
} catch (InterruptedException e1) { | |||||
throw new RuntimeException(e1); | |||||
} | |||||
} else { | |||||
throw e; | |||||
} | |||||
} | |||||
} while(++retryTimes < maxRetryTimes); | |||||
throw new RuntimeException("微信服务端异常,超出重试次数"); | |||||
} | |||||
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||||
String accessToken = getAccessToken(false); | |||||
String uriWithAccessToken = uri; | String uriWithAccessToken = uri; | ||||
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; | uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; | ||||
@@ -387,30 +418,10 @@ public class WxCpServiceImpl implements WxCpService { | |||||
* 42001 access_token超时 | * 42001 access_token超时 | ||||
*/ | */ | ||||
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | ||||
accessTokenRefresh(); | |||||
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||||
wxCpConfigStorage.expireAccessToken(); | |||||
return execute(executor, uri, data); | return execute(executor, uri, data); | ||||
} | } | ||||
/** | |||||
* -1 系统繁忙, 1000ms后重试 | |||||
*/ | |||||
if (error.getErrorCode() == -1) { | |||||
if (retryTimes.get() == null) { | |||||
retryTimes.set(0); | |||||
} | |||||
if (retryTimes.get() > 4) { | |||||
retryTimes.set(0); | |||||
throw new RuntimeException("微信服务端异常,超出重试次数"); | |||||
} | |||||
int sleepMillis = 1000 * (1 << retryTimes.get()); | |||||
try { | |||||
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试"); | |||||
Thread.sleep(sleepMillis); | |||||
retryTimes.set(retryTimes.get() + 1); | |||||
return execute(executor, uri, data); | |||||
} catch (InterruptedException e1) { | |||||
throw new RuntimeException(e1); | |||||
} | |||||
} | |||||
if (error.getErrorCode() != 0) { | if (error.getErrorCode() != 0) { | ||||
throw new WxErrorException(error); | throw new WxErrorException(error); | ||||
} | } | ||||
@@ -421,7 +432,6 @@ public class WxCpServiceImpl implements WxCpService { | |||||
throw new RuntimeException(e); | throw new RuntimeException(e); | ||||
} | } | ||||
} | } | ||||
protected CloseableHttpClient getHttpclient() { | protected CloseableHttpClient getHttpclient() { | ||||
return httpClient; | return httpClient; | ||||
} | } | ||||
@@ -456,4 +466,37 @@ public class WxCpServiceImpl implements WxCpService { | |||||
} | } | ||||
} | } | ||||
@Override | |||||
public void setRetrySleepMillis(int retrySleepMillis) { | |||||
this.retrySleepMillis = retrySleepMillis; | |||||
} | |||||
@Override | |||||
public void setMaxRetryTimes(int maxRetryTimes) { | |||||
this.maxRetryTimes = maxRetryTimes; | |||||
} | |||||
@Override | |||||
public WxSession getSession(String id) { | |||||
if (sessionManager == null) { | |||||
return null; | |||||
} | |||||
return sessionManager.getSession(id); | |||||
} | |||||
@Override | |||||
public WxSession getSession(String id, boolean create) { | |||||
if (sessionManager == null) { | |||||
return null; | |||||
} | |||||
return sessionManager.getSession(id, create); | |||||
} | |||||
@Override | |||||
public void setSessionManager(WxSessionManager sessionManager) { | |||||
this.sessionManager = sessionManager; | |||||
} | |||||
} | } |
@@ -1,11 +1,11 @@ | |||||
package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.cp.bean.messagebuilder.*; | import me.chanjar.weixin.cp.bean.messagebuilder.*; | ||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | ||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
/** | /** | ||||
* 消息 | * 消息 | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
@@ -21,6 +21,8 @@ public class WxCpUser { | |||||
private String tel; | private String tel; | ||||
private String email; | private String email; | ||||
private String weiXinId; | private String weiXinId; | ||||
private String avatar; | |||||
private Integer status; | |||||
private final List<Attr> extAttrs = new ArrayList<Attr>(); | private final List<Attr> extAttrs = new ArrayList<Attr>(); | ||||
public String getUserId() { | public String getUserId() { | ||||
@@ -95,6 +97,22 @@ public class WxCpUser { | |||||
this.weiXinId = weiXinId; | this.weiXinId = weiXinId; | ||||
} | } | ||||
public String getAvatar() { | |||||
return avatar; | |||||
} | |||||
public void setAvatar(String avatar) { | |||||
this.avatar = avatar; | |||||
} | |||||
public Integer getStatus() { | |||||
return status; | |||||
} | |||||
public void setStatus(Integer status) { | |||||
this.status = status; | |||||
} | |||||
public void addExtAttr(String name, String value) { | public void addExtAttr(String name, String value) { | ||||
this.extAttrs.add(new Attr(name, value)); | this.extAttrs.add(new Attr(name, value)); | ||||
} | } | ||||
@@ -1,14 +1,13 @@ | |||||
package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||||
import me.chanjar.weixin.cp.api.WxCpConfigStorage; | import me.chanjar.weixin.cp.api.WxCpConfigStorage; | ||||
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | ||||
import me.chanjar.weixin.cp.util.xml.XmlTransformer; | |||||
import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | |||||
import org.apache.commons.io.IOUtils; | import org.apache.commons.io.IOUtils; | ||||
import javax.xml.bind.JAXBException; | |||||
import javax.xml.bind.annotation.*; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
@@ -25,103 +24,102 @@ import java.util.List; | |||||
* | * | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
*/ | */ | ||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxCpXmlMessage { | public class WxCpXmlMessage { | ||||
/////////////////////// | /////////////////////// | ||||
// 以下都是微信推送过来的消息的xml的element所对应的属性 | // 以下都是微信推送过来的消息的xml的element所对应的属性 | ||||
/////////////////////// | /////////////////////// | ||||
@XmlElement(name="AgentID") | |||||
@XStreamAlias("AgentID") | |||||
private Integer agentId; | private Integer agentId; | ||||
@XmlElement(name = "ToUserName") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ToUserName") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String toUserName; | private String toUserName; | ||||
@XmlElement(name = "FromUserName") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("FromUserName") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String fromUserName; | private String fromUserName; | ||||
@XmlElement(name = "CreateTime") | |||||
@XStreamAlias("CreateTime") | |||||
private Long createTime; | private Long createTime; | ||||
@XmlElement(name = "MsgType") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("MsgType") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String msgType; | private String msgType; | ||||
@XmlElement(name = "Content") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Content") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String content; | private String content; | ||||
@XmlElement(name = "MsgId") | |||||
@XStreamAlias("MsgId") | |||||
private Long msgId; | private Long msgId; | ||||
@XmlElement(name = "PicUrl") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("PicUrl") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String picUrl; | private String picUrl; | ||||
@XmlElement(name = "MediaId") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("MediaId") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String mediaId; | private String mediaId; | ||||
@XmlElement(name = "Format") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Format") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String format; | private String format; | ||||
@XmlElement(name = "ThumbMediaId") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ThumbMediaId") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String thumbMediaId; | private String thumbMediaId; | ||||
@XmlElement(name = "Location_X") | |||||
@XStreamAlias("Location_X") | |||||
private Double locationX; | private Double locationX; | ||||
@XmlElement(name = "Location_Y") | |||||
@XStreamAlias("Location_Y") | |||||
private Double locationY; | private Double locationY; | ||||
@XmlElement(name = "Scale") | |||||
@XStreamAlias("Scale") | |||||
private Double scale; | private Double scale; | ||||
@XmlElement(name = "Label") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Label") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String label; | private String label; | ||||
@XmlElement(name = "Title") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Title") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String title; | private String title; | ||||
@XmlElement(name = "Description") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Description") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String description; | private String description; | ||||
@XmlElement(name = "Url") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Url") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String url; | private String url; | ||||
@XmlElement(name = "Event") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Event") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String event; | private String event; | ||||
@XmlElement(name = "EventKey") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("EventKey") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String eventKey; | private String eventKey; | ||||
@XmlElement(name = "Ticket") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Ticket") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String ticket; | private String ticket; | ||||
@XmlElement(name = "Latitude") | |||||
@XStreamAlias("Latitude") | |||||
private Double latitude; | private Double latitude; | ||||
@XmlElement(name = "Longitude") | |||||
@XStreamAlias("Longitude") | |||||
private Double longitude; | private Double longitude; | ||||
@XmlElement(name = "Precision") | |||||
@XStreamAlias("Precision") | |||||
private Double precision; | private Double precision; | ||||
@XmlElement(name = "Recognition") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Recognition") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String recognition; | private String recognition; | ||||
/////////////////////////////////////// | /////////////////////////////////////// | ||||
@@ -130,37 +128,37 @@ public class WxCpXmlMessage { | |||||
/** | /** | ||||
* 群发的结果 | * 群发的结果 | ||||
*/ | */ | ||||
@XmlElement(name = "Status") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Status") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String status; | private String status; | ||||
/** | /** | ||||
* group_id下粉丝数;或者openid_list中的粉丝数 | * group_id下粉丝数;或者openid_list中的粉丝数 | ||||
*/ | */ | ||||
@XmlElement(name = "TotalCount") | |||||
@XStreamAlias("TotalCount") | |||||
private Integer totalCount; | private Integer totalCount; | ||||
/** | /** | ||||
* 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount | * 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount | ||||
*/ | */ | ||||
@XmlElement(name = "FilterCount") | |||||
@XStreamAlias("FilterCount") | |||||
private Integer filterCount; | private Integer filterCount; | ||||
/** | /** | ||||
* 发送成功的粉丝数 | * 发送成功的粉丝数 | ||||
*/ | */ | ||||
@XmlElement(name = "SentCount") | |||||
@XStreamAlias("SentCount") | |||||
private Integer sentCount; | private Integer sentCount; | ||||
/** | /** | ||||
* 发送失败的粉丝数 | * 发送失败的粉丝数 | ||||
*/ | */ | ||||
@XmlElement(name = "ErrorCount") | |||||
@XStreamAlias("ErrorCount") | |||||
private Integer errorCount; | private Integer errorCount; | ||||
@XmlElement(name = "ScanCodeInfo") | |||||
@XStreamAlias("ScanCodeInfo") | |||||
private ScanCodeInfo scanCodeInfo = new ScanCodeInfo(); | private ScanCodeInfo scanCodeInfo = new ScanCodeInfo(); | ||||
@XmlElement(name = "SendPicsInfo") | |||||
@XStreamAlias("SendPicsInfo") | |||||
private SendPicsInfo sendPicsInfo = new SendPicsInfo(); | private SendPicsInfo sendPicsInfo = new SendPicsInfo(); | ||||
@XmlElement(name = "SendLocationInfo") | |||||
@XStreamAlias("SendLocationInfo") | |||||
private SendLocationInfo sendLocationInfo = new SendLocationInfo(); | private SendLocationInfo sendLocationInfo = new SendLocationInfo(); | ||||
public Integer getAgentId() { | public Integer getAgentId() { | ||||
@@ -390,19 +388,11 @@ public class WxCpXmlMessage { | |||||
} | } | ||||
protected static WxCpXmlMessage fromXml(String xml) { | protected static WxCpXmlMessage fromXml(String xml) { | ||||
try { | |||||
return XmlTransformer.fromXml(WxCpXmlMessage.class, xml); | |||||
} catch (JAXBException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
return XStreamTransformer.fromXml(WxCpXmlMessage.class, xml); | |||||
} | } | ||||
protected static WxCpXmlMessage fromXml(InputStream is) { | protected static WxCpXmlMessage fromXml(InputStream is) { | ||||
try { | |||||
return XmlTransformer.fromXml(WxCpXmlMessage.class, is); | |||||
} catch (JAXBException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
return XStreamTransformer.fromXml(WxCpXmlMessage.class, is); | |||||
} | } | ||||
/** | /** | ||||
@@ -499,16 +489,15 @@ public class WxCpXmlMessage { | |||||
this.sendLocationInfo = sendLocationInfo; | this.sendLocationInfo = sendLocationInfo; | ||||
} | } | ||||
@XmlRootElement(name = "ScanCodeInfo") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("ScanCodeInfo") | |||||
public static class ScanCodeInfo { | public static class ScanCodeInfo { | ||||
@XmlElement(name = "ScanType") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ScanType") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String scanType; | private String scanType; | ||||
@XmlElement(name = "ScanResult") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ScanResult") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String scanResult; | private String scanResult; | ||||
/** | /** | ||||
@@ -538,15 +527,13 @@ public class WxCpXmlMessage { | |||||
} | } | ||||
@XmlRootElement(name = "SendPicsInfo") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("SendPicsInfo") | |||||
public static class SendPicsInfo { | public static class SendPicsInfo { | ||||
@XmlElement(name = "Count") | |||||
@XStreamAlias("Count") | |||||
private Long count; | private Long count; | ||||
@XmlElementWrapper(name="PicList") | |||||
@XmlElement(name = "item") | |||||
@XStreamAlias("PicList") | |||||
protected final List<Item> picList = new ArrayList<Item>(); | protected final List<Item> picList = new ArrayList<Item>(); | ||||
public Long getCount() { | public Long getCount() { | ||||
@@ -561,13 +548,11 @@ public class WxCpXmlMessage { | |||||
return picList; | return picList; | ||||
} | } | ||||
@XmlRootElement(name = "item") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XmlType(name = "WxXmlMessage.SendPicsInfo.Item") | |||||
@XStreamAlias("item") | |||||
public static class Item { | public static class Item { | ||||
@XmlElement(name = "PicMd5Sum") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("PicMd5Sum") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String PicMd5Sum; | private String PicMd5Sum; | ||||
public String getPicMd5Sum() { | public String getPicMd5Sum() { | ||||
@@ -580,28 +565,27 @@ public class WxCpXmlMessage { | |||||
} | } | ||||
} | } | ||||
@XmlRootElement(name = "SendLocationInfo") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("SendLocationInfo") | |||||
public static class SendLocationInfo { | public static class SendLocationInfo { | ||||
@XmlElement(name = "Location_X") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Location_X") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String locationX; | private String locationX; | ||||
@XmlElement(name = "Location_Y") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Location_Y") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String locationY; | private String locationY; | ||||
@XmlElement(name = "Scale") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Scale") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String scale; | private String scale; | ||||
@XmlElement(name = "Label") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Label") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String label; | private String label; | ||||
@XmlElement(name = "Poiname") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Poiname") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String poiname; | private String poiname; | ||||
public String getLocationX() { | public String getLocationX() { | ||||
@@ -1,20 +1,15 @@ | |||||
package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.util.xml.MediaIdMarshaller; | |||||
import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxCpXmlOutImageMessage extends WxCpXmlOutMessage { | public class WxCpXmlOutImageMessage extends WxCpXmlOutMessage { | ||||
@XmlElement(name="Image") | |||||
@XmlJavaTypeAdapter(MediaIdMarshaller.class) | |||||
@XStreamAlias("Image") | |||||
@XStreamConverter(value=XStreamMediaIdConverter.class) | |||||
private String mediaId; | private String mediaId; | ||||
public WxCpXmlOutImageMessage() { | public WxCpXmlOutImageMessage() { | ||||
@@ -1,35 +1,29 @@ | |||||
package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||||
import me.chanjar.weixin.cp.api.WxCpConfigStorage; | import me.chanjar.weixin.cp.api.WxCpConfigStorage; | ||||
import me.chanjar.weixin.cp.bean.outxmlbuilder.*; | import me.chanjar.weixin.cp.bean.outxmlbuilder.*; | ||||
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | ||||
import me.chanjar.weixin.cp.util.xml.XmlTransformer; | |||||
import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | |||||
import javax.xml.bind.JAXBException; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
@XStreamAlias("xml") | |||||
public abstract class WxCpXmlOutMessage { | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public class WxCpXmlOutMessage { | |||||
@XmlElement(name="ToUserName") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ToUserName") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
protected String toUserName; | protected String toUserName; | ||||
@XmlElement(name="FromUserName") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("FromUserName") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
protected String fromUserName; | protected String fromUserName; | ||||
@XmlElement(name="CreateTime") | |||||
@XStreamAlias("CreateTime") | |||||
protected Long createTime; | protected Long createTime; | ||||
@XmlElement(name="MsgType") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("MsgType") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
protected String msgType; | protected String msgType; | ||||
public String getToUserName() { | public String getToUserName() { | ||||
@@ -65,11 +59,7 @@ public class WxCpXmlOutMessage { | |||||
} | } | ||||
protected String toXml() { | protected String toXml() { | ||||
try { | |||||
return XmlTransformer.toXml((Class)this.getClass(), this); | |||||
} catch (JAXBException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
return XStreamTransformer.toXml((Class)this.getClass(), this); | |||||
} | } | ||||
/** | /** | ||||
@@ -1,22 +1,20 @@ | |||||
package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||||
import javax.xml.bind.annotation.*; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | import java.util.List; | ||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxCpXmlOutNewsMessage extends WxCpXmlOutMessage { | public class WxCpXmlOutNewsMessage extends WxCpXmlOutMessage { | ||||
@XmlElement(name = "ArticleCount") | |||||
@XStreamAlias("ArticleCount") | |||||
protected int articleCount; | protected int articleCount; | ||||
@XmlElementWrapper(name="Articles") | |||||
@XmlElement(name = "item") | |||||
@XStreamAlias("Articles") | |||||
protected final List<Item> articles = new ArrayList<Item>(); | protected final List<Item> articles = new ArrayList<Item>(); | ||||
public WxCpXmlOutNewsMessage() { | public WxCpXmlOutNewsMessage() { | ||||
@@ -37,24 +35,23 @@ public class WxCpXmlOutNewsMessage extends WxCpXmlOutMessage { | |||||
} | } | ||||
@XmlRootElement(name = "Item") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("item") | |||||
public static class Item { | public static class Item { | ||||
@XmlElement(name = "Title") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Title") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String Title; | private String Title; | ||||
@XmlElement(name = "Description") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Description") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String Description; | private String Description; | ||||
@XmlElement(name="PicUrl") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("PicUrl") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String PicUrl; | private String PicUrl; | ||||
@XmlElement(name="Url") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Url") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String Url; | private String Url; | ||||
public String getTitle() { | public String getTitle() { | ||||
@@ -1,20 +1,15 @@ | |||||
package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxCpXmlOutTextMessage extends WxCpXmlOutMessage { | public class WxCpXmlOutTextMessage extends WxCpXmlOutMessage { | ||||
@XmlElement(name="Content") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Content") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String content; | private String content; | ||||
public WxCpXmlOutTextMessage() { | public WxCpXmlOutTextMessage() { | ||||
@@ -1,19 +1,14 @@ | |||||
package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxCpXmlOutVideoMessage extends WxCpXmlOutMessage { | public class WxCpXmlOutVideoMessage extends WxCpXmlOutMessage { | ||||
@XmlElement(name = "Video") | |||||
@XStreamAlias("Video") | |||||
protected final Video video = new Video(); | protected final Video video = new Video(); | ||||
public WxCpXmlOutVideoMessage() { | public WxCpXmlOutVideoMessage() { | ||||
@@ -45,20 +40,19 @@ public class WxCpXmlOutVideoMessage extends WxCpXmlOutMessage { | |||||
} | } | ||||
@XmlRootElement(name = "Video") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
private static class Video { | |||||
@XStreamAlias("Video") | |||||
public static class Video { | |||||
@XmlElement(name = "MediaId") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("MediaId") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String mediaId; | private String mediaId; | ||||
@XmlElement(name = "Title") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Title") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String title; | private String title; | ||||
@XmlElement(name = "Description") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Description") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String description; | private String description; | ||||
public String getMediaId() { | public String getMediaId() { | ||||
@@ -1,20 +1,15 @@ | |||||
package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.util.xml.MediaIdMarshaller; | |||||
import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxCpXmlOutVoiceMessage extends WxCpXmlOutMessage { | public class WxCpXmlOutVoiceMessage extends WxCpXmlOutMessage { | ||||
@XmlElement(name="Voice") | |||||
@XmlJavaTypeAdapter(MediaIdMarshaller.class) | |||||
@XStreamAlias("Voice") | |||||
@XStreamConverter(value=XStreamMediaIdConverter.class) | |||||
private String mediaId; | private String mediaId; | ||||
public WxCpXmlOutVoiceMessage() { | public WxCpXmlOutVoiceMessage() { | ||||
@@ -1,11 +1,11 @@ | |||||
package me.chanjar.weixin.cp.bean.outxmlbuilder; | package me.chanjar.weixin.cp.bean.outxmlbuilder; | ||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessage; | ||||
import me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessage.Item; | import me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessage.Item; | ||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
/** | /** | ||||
* 图文消息builder | * 图文消息builder | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
@@ -8,18 +8,11 @@ | |||||
*/ | */ | ||||
package me.chanjar.weixin.cp.util.json; | package me.chanjar.weixin.cp.util.json; | ||||
import java.lang.reflect.Type; | |||||
import com.google.gson.*; | |||||
import me.chanjar.weixin.common.util.json.GsonHelper; | import me.chanjar.weixin.common.util.json.GsonHelper; | ||||
import me.chanjar.weixin.cp.bean.WxCpDepart; | import me.chanjar.weixin.cp.bean.WxCpDepart; | ||||
import com.google.gson.JsonDeserializationContext; | |||||
import com.google.gson.JsonDeserializer; | |||||
import com.google.gson.JsonElement; | |||||
import com.google.gson.JsonObject; | |||||
import com.google.gson.JsonParseException; | |||||
import com.google.gson.JsonSerializationContext; | |||||
import com.google.gson.JsonSerializer; | |||||
import java.lang.reflect.Type; | |||||
/** | /** | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
@@ -2,11 +2,12 @@ package me.chanjar.weixin.cp.util.json; | |||||
import com.google.gson.Gson; | import com.google.gson.Gson; | ||||
import com.google.gson.GsonBuilder; | import com.google.gson.GsonBuilder; | ||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | |||||
import me.chanjar.weixin.common.bean.result.WxError; | import me.chanjar.weixin.common.bean.result.WxError; | ||||
import me.chanjar.weixin.common.util.json.WxMediaUploadResultAdapter; | |||||
import me.chanjar.weixin.common.util.json.WxErrorAdapter; | import me.chanjar.weixin.common.util.json.WxErrorAdapter; | ||||
import me.chanjar.weixin.cp.bean.*; | |||||
import me.chanjar.weixin.cp.bean.WxCpDepart; | |||||
import me.chanjar.weixin.cp.bean.WxCpMessage; | |||||
import me.chanjar.weixin.cp.bean.WxCpTag; | |||||
import me.chanjar.weixin.cp.bean.WxCpUser; | |||||
public class WxCpGsonBuilder { | public class WxCpGsonBuilder { | ||||
@@ -8,12 +8,12 @@ | |||||
*/ | */ | ||||
package me.chanjar.weixin.cp.util.json; | package me.chanjar.weixin.cp.util.json; | ||||
import java.lang.reflect.Type; | |||||
import com.google.gson.*; | import com.google.gson.*; | ||||
import me.chanjar.weixin.common.util.json.GsonHelper; | import me.chanjar.weixin.common.util.json.GsonHelper; | ||||
import me.chanjar.weixin.cp.bean.WxCpUser; | import me.chanjar.weixin.cp.bean.WxCpUser; | ||||
import java.lang.reflect.Type; | |||||
/** | /** | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
*/ | */ | ||||
@@ -49,6 +49,8 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||||
user.setTel(GsonHelper.getString(o, "tel")); | user.setTel(GsonHelper.getString(o, "tel")); | ||||
user.setEmail(GsonHelper.getString(o, "email")); | user.setEmail(GsonHelper.getString(o, "email")); | ||||
user.setWeiXinId(GsonHelper.getString(o, "weixinid")); | user.setWeiXinId(GsonHelper.getString(o, "weixinid")); | ||||
user.setAvatar(GsonHelper.getString(o, "avatar")); | |||||
user.setStatus(GsonHelper.getInteger(o, "status")); | |||||
if (GsonHelper.isNotNull(o.get("extattr"))) { | if (GsonHelper.isNotNull(o.get("extattr"))) { | ||||
JsonArray attrJsonElements = o.get("extattr").getAsJsonObject().get("attrs").getAsJsonArray(); | JsonArray attrJsonElements = o.get("extattr").getAsJsonObject().get("attrs").getAsJsonArray(); | ||||
@@ -97,6 +99,12 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||||
if (user.getWeiXinId() != null) { | if (user.getWeiXinId() != null) { | ||||
o.addProperty("weixinid", user.getWeiXinId()); | o.addProperty("weixinid", user.getWeiXinId()); | ||||
} | } | ||||
if (user.getAvatar() != null) { | |||||
o.addProperty("avatar", user.getAvatar()); | |||||
} | |||||
if (user.getStatus() != null) { | |||||
o.addProperty("status", user.getStatus()); | |||||
} | |||||
if (user.getExtAttrs().size() > 0) { | if (user.getExtAttrs().size() > 0) { | ||||
JsonArray attrsJsonArray = new JsonArray(); | JsonArray attrsJsonArray = new JsonArray(); | ||||
for (WxCpUser.Attr attr : user.getExtAttrs()) { | for (WxCpUser.Attr attr : user.getExtAttrs()) { | ||||
@@ -0,0 +1,103 @@ | |||||
package me.chanjar.weixin.cp.util.xml; | |||||
import com.thoughtworks.xstream.XStream; | |||||
import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||||
import me.chanjar.weixin.cp.bean.*; | |||||
import java.io.InputStream; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
public class XStreamTransformer { | |||||
protected static final Map<Class, XStream> CLASS_2_XSTREAM_INSTANCE = configXStreamInstance(); | |||||
/** | |||||
* xml -> pojo | |||||
* | |||||
* @param clazz | |||||
* @param xml | |||||
* @return | |||||
*/ | |||||
@SuppressWarnings("unchecked") | |||||
public static <T> T fromXml(Class<T> clazz, String xml) { | |||||
T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(xml); | |||||
return object; | |||||
} | |||||
@SuppressWarnings("unchecked") | |||||
public static <T> T fromXml(Class<T> clazz, InputStream is) { | |||||
T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is); | |||||
return object; | |||||
} | |||||
/** | |||||
* pojo -> xml | |||||
* | |||||
* @param clazz | |||||
* @param object | |||||
* @return | |||||
*/ | |||||
public static <T> String toXml(Class<T> clazz, T object) { | |||||
return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object); | |||||
} | |||||
private static Map<Class, XStream> configXStreamInstance() { | |||||
Map<Class, XStream> map = new HashMap<Class, XStream>(); | |||||
map.put(WxCpXmlMessage.class, config_WxCpXmlMessage()); | |||||
map.put(WxCpXmlOutNewsMessage.class, config_WxCpXmlOutNewsMessage()); | |||||
map.put(WxCpXmlOutTextMessage.class, config_WxCpXmlOutTextMessage()); | |||||
map.put(WxCpXmlOutImageMessage.class, config_WxCpXmlOutImageMessage()); | |||||
map.put(WxCpXmlOutVideoMessage.class, config_WxCpXmlOutVideoMessage()); | |||||
map.put(WxCpXmlOutVoiceMessage.class, config_WxCpXmlOutVoiceMessage()); | |||||
return map; | |||||
} | |||||
private static XStream config_WxCpXmlMessage() { | |||||
XStream xstream = XStreamInitializer.getInstance(); | |||||
xstream.processAnnotations(WxCpXmlMessage.class); | |||||
xstream.processAnnotations(WxCpXmlMessage.ScanCodeInfo.class); | |||||
xstream.processAnnotations(WxCpXmlMessage.SendPicsInfo.class); | |||||
xstream.processAnnotations(WxCpXmlMessage.SendPicsInfo.Item.class); | |||||
xstream.processAnnotations(WxCpXmlMessage.SendLocationInfo.class); | |||||
return xstream; | |||||
} | |||||
private static XStream config_WxCpXmlOutImageMessage() { | |||||
XStream xstream = XStreamInitializer.getInstance(); | |||||
xstream.processAnnotations(WxCpXmlOutMessage.class); | |||||
xstream.processAnnotations(WxCpXmlOutImageMessage.class); | |||||
return xstream; | |||||
} | |||||
private static XStream config_WxCpXmlOutNewsMessage() { | |||||
XStream xstream = XStreamInitializer.getInstance(); | |||||
xstream.processAnnotations(WxCpXmlOutMessage.class); | |||||
xstream.processAnnotations(WxCpXmlOutNewsMessage.class); | |||||
xstream.processAnnotations(WxCpXmlOutNewsMessage.Item.class); | |||||
return xstream; | |||||
} | |||||
private static XStream config_WxCpXmlOutTextMessage() { | |||||
XStream xstream = XStreamInitializer.getInstance(); | |||||
xstream.processAnnotations(WxCpXmlOutMessage.class); | |||||
xstream.processAnnotations(WxCpXmlOutTextMessage.class); | |||||
return xstream; | |||||
} | |||||
private static XStream config_WxCpXmlOutVideoMessage() { | |||||
XStream xstream = XStreamInitializer.getInstance(); | |||||
xstream.processAnnotations(WxCpXmlOutMessage.class); | |||||
xstream.processAnnotations(WxCpXmlOutVideoMessage.class); | |||||
xstream.processAnnotations(WxCpXmlOutVideoMessage.Video.class); | |||||
return xstream; | |||||
} | |||||
private static XStream config_WxCpXmlOutVoiceMessage() { | |||||
XStream xstream = XStreamInitializer.getInstance(); | |||||
xstream.processAnnotations(WxCpXmlOutMessage.class); | |||||
xstream.processAnnotations(WxCpXmlOutVoiceMessage.class); | |||||
return xstream; | |||||
} | |||||
} |
@@ -1,93 +0,0 @@ | |||||
package me.chanjar.weixin.cp.util.xml; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.io.StringReader; | |||||
import java.io.StringWriter; | |||||
import java.io.Writer; | |||||
import javax.xml.bind.*; | |||||
import me.chanjar.weixin.cp.bean.*; | |||||
import org.xml.sax.InputSource; | |||||
import com.sun.xml.bind.marshaller.CharacterEscapeHandler; | |||||
public class XmlTransformer { | |||||
protected static final JAXBContext JAXB_CONTEXT = initJAXBContext(); | |||||
/** | |||||
* xml -> pojo | |||||
* | |||||
* @param clazz | |||||
* @param xml | |||||
* @return | |||||
* @throws JAXBException | |||||
*/ | |||||
@SuppressWarnings("unchecked") | |||||
public static <T> T fromXml(Class<T> clazz, String xml) throws JAXBException { | |||||
Unmarshaller um = JAXB_CONTEXT.createUnmarshaller(); | |||||
T object = (T) um.unmarshal(new StringReader(xml)); | |||||
return object; | |||||
} | |||||
@SuppressWarnings("unchecked") | |||||
public static <T> T fromXml(Class<T> clazz, InputStream is) throws JAXBException { | |||||
Unmarshaller um = JAXB_CONTEXT.createUnmarshaller(); | |||||
InputSource inputSource = new InputSource(is); | |||||
inputSource.setEncoding("utf-8"); | |||||
T object = (T) um.unmarshal(inputSource); | |||||
return object; | |||||
} | |||||
/** | |||||
* pojo -> xml | |||||
* | |||||
* @param clazz | |||||
* @param object | |||||
* @return | |||||
* @throws JAXBException | |||||
*/ | |||||
public static <T> String toXml(Class<T> clazz, T object) throws JAXBException { | |||||
StringWriter stringWriter = new StringWriter(); | |||||
toXml(clazz, object, stringWriter); | |||||
return stringWriter.getBuffer().toString(); | |||||
} | |||||
public static <T> void toXml(Class<T> clazz, T object, Writer writer) throws JAXBException { | |||||
Marshaller m = JAXB_CONTEXT.createMarshaller(); | |||||
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); | |||||
m.setProperty(CharacterEscapeHandler.class.getName(), CHAR_ESCAPE_HANDLER); | |||||
m.setProperty(Marshaller.JAXB_FRAGMENT, true); | |||||
m.marshal(object, writer); | |||||
} | |||||
protected static final CharacterEscapeHandler CHAR_ESCAPE_HANDLER = new CharacterUnescapeHandler(); | |||||
protected static class CharacterUnescapeHandler implements CharacterEscapeHandler { | |||||
public void escape(char[] ac, int i, int j, boolean flag, Writer writer) throws IOException { | |||||
writer.write(ac, i, j); | |||||
} | |||||
} | |||||
private static JAXBContext initJAXBContext() { | |||||
/* | |||||
* JAXBContext对象是线程安全的,根据官方文档的建议将对象作为全局实例 | |||||
* https://jaxb.java.net/guide/Performance_and_thread_safety.html | |||||
*/ | |||||
try { | |||||
return JAXBContext.newInstance( | |||||
WxCpXmlOutMessage.class, | |||||
WxCpXmlOutImageMessage.class, | |||||
WxCpXmlOutNewsMessage.class, | |||||
WxCpXmlOutTextMessage.class, | |||||
WxCpXmlOutVideoMessage.class, | |||||
WxCpXmlOutVoiceMessage.class, | |||||
WxCpXmlMessage.class); | |||||
} catch (JAXBException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
} |
@@ -1,23 +1,17 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import java.io.InputStream; | |||||
import javax.xml.bind.JAXBContext; | |||||
import javax.xml.bind.JAXBException; | |||||
import javax.xml.bind.Unmarshaller; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import com.google.inject.Binder; | import com.google.inject.Binder; | ||||
import com.google.inject.Module; | import com.google.inject.Module; | ||||
import org.xml.sax.InputSource; | |||||
import com.thoughtworks.xstream.XStream; | |||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||||
import java.io.InputStream; | |||||
public class ApiTestModule implements Module { | public class ApiTestModule implements Module { | ||||
@Override | @Override | ||||
public void configure(Binder binder) { | public void configure(Binder binder) { | ||||
try { | |||||
InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | ||||
WxXmlCpInMemoryConfigStorage config = fromXml(WxXmlCpInMemoryConfigStorage.class, is1); | WxXmlCpInMemoryConfigStorage config = fromXml(WxXmlCpInMemoryConfigStorage.class, is1); | ||||
WxCpServiceImpl wxService = new WxCpServiceImpl(); | WxCpServiceImpl wxService = new WxCpServiceImpl(); | ||||
@@ -25,21 +19,16 @@ public class ApiTestModule implements Module { | |||||
binder.bind(WxCpServiceImpl.class).toInstance(wxService); | binder.bind(WxCpServiceImpl.class).toInstance(wxService); | ||||
binder.bind(WxCpConfigStorage.class).toInstance(config); | binder.bind(WxCpConfigStorage.class).toInstance(config); | ||||
} catch (JAXBException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | } | ||||
public static <T> T fromXml(Class<T> clazz, InputStream is) throws JAXBException { | |||||
Unmarshaller um = JAXBContext.newInstance(clazz).createUnmarshaller(); | |||||
InputSource inputSource = new InputSource(is); | |||||
inputSource.setEncoding("utf-8"); | |||||
T object = (T) um.unmarshal(inputSource); | |||||
return object; | |||||
public static <T> T fromXml(Class<T> clazz, InputStream is) { | |||||
XStream xstream = XStreamInitializer.getInstance(); | |||||
xstream.alias("xml", clazz); | |||||
xstream.processAnnotations(clazz); | |||||
return (T) xstream.fromXML(is); | |||||
} | } | ||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public static class WxXmlCpInMemoryConfigStorage extends WxCpInMemoryConfigStorage { | public static class WxXmlCpInMemoryConfigStorage extends WxCpInMemoryConfigStorage { | ||||
protected String userId; | protected String userId; | ||||
@@ -1,14 +1,12 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import com.google.inject.Inject; | |||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import me.chanjar.weixin.common.util.StringUtils; | import me.chanjar.weixin.common.util.StringUtils; | ||||
import org.testng.Assert; | import org.testng.Assert; | ||||
import org.testng.annotations.Guice; | import org.testng.annotations.Guice; | ||||
import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import com.google.inject.Inject; | |||||
/** | /** | ||||
* 基础API测试 | * 基础API测试 | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
@@ -24,7 +22,7 @@ public class WxCpBaseAPITest { | |||||
public void testRefreshAccessToken() throws WxErrorException { | public void testRefreshAccessToken() throws WxErrorException { | ||||
WxCpConfigStorage configStorage = wxService.wxCpConfigStorage; | WxCpConfigStorage configStorage = wxService.wxCpConfigStorage; | ||||
String before = configStorage.getAccessToken(); | String before = configStorage.getAccessToken(); | ||||
wxService.accessTokenRefresh(); | |||||
wxService.getAccessToken(false); | |||||
String after = configStorage.getAccessToken(); | String after = configStorage.getAccessToken(); | ||||
@@ -0,0 +1,66 @@ | |||||
package me.chanjar.weixin.cp.api; | |||||
import me.chanjar.weixin.common.bean.result.WxError; | |||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||||
import org.testng.annotations.DataProvider; | |||||
import org.testng.annotations.Test; | |||||
import java.util.concurrent.ExecutionException; | |||||
import java.util.concurrent.ExecutorService; | |||||
import java.util.concurrent.Executors; | |||||
import java.util.concurrent.Future; | |||||
@Test | |||||
public class WxCpBusyRetryTest { | |||||
@DataProvider(name="getService") | |||||
public Object[][] getService() { | |||||
WxCpService service = new WxCpServiceImpl() { | |||||
@Override | |||||
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||||
WxError error = new WxError(); | |||||
error.setErrorCode(-1); | |||||
throw new WxErrorException(error); | |||||
} | |||||
}; | |||||
service.setMaxRetryTimes(3); | |||||
service.setRetrySleepMillis(500); | |||||
return new Object[][] { | |||||
new Object[] { service } | |||||
}; | |||||
} | |||||
@Test(dataProvider = "getService", expectedExceptions = RuntimeException.class) | |||||
public void testRetry(WxCpService service) throws WxErrorException { | |||||
service.execute(null, null, null); | |||||
} | |||||
@Test(dataProvider = "getService") | |||||
public void testRetryInThreadPool(final WxCpService service) throws InterruptedException, ExecutionException { | |||||
// 当线程池中的线程复用的时候,还是能保证相同的重试次数 | |||||
ExecutorService executorService = Executors.newFixedThreadPool(1); | |||||
Runnable runnable = new Runnable() { | |||||
@Override | |||||
public void run() { | |||||
try { | |||||
System.out.println("====================="); | |||||
System.out.println(Thread.currentThread().getName() + ": testRetry"); | |||||
service.execute(null, null, null); | |||||
} catch (WxErrorException e) { | |||||
throw new RuntimeException(e); | |||||
} catch (RuntimeException e) { | |||||
// OK | |||||
} | |||||
} | |||||
}; | |||||
Future<?> submit1 = executorService.submit(runnable); | |||||
Future<?> submit2 = executorService.submit(runnable); | |||||
submit1.get(); | |||||
submit2.get(); | |||||
} | |||||
} |
@@ -1,15 +1,13 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import java.util.List; | |||||
import com.google.inject.Inject; | |||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import me.chanjar.weixin.cp.bean.WxCpDepart; | import me.chanjar.weixin.cp.bean.WxCpDepart; | ||||
import org.testng.Assert; | import org.testng.Assert; | ||||
import org.testng.annotations.Guice; | import org.testng.annotations.Guice; | ||||
import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import com.google.inject.Inject; | |||||
import java.util.List; | |||||
/** | /** | ||||
* 测试部门接口 | * 测试部门接口 | ||||
@@ -1,20 +1,18 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
import com.google.inject.Inject; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import org.testng.Assert; | import org.testng.Assert; | ||||
import org.testng.annotations.DataProvider; | import org.testng.annotations.DataProvider; | ||||
import org.testng.annotations.Guice; | import org.testng.annotations.Guice; | ||||
import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import com.google.inject.Inject; | |||||
import java.io.IOException; | |||||
import java.io.InputStream; | |||||
import java.util.ArrayList; | |||||
import java.util.List; | |||||
/** | /** | ||||
* 测试多媒体文件上传下载 | * 测试多媒体文件上传下载 | ||||
@@ -1,14 +1,12 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import com.google.inject.Inject; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import me.chanjar.weixin.cp.bean.WxCpMessage; | import me.chanjar.weixin.cp.bean.WxCpMessage; | ||||
import org.testng.annotations.Guice; | import org.testng.annotations.Guice; | ||||
import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import com.google.inject.Inject; | |||||
/*** | /*** | ||||
* 测试发送消息 | * 测试发送消息 | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
@@ -1,14 +1,16 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import java.util.Map; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||||
import me.chanjar.weixin.common.session.WxSessionManager; | |||||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | ||||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | ||||
import org.testng.Assert; | import org.testng.Assert; | ||||
import org.testng.annotations.DataProvider; | import org.testng.annotations.DataProvider; | ||||
import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
import java.util.Map; | |||||
/** | /** | ||||
* 测试消息路由器 | * 测试消息路由器 | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
@@ -67,7 +69,8 @@ public class WxCpMessageRouterTest { | |||||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | final WxCpMessageRouter router = new WxCpMessageRouter(null); | ||||
router.rule().handler(new WxCpMessageHandler() { | router.rule().handler(new WxCpMessageHandler() { | ||||
@Override | @Override | ||||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService) { | |||||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService, | |||||
WxSessionManager sessionManager) { | |||||
return null; | return null; | ||||
} | } | ||||
}).end(); | }).end(); | ||||
@@ -149,11 +152,145 @@ public class WxCpMessageRouterTest { | |||||
} | } | ||||
@Override | @Override | ||||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService) { | |||||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService, | |||||
WxSessionManager sessionManager) { | |||||
sb.append(this.echoStr).append(','); | sb.append(this.echoStr).append(','); | ||||
return null; | return null; | ||||
} | } | ||||
} | } | ||||
@DataProvider | |||||
public Object[][] standardSessionManager() { | |||||
// 故意把session存活时间变短,清理更频繁 | |||||
StandardSessionManager ism = new StandardSessionManager(); | |||||
ism.setMaxInactiveInterval(1); | |||||
ism.setProcessExpiresFrequency(1); | |||||
ism.setBackgroundProcessorDelay(1); | |||||
return new Object[][] { | |||||
new Object[] { ism } | |||||
}; | |||||
} | |||||
@Test(dataProvider = "standardSessionManager") | |||||
public void testSessionClean1(StandardSessionManager ism) throws InterruptedException { | |||||
// 两个同步请求,看是否处理完毕后会被清理掉 | |||||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||||
router.setSessionManager(ism); | |||||
router | |||||
.rule().async(false).handler(new WxSessionMessageHandler()).next() | |||||
.rule().async(false).handler(new WxSessionMessageHandler()).end(); | |||||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||||
msg.setFromUserName("abc"); | |||||
router.route(msg); | |||||
Thread.sleep(2000l); | |||||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||||
} | |||||
@Test(dataProvider = "standardSessionManager") | |||||
public void testSessionClean2(StandardSessionManager ism) throws InterruptedException { | |||||
// 1个同步,1个异步请求,看是否处理完毕后会被清理掉 | |||||
{ | |||||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||||
router.setSessionManager(ism); | |||||
router | |||||
.rule().async(false).handler(new WxSessionMessageHandler()).next() | |||||
.rule().async(true).handler(new WxSessionMessageHandler()).end(); | |||||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||||
msg.setFromUserName("abc"); | |||||
router.route(msg); | |||||
Thread.sleep(2000l); | |||||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||||
} | |||||
{ | |||||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||||
router.setSessionManager(ism); | |||||
router | |||||
.rule().async(true).handler(new WxSessionMessageHandler()).next() | |||||
.rule().async(false).handler(new WxSessionMessageHandler()).end(); | |||||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||||
msg.setFromUserName("abc"); | |||||
router.route(msg); | |||||
Thread.sleep(2000l); | |||||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||||
} | |||||
} | |||||
@Test(dataProvider = "standardSessionManager") | |||||
public void testSessionClean3(StandardSessionManager ism) throws InterruptedException { | |||||
// 2个异步请求,看是否处理完毕后会被清理掉 | |||||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||||
router.setSessionManager(ism); | |||||
router | |||||
.rule().async(true).handler(new WxSessionMessageHandler()).next() | |||||
.rule().async(true).handler(new WxSessionMessageHandler()).end(); | |||||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||||
msg.setFromUserName("abc"); | |||||
router.route(msg); | |||||
Thread.sleep(2000l); | |||||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||||
} | |||||
@Test(dataProvider = "standardSessionManager") | |||||
public void testSessionClean4(StandardSessionManager ism) throws InterruptedException { | |||||
// 一个同步请求,看是否处理完毕后会被清理掉 | |||||
{ | |||||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||||
router.setSessionManager(ism); | |||||
router | |||||
.rule().async(false).handler(new WxSessionMessageHandler()).end(); | |||||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||||
msg.setFromUserName("abc"); | |||||
router.route(msg); | |||||
Thread.sleep(2000l); | |||||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||||
} | |||||
{ | |||||
final WxCpMessageRouter router = new WxCpMessageRouter(null); | |||||
router.setSessionManager(ism); | |||||
router | |||||
.rule().async(true).handler(new WxSessionMessageHandler()).end(); | |||||
WxCpXmlMessage msg = new WxCpXmlMessage(); | |||||
msg.setFromUserName("abc"); | |||||
router.route(msg); | |||||
Thread.sleep(2000l); | |||||
Assert.assertEquals(ism.getActiveSessions(), 0); | |||||
} | |||||
} | |||||
public static class WxSessionMessageHandler implements WxCpMessageHandler { | |||||
@Override | |||||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService wxCpService, | |||||
WxSessionManager sessionManager) { | |||||
sessionManager.getSession(wxMessage.getFromUserName()); | |||||
return null; | |||||
} | |||||
} | |||||
} | } |
@@ -1,9 +1,9 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import com.google.inject.Inject; | import com.google.inject.Inject; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import me.chanjar.weixin.cp.bean.WxCpDepart; | import me.chanjar.weixin.cp.bean.WxCpDepart; | ||||
import me.chanjar.weixin.cp.bean.WxCpUser; | import me.chanjar.weixin.cp.bean.WxCpUser; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import org.testng.Assert; | import org.testng.Assert; | ||||
import org.testng.annotations.Guice; | import org.testng.annotations.Guice; | ||||
import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
@@ -26,30 +26,30 @@ public class WxCpUserAPITest { | |||||
public void testUserCreate() throws WxErrorException { | public void testUserCreate() throws WxErrorException { | ||||
WxCpUser user = new WxCpUser(); | WxCpUser user = new WxCpUser(); | ||||
user.setUserId("xiaohe.yang"); | |||||
user.setName("杨宝"); | |||||
user.setUserId("some.woman"); | |||||
user.setName("Some Woman"); | |||||
user.setDepartIds(new Integer[] { 9, 8 }); | user.setDepartIds(new Integer[] { 9, 8 }); | ||||
user.setEmail("yangxiaohe@ddd.com"); | |||||
user.setEmail("none@none.com"); | |||||
user.setGender("女"); | user.setGender("女"); | ||||
user.setMobile("13564684979"); | |||||
user.setPosition("老婆"); | |||||
user.setMobile("13560084979"); | |||||
user.setPosition("woman"); | |||||
user.setTel("3300393"); | user.setTel("3300393"); | ||||
user.addExtAttr("爱好", "老公"); | |||||
user.addExtAttr("爱好", "table"); | |||||
wxCpService.userCreate(user); | wxCpService.userCreate(user); | ||||
} | } | ||||
@Test(dependsOnMethods = "testUserCreate") | @Test(dependsOnMethods = "testUserCreate") | ||||
public void testUserUpdate() throws WxErrorException { | public void testUserUpdate() throws WxErrorException { | ||||
WxCpUser user = new WxCpUser(); | WxCpUser user = new WxCpUser(); | ||||
user.setUserId("xiaohe.yang"); | |||||
user.setName("杨宝"); | |||||
user.addExtAttr("爱好", "老公2"); | |||||
user.setUserId("some.woman"); | |||||
user.setName("Some Woman"); | |||||
user.addExtAttr("爱好", "table2"); | |||||
wxCpService.userUpdate(user); | wxCpService.userUpdate(user); | ||||
} | } | ||||
@Test(dependsOnMethods = "testUserUpdate") | @Test(dependsOnMethods = "testUserUpdate") | ||||
public void testUserGet() throws WxErrorException { | public void testUserGet() throws WxErrorException { | ||||
WxCpUser user = wxCpService.userGet("xiaohe.yang"); | |||||
WxCpUser user = wxCpService.userGet("some.woman"); | |||||
Assert.assertNotNull(user); | Assert.assertNotNull(user); | ||||
} | } | ||||
@@ -61,6 +61,6 @@ public class WxCpUserAPITest { | |||||
@Test(dependsOnMethods = "testDepartGetUsers") | @Test(dependsOnMethods = "testDepartGetUsers") | ||||
public void testUserDelete() throws WxErrorException { | public void testUserDelete() throws WxErrorException { | ||||
wxCpService.userDelete("xiaohe.yang"); | |||||
wxCpService.userDelete("some.woman"); | |||||
} | } | ||||
} | } |
@@ -1,19 +1,15 @@ | |||||
package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
import javax.xml.bind.JAXBException; | |||||
import com.google.inject.Inject; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.bean.WxMenu; | import me.chanjar.weixin.common.bean.WxMenu; | ||||
import me.chanjar.weixin.common.bean.WxMenu.WxMenuButton; | |||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import org.testng.Assert; | import org.testng.Assert; | ||||
import org.testng.annotations.DataProvider; | import org.testng.annotations.DataProvider; | ||||
import org.testng.annotations.Guice; | import org.testng.annotations.Guice; | ||||
import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
import com.google.inject.Inject; | |||||
import me.chanjar.weixin.common.bean.WxMenu.WxMenuButton; | |||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
/** | /** | ||||
* 测试菜单 | * 测试菜单 | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
@@ -42,7 +38,7 @@ public class WxMenuAPITest { | |||||
} | } | ||||
@DataProvider(name="menu") | @DataProvider(name="menu") | ||||
public Object[][] getMenu() throws JAXBException { | |||||
public Object[][] getMenu() { | |||||
WxMenu menu = new WxMenu(); | WxMenu menu = new WxMenu(); | ||||
WxMenuButton button1 = new WxMenuButton(); | WxMenuButton button1 = new WxMenuButton(); | ||||
button1.setType(WxConsts.BUTTON_CLICK); | button1.setType(WxConsts.BUTTON_CLICK); | ||||
@@ -1,11 +1,10 @@ | |||||
package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.cp.bean.WxCpMessage.WxArticle; | |||||
import org.testng.Assert; | import org.testng.Assert; | ||||
import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
import me.chanjar.weixin.cp.bean.WxCpMessage.WxArticle; | |||||
@Test | @Test | ||||
public class WxCpMessageTest { | public class WxCpMessageTest { | ||||
@@ -1,35 +1,29 @@ | |||||
package me.chanjar.weixin.cp.demo; | package me.chanjar.weixin.cp.demo; | ||||
import com.thoughtworks.xstream.XStream; | |||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||||
import me.chanjar.weixin.cp.api.WxCpInMemoryConfigStorage; | import me.chanjar.weixin.cp.api.WxCpInMemoryConfigStorage; | ||||
import org.xml.sax.InputSource; | |||||
import javax.xml.bind.JAXBContext; | |||||
import javax.xml.bind.JAXBException; | |||||
import javax.xml.bind.Unmarshaller; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import java.io.InputStream; | import java.io.InputStream; | ||||
/** | /** | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
*/ | */ | ||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage { | class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage { | ||||
@Override | @Override | ||||
public String toString() { | public String toString() { | ||||
return "SimpleWxConfigProvider [appidOrCorpid=" + corpId + ", corpSecret=" + corpSecret + ", accessToken=" + accessToken | return "SimpleWxConfigProvider [appidOrCorpid=" + corpId + ", corpSecret=" + corpSecret + ", accessToken=" + accessToken | ||||
+ ", expiresIn=" + expiresIn + ", token=" + token + ", aesKey=" + aesKey + "]"; | |||||
+ ", expiresTime=" + expiresTime + ", token=" + token + ", aesKey=" + aesKey + "]"; | |||||
} | } | ||||
public static WxCpDemoInMemoryConfigStorage fromXml(InputStream is) throws JAXBException { | |||||
Unmarshaller um = JAXBContext.newInstance(WxCpDemoInMemoryConfigStorage.class).createUnmarshaller(); | |||||
InputSource inputSource = new InputSource(is); | |||||
inputSource.setEncoding("utf-8"); | |||||
return (WxCpDemoInMemoryConfigStorage) um.unmarshal(inputSource); | |||||
public static WxCpDemoInMemoryConfigStorage fromXml(InputStream is) { | |||||
XStream xstream = XStreamInitializer.getInstance(); | |||||
xstream.processAnnotations(WxCpDemoInMemoryConfigStorage.class); | |||||
return (WxCpDemoInMemoryConfigStorage) xstream.fromXML(is); | |||||
} | } | ||||
} | } |
@@ -1,5 +1,6 @@ | |||||
package me.chanjar.weixin.cp.demo; | package me.chanjar.weixin.cp.demo; | ||||
import me.chanjar.weixin.common.session.WxSessionManager; | |||||
import me.chanjar.weixin.cp.api.*; | import me.chanjar.weixin.cp.api.*; | ||||
import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlMessage; | ||||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | ||||
@@ -8,7 +9,6 @@ import org.eclipse.jetty.server.Server; | |||||
import org.eclipse.jetty.servlet.ServletHandler; | import org.eclipse.jetty.servlet.ServletHandler; | ||||
import org.eclipse.jetty.servlet.ServletHolder; | import org.eclipse.jetty.servlet.ServletHolder; | ||||
import javax.xml.bind.JAXBException; | |||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.util.Map; | import java.util.Map; | ||||
@@ -37,7 +37,6 @@ public class WxCpDemoServer { | |||||
} | } | ||||
private static void initWeixin() { | private static void initWeixin() { | ||||
try { | |||||
InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); | ||||
WxCpDemoInMemoryConfigStorage config = WxCpDemoInMemoryConfigStorage.fromXml(is1); | WxCpDemoInMemoryConfigStorage config = WxCpDemoInMemoryConfigStorage.fromXml(is1); | ||||
@@ -48,7 +47,7 @@ public class WxCpDemoServer { | |||||
WxCpMessageHandler handler = new WxCpMessageHandler() { | WxCpMessageHandler handler = new WxCpMessageHandler() { | ||||
@Override | @Override | ||||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, | ||||
WxCpService wxCpService) { | |||||
WxCpService wxCpService, WxSessionManager sessionManager) { | |||||
WxCpXmlOutTextMessage m = WxCpXmlOutMessage | WxCpXmlOutTextMessage m = WxCpXmlOutMessage | ||||
.TEXT() | .TEXT() | ||||
.content("测试加密消息") | .content("测试加密消息") | ||||
@@ -62,7 +61,7 @@ public class WxCpDemoServer { | |||||
WxCpMessageHandler oauth2handler = new WxCpMessageHandler() { | WxCpMessageHandler oauth2handler = new WxCpMessageHandler() { | ||||
@Override | @Override | ||||
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, | public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, | ||||
WxCpService wxCpService) { | |||||
WxCpService wxCpService, WxSessionManager sessionManager) { | |||||
String href = "<a href=\"" + wxCpService.oauth2buildAuthorizationUrl(null) | String href = "<a href=\"" + wxCpService.oauth2buildAuthorizationUrl(null) | ||||
+ "\">测试oauth2</a>"; | + "\">测试oauth2</a>"; | ||||
return WxCpXmlOutMessage | return WxCpXmlOutMessage | ||||
@@ -87,8 +86,5 @@ public class WxCpDemoServer { | |||||
.end() | .end() | ||||
; | ; | ||||
} catch (JAXBException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | } | ||||
} | } |
@@ -1,20 +1,18 @@ | |||||
package me.chanjar.weixin.cp.demo; | package me.chanjar.weixin.cp.demo; | ||||
import me.chanjar.weixin.common.util.StringUtils; | import me.chanjar.weixin.common.util.StringUtils; | ||||
import me.chanjar.weixin.cp.api.*; | |||||
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.WxCpXmlMessage; | ||||
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; | ||||
import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage; | |||||
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | ||||
import javax.servlet.ServletException; | import javax.servlet.ServletException; | ||||
import javax.servlet.http.HttpServlet; | import javax.servlet.http.HttpServlet; | ||||
import javax.servlet.http.HttpServletRequest; | import javax.servlet.http.HttpServletRequest; | ||||
import javax.servlet.http.HttpServletResponse; | import javax.servlet.http.HttpServletResponse; | ||||
import javax.xml.bind.JAXBException; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | |||||
import java.util.Map; | |||||
/** | /** | ||||
* @author Daniel Qian | * @author Daniel Qian | ||||
@@ -10,9 +10,6 @@ import javax.servlet.http.HttpServletResponse; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.Arrays; | import java.util.Arrays; | ||||
/** | |||||
* Created by qianjia on 14/11/28. | |||||
*/ | |||||
public class WxCpOAuth2Servlet extends HttpServlet { | public class WxCpOAuth2Servlet extends HttpServlet { | ||||
protected WxCpService wxCpService; | protected WxCpService wxCpService; | ||||
@@ -0,0 +1,16 @@ | |||||
<configuration> | |||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> | |||||
<!-- encoders are assigned the type | |||||
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default --> | |||||
<encoder> | |||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> | |||||
</encoder> | |||||
</appender> | |||||
<root level="info"> | |||||
<appender-ref ref="STDOUT" /> | |||||
</root> | |||||
<logger name="me.chanjar.weixin.cp" level="debug" /> | |||||
</configuration> |
@@ -5,7 +5,7 @@ | |||||
<token>企业号应用Token</token> | <token>企业号应用Token</token> | ||||
<aesKey>企业号应用EncodingAESKey</aesKey> | <aesKey>企业号应用EncodingAESKey</aesKey> | ||||
<accessToken>可以不填写</accessToken> | <accessToken>可以不填写</accessToken> | ||||
<expiresIn>可以不填写</expiresIn> | |||||
<expiresTime>可以不填写</expiresTime> | |||||
<userId>企业号通讯录里的某个userid</userId> | <userId>企业号通讯录里的某个userid</userId> | ||||
<departmentId>企业号通讯录的某个部门id</departmentId> | <departmentId>企业号通讯录的某个部门id</departmentId> | ||||
<tagId>企业号通讯录里的某个tagid</tagId> | <tagId>企业号通讯录里的某个tagid</tagId> | ||||
@@ -1,28 +1,29 @@ | |||||
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > | <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" > | ||||
<suite name="Weixin-java-tool-suite" verbose="1"> | <suite name="Weixin-java-tool-suite" verbose="1"> | ||||
<test name="API_Test"> | |||||
<classes> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpBaseAPITest" /> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpMessageAPITest" /> | |||||
<class name="me.chanjar.weixin.cp.api.WxMenuAPITest" /> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpDepartAPITest" /> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpMediaAPITest" /> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpMessageRouterTest" /> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpTagAPITest" /> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpUserAPITest" /> | |||||
</classes> | |||||
</test> | |||||
<test name="API_Test"> | |||||
<classes> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpBusyRetryTest"/> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpBaseAPITest"/> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpMessageAPITest"/> | |||||
<class name="me.chanjar.weixin.cp.api.WxMenuAPITest"/> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpDepartAPITest"/> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpMediaAPITest"/> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpMessageRouterTest"/> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpTagAPITest"/> | |||||
<class name="me.chanjar.weixin.cp.api.WxCpUserAPITest"/> | |||||
</classes> | |||||
</test> | |||||
<test name="Bean_Test"> | |||||
<classes> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpMessageTest" /> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlMessageTest" /> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessageTest" /> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessageTest" /> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessageTest" /> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessageTest" /> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessageTest" /> | |||||
</classes> | |||||
</test> | |||||
<test name="Bean_Test"> | |||||
<classes> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpMessageTest"/> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlMessageTest"/> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessageTest"/> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessageTest"/> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessageTest"/> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessageTest"/> | |||||
<class name="me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessageTest"/> | |||||
</classes> | |||||
</test> | |||||
</suite> | </suite> |
@@ -6,7 +6,7 @@ | |||||
<parent> | <parent> | ||||
<groupId>me.chanjar</groupId> | <groupId>me.chanjar</groupId> | ||||
<artifactId>weixin-java-parent</artifactId> | <artifactId>weixin-java-parent</artifactId> | ||||
<version>1.0.6</version> | |||||
<version>1.1.0-SNAPSHOT</version> | |||||
</parent> | </parent> | ||||
<artifactId>weixin-java-mp</artifactId> | <artifactId>weixin-java-mp</artifactId> | ||||
<name>WeiXin Java Tools - MP</name> | <name>WeiXin Java Tools - MP</name> | ||||
@@ -1,72 +0,0 @@ | |||||
<?xml version="1.0"?> | |||||
<project | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | |||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<parent> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-parent</artifactId> | |||||
<version>1.0.5-SNAPSHOT</version> | |||||
</parent> | |||||
<artifactId>weixin-java-mp</artifactId> | |||||
<name>WeiXin Java Tools - MP</name> | |||||
<description>微信公众号Java SDK</description> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>me.chanjar</groupId> | |||||
<artifactId>weixin-java-common</artifactId> | |||||
<version>${project.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.testng</groupId> | |||||
<artifactId>testng</artifactId> | |||||
<version>6.8.7</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.mockito</groupId> | |||||
<artifactId>mockito-all</artifactId> | |||||
<version>1.9.5</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.google.inject</groupId> | |||||
<artifactId>guice</artifactId> | |||||
<version>3.0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-server</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.eclipse.jetty</groupId> | |||||
<artifactId>jetty-servlet</artifactId> | |||||
<version>9.3.0.M0</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
<build> | |||||
<plugins> | |||||
<plugin> | |||||
<groupId>org.apache.maven.plugins</groupId> | |||||
<artifactId>maven-surefire-plugin</artifactId> | |||||
<configuration> | |||||
<suiteXmlFiles> | |||||
<suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> | |||||
</suiteXmlFiles> | |||||
</configuration> | |||||
</plugin> | |||||
</plugins> | |||||
</build> | |||||
</project> |
@@ -9,21 +9,52 @@ import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
*/ | */ | ||||
public interface WxMpConfigStorage { | public interface WxMpConfigStorage { | ||||
public String getAccessToken(); | |||||
public boolean isAccessTokenExpired(); | |||||
/** | |||||
* 强制将access token过期掉 | |||||
*/ | |||||
public void expireAccessToken(); | |||||
/** | |||||
* 应该是线程安全的 | |||||
* @param accessToken | |||||
*/ | |||||
public void updateAccessToken(WxAccessToken accessToken); | public void updateAccessToken(WxAccessToken accessToken); | ||||
/** | |||||
* 应该是线程安全的 | |||||
* @param accessToken | |||||
* @param expiresIn | |||||
*/ | |||||
public void updateAccessToken(String accessToken, int expiresIn); | public void updateAccessToken(String accessToken, int expiresIn); | ||||
public String getAccessToken(); | |||||
public String getJsapiTicket(); | |||||
public boolean isJsapiTicketExpired(); | |||||
/** | |||||
* 强制将jsapi ticket过期掉 | |||||
*/ | |||||
public void expireJsapiTicket(); | |||||
/** | |||||
* 应该是线程安全的 | |||||
* @param jsapiTicket | |||||
*/ | |||||
public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | |||||
public String getAppId(); | public String getAppId(); | ||||
public String getSecret(); | public String getSecret(); | ||||
public String getToken(); | public String getToken(); | ||||
public String getAesKey(); | public String getAesKey(); | ||||
public int getExpiresIn(); | |||||
public long getExpiresTime(); | |||||
public String getOauth2redirectUri(); | public String getOauth2redirectUri(); | ||||
@@ -33,6 +64,7 @@ public interface WxMpConfigStorage { | |||||
public String getHttp_proxy_username(); | public String getHttp_proxy_username(); | ||||
public String getHttp_proxy_password(); | public String getHttp_proxy_password(); | ||||
} | } |
@@ -9,12 +9,14 @@ import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
*/ | */ | ||||
public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | ||||
private static final long l = 7000 * 1000l; | |||||
protected String appId; | protected String appId; | ||||
protected String secret; | protected String secret; | ||||
protected String token; | protected String token; | ||||
protected String accessToken; | protected String accessToken; | ||||
protected String aesKey; | protected String aesKey; | ||||
protected int expiresIn; | |||||
protected long expiresTime; | |||||
protected String oauth2redirectUri; | protected String oauth2redirectUri; | ||||
@@ -23,17 +25,46 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
protected String http_proxy_username; | protected String http_proxy_username; | ||||
protected String http_proxy_password; | protected String http_proxy_password; | ||||
public void updateAccessToken(WxAccessToken accessToken) { | |||||
protected String jsapiTicket; | |||||
protected long jsapiTicketExpiresTime; | |||||
public String getAccessToken() { | |||||
return this.accessToken; | |||||
} | |||||
public boolean isAccessTokenExpired() { | |||||
return System.currentTimeMillis() > this.expiresTime; | |||||
} | |||||
public synchronized void updateAccessToken(WxAccessToken accessToken) { | |||||
updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | ||||
} | } | ||||
public void updateAccessToken(String accessToken, int expiresIn) { | |||||
public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { | |||||
this.accessToken = accessToken; | this.accessToken = accessToken; | ||||
this.expiresIn = expiresIn; | |||||
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||||
} | } | ||||
public String getAccessToken() { | |||||
return this.accessToken; | |||||
public void expireAccessToken() { | |||||
this.expiresTime = 0; | |||||
} | |||||
public String getJsapiTicket() { | |||||
return jsapiTicket; | |||||
} | |||||
public boolean isJsapiTicketExpired() { | |||||
return System.currentTimeMillis() > this.jsapiTicketExpiresTime; | |||||
} | |||||
public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||||
this.jsapiTicket = jsapiTicket; | |||||
// 预留200秒的时间 | |||||
this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; | |||||
} | |||||
public void expireJsapiTicket() { | |||||
this.jsapiTicketExpiresTime = 0; | |||||
} | } | ||||
public String getAppId() { | public String getAppId() { | ||||
@@ -48,8 +79,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
return this.token; | return this.token; | ||||
} | } | ||||
public int getExpiresIn() { | |||||
return this.expiresIn; | |||||
public long getExpiresTime() { | |||||
return this.expiresTime; | |||||
} | } | ||||
public void setAppId(String appId) { | public void setAppId(String appId) { | ||||
@@ -76,8 +107,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
this.accessToken = accessToken; | this.accessToken = accessToken; | ||||
} | } | ||||
public void setExpiresIn(int expiresIn) { | |||||
this.expiresIn = expiresIn; | |||||
public void setExpiresTime(long expiresTime) { | |||||
this.expiresTime = expiresTime; | |||||
} | } | ||||
@Override | @Override | ||||
@@ -129,11 +160,13 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { | |||||
", token='" + token + '\'' + | ", token='" + token + '\'' + | ||||
", accessToken='" + accessToken + '\'' + | ", accessToken='" + accessToken + '\'' + | ||||
", aesKey='" + aesKey + '\'' + | ", aesKey='" + aesKey + '\'' + | ||||
", expiresIn=" + expiresIn + | |||||
", expiresTime=" + expiresTime + | |||||
", http_proxy_host='" + http_proxy_host + '\'' + | ", http_proxy_host='" + http_proxy_host + '\'' + | ||||
", http_proxy_port=" + http_proxy_port + | ", http_proxy_port=" + http_proxy_port + | ||||
", http_proxy_username='" + http_proxy_username + '\'' + | ", http_proxy_username='" + http_proxy_username + '\'' + | ||||
", http_proxy_password='" + http_proxy_password + '\'' + | ", http_proxy_password='" + http_proxy_password + '\'' + | ||||
", jsapiTicket='" + jsapiTicket + '\'' + | |||||
", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' + | |||||
'}'; | '}'; | ||||
} | } | ||||
@@ -1,5 +1,6 @@ | |||||
package me.chanjar.weixin.mp.api; | package me.chanjar.weixin.mp.api; | ||||
import me.chanjar.weixin.common.session.WxSessionManager; | |||||
import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | ||||
import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | ||||
@@ -7,18 +8,21 @@ import java.util.Map; | |||||
/** | /** | ||||
* 处理微信推送消息的处理器接口 | * 处理微信推送消息的处理器接口 | ||||
* @author chanjarster | |||||
* | * | ||||
* @author Daniel Qian | |||||
*/ | */ | ||||
public interface WxMpMessageHandler { | public interface WxMpMessageHandler { | ||||
/** | /** | ||||
* | |||||
* @param wxMessage | * @param wxMessage | ||||
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 | |||||
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 | |||||
* @param wxMpService | * @param wxMpService | ||||
* @param sessionManager | |||||
* @return xml格式的消息,如果在异步规则里处理的话,可以返回null | * @return xml格式的消息,如果在异步规则里处理的话,可以返回null | ||||
*/ | */ | ||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService); | |||||
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, | |||||
Map<String, Object> context, | |||||
WxMpService wxMpService, | |||||
WxSessionManager sessionManager); | |||||
} | } |
@@ -1,23 +1,29 @@ | |||||
package me.chanjar.weixin.mp.api; | package me.chanjar.weixin.mp.api; | ||||
import me.chanjar.weixin.common.session.WxSessionManager; | |||||
import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | ||||
import java.util.Map; | import java.util.Map; | ||||
/** | /** | ||||
* 微信消息拦截器,可以用来做验证 | * 微信消息拦截器,可以用来做验证 | ||||
* @author qianjia | |||||
* | * | ||||
* @author Daniel Qian | |||||
*/ | */ | ||||
public interface WxMpMessageInterceptor { | public interface WxMpMessageInterceptor { | ||||
/** | /** | ||||
* 拦截微信消息 | * 拦截微信消息 | ||||
* | |||||
* @param wxMessage | * @param wxMessage | ||||
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 | |||||
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个 | |||||
* @param wxMpService | * @param wxMpService | ||||
* @return true代表OK,false代表不OK | |||||
* @param sessionManager | |||||
* @return true代表OK,false代表不OK | |||||
*/ | */ | ||||
public boolean intercept(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService); | |||||
public boolean intercept(WxMpXmlMessage wxMessage, | |||||
Map<String, Object> context, | |||||
WxMpService wxMpService, | |||||
WxSessionManager sessionManager); | |||||
} | } |
@@ -1,14 +1,22 @@ | |||||
package me.chanjar.weixin.mp.api; | package me.chanjar.weixin.mp.api; | ||||
import me.chanjar.weixin.common.api.WxConsts; | |||||
import me.chanjar.weixin.common.session.*; | |||||
import me.chanjar.weixin.common.util.WxMessageDuplicateChecker; | |||||
import me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker; | |||||
import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | import me.chanjar.weixin.mp.bean.WxMpXmlMessage; | ||||
import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; | ||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.concurrent.ExecutionException; | |||||
import java.util.concurrent.ExecutorService; | import java.util.concurrent.ExecutorService; | ||||
import java.util.concurrent.Executors; | import java.util.concurrent.Executors; | ||||
import java.util.concurrent.Future; | |||||
import java.util.regex.Pattern; | import java.util.regex.Pattern; | ||||
/** | /** | ||||
@@ -36,27 +44,61 @@ import java.util.regex.Pattern; | |||||
* router.route(message); | * router.route(message); | ||||
* | * | ||||
* </pre> | * </pre> | ||||
* @author qianjia | |||||
* | |||||
*/ | */ | ||||
public class WxMpMessageRouter { | public class WxMpMessageRouter { | ||||
private static final int DEFAULT_THREAD_POOL_SIZE = 20; | |||||
protected final Logger log = LoggerFactory.getLogger(WxMpMessageRouter.class); | |||||
private final List<Rule> rules = new ArrayList<Rule>(); | |||||
private static final int DEFAULT_THREAD_POOL_SIZE = 100; | |||||
private final ExecutorService executorService; | |||||
private final List<Rule> rules = new ArrayList<Rule>(); | |||||
private final WxMpService wxMpService; | private final WxMpService wxMpService; | ||||
private ExecutorService executorService; | |||||
private WxMessageDuplicateChecker messageDuplicateChecker; | |||||
private WxSessionManager sessionManager; | |||||
public WxMpMessageRouter(WxMpService wxMpService) { | public WxMpMessageRouter(WxMpService wxMpService) { | ||||
this.wxMpService = wxMpService; | this.wxMpService = wxMpService; | ||||
this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | ||||
this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); | |||||
this.sessionManager = new StandardSessionManager(); | |||||
} | } | ||||
public WxMpMessageRouter(WxMpService wxMpService, int threadPoolSize) { | |||||
this.wxMpService = wxMpService; | |||||
this.executorService = Executors.newFixedThreadPool(threadPoolSize); | |||||
/** | |||||
* <pre> | |||||
* 设置自定义的 {@link ExecutorService} | |||||
* 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100) | |||||
* </pre> | |||||
* @param executorService | |||||
*/ | |||||
public void setExecutorService(ExecutorService executorService) { | |||||
this.executorService = executorService; | |||||
} | |||||
/** | |||||
* <pre> | |||||
* 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker} | |||||
* 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker} | |||||
* </pre> | |||||
* @param messageDuplicateChecker | |||||
*/ | |||||
public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) { | |||||
this.messageDuplicateChecker = messageDuplicateChecker; | |||||
} | |||||
/** | |||||
* <pre> | |||||
* 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager} | |||||
* 如果不调用该方法,默认使用 {@linke SessionManagerImpl} | |||||
* </pre> | |||||
* @param sessionManager | |||||
*/ | |||||
public void setSessionManager(WxSessionManager sessionManager) { | |||||
this.sessionManager = sessionManager; | |||||
} | } | ||||
/** | /** | ||||
@@ -64,7 +106,7 @@ public class WxMpMessageRouter { | |||||
* @return | * @return | ||||
*/ | */ | ||||
public Rule rule() { | public Rule rule() { | ||||
return new Rule(this, wxMpService); | |||||
return new Rule(this, wxMpService, sessionManager); | |||||
} | } | ||||
/** | /** | ||||
@@ -72,6 +114,11 @@ public class WxMpMessageRouter { | |||||
* @param wxMessage | * @param wxMessage | ||||
*/ | */ | ||||
public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) { | public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) { | ||||
if (isDuplicateMessage(wxMessage)) { | |||||
// 如果是重复消息,那么就不做处理 | |||||
return null; | |||||
} | |||||
final List<Rule> matchRules = new ArrayList<Rule>(); | final List<Rule> matchRules = new ArrayList<Rule>(); | ||||
// 收集匹配的规则 | // 收集匹配的规则 | ||||
for (final Rule rule : rules) { | for (final Rule rule : rules) { | ||||
@@ -82,33 +129,90 @@ public class WxMpMessageRouter { | |||||
} | } | ||||
} | } | ||||
} | } | ||||
if (matchRules.size() == 0) { | if (matchRules.size() == 0) { | ||||
return null; | return null; | ||||
} | } | ||||
WxMpXmlOutMessage res = null; | WxMpXmlOutMessage res = null; | ||||
final List<Future> futures = new ArrayList<Future>(); | |||||
for (final Rule rule : matchRules) { | for (final Rule rule : matchRules) { | ||||
// 返回最后一个非异步的rule的执行结果 | // 返回最后一个非异步的rule的执行结果 | ||||
if(rule.async) { | if(rule.async) { | ||||
executorService.submit(new Runnable() { | |||||
public void run() { | |||||
rule.service(wxMessage); | |||||
} | |||||
}); | |||||
futures.add( | |||||
executorService.submit(new Runnable() { | |||||
public void run() { | |||||
rule.service(wxMessage); | |||||
} | |||||
}) | |||||
); | |||||
} else { | } else { | ||||
res = rule.service(wxMessage); | res = rule.service(wxMessage); | ||||
// 在同步操作结束,session访问结束 | |||||
log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUserName()); | |||||
sessionEndAccess(wxMessage); | |||||
} | } | ||||
} | } | ||||
if (futures.size() > 0) { | |||||
executorService.submit(new Runnable() { | |||||
@Override | |||||
public void run() { | |||||
for (Future future : futures) { | |||||
try { | |||||
future.get(); | |||||
log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUserName()); | |||||
// 异步操作结束,session访问结束 | |||||
sessionEndAccess(wxMessage); | |||||
} catch (InterruptedException e) { | |||||
log.error("Error happened when wait task finish", e); | |||||
} catch (ExecutionException e) { | |||||
log.error("Error happened when wait task finish", e); | |||||
} | |||||
} | |||||
} | |||||
}); | |||||
} | |||||
return res; | return res; | ||||
} | } | ||||
protected boolean isDuplicateMessage(WxMpXmlMessage wxMessage) { | |||||
String messageId = ""; | |||||
if (wxMessage.getMsgId() == null) { | |||||
messageId = wxMessage.getFromUserName() + "-" + String.valueOf(wxMessage.getCreateTime()); | |||||
} else { | |||||
messageId = String.valueOf(wxMessage.getMsgId()); | |||||
} | |||||
if (messageDuplicateChecker.isDuplicate(messageId)) { | |||||
return true; | |||||
} | |||||
return false; | |||||
} | |||||
/** | |||||
* 对session的访问结束 | |||||
* @param wxMessage | |||||
*/ | |||||
protected void sessionEndAccess(WxMpXmlMessage wxMessage) { | |||||
InternalSession session = ((InternalSessionManager)sessionManager).findSession(wxMessage.getFromUserName()); | |||||
if (session != null) { | |||||
session.endAccess(); | |||||
} | |||||
} | |||||
public static class Rule { | public static class Rule { | ||||
private final WxMpMessageRouter routerBuilder; | private final WxMpMessageRouter routerBuilder; | ||||
private final WxMpService wxMpService; | private final WxMpService wxMpService; | ||||
private final WxSessionManager sessionManager; | |||||
private boolean async = true; | private boolean async = true; | ||||
private String fromUser; | private String fromUser; | ||||
@@ -128,10 +232,11 @@ public class WxMpMessageRouter { | |||||
private List<WxMpMessageHandler> handlers = new ArrayList<WxMpMessageHandler>(); | private List<WxMpMessageHandler> handlers = new ArrayList<WxMpMessageHandler>(); | ||||
private List<WxMpMessageInterceptor> interceptors = new ArrayList<WxMpMessageInterceptor>(); | private List<WxMpMessageInterceptor> interceptors = new ArrayList<WxMpMessageInterceptor>(); | ||||
protected Rule(WxMpMessageRouter routerBuilder, WxMpService wxMpService) { | |||||
protected Rule(WxMpMessageRouter routerBuilder, WxMpService wxMpService, WxSessionManager sessionManager) { | |||||
this.routerBuilder = routerBuilder; | this.routerBuilder = routerBuilder; | ||||
this.wxMpService = wxMpService; | this.wxMpService = wxMpService; | ||||
this.sessionManager = sessionManager; | |||||
} | } | ||||
/** | /** | ||||
@@ -297,7 +402,7 @@ public class WxMpMessageRouter { | |||||
Map<String, Object> context = new HashMap<String, Object>(); | Map<String, Object> context = new HashMap<String, Object>(); | ||||
// 如果拦截器不通过 | // 如果拦截器不通过 | ||||
for (WxMpMessageInterceptor interceptor : this.interceptors) { | for (WxMpMessageInterceptor interceptor : this.interceptors) { | ||||
if (!interceptor.intercept(wxMessage, context, wxMpService)) { | |||||
if (!interceptor.intercept(wxMessage, context, wxMpService, sessionManager)) { | |||||
return null; | return null; | ||||
} | } | ||||
} | } | ||||
@@ -306,7 +411,7 @@ public class WxMpMessageRouter { | |||||
WxMpXmlOutMessage res = null; | WxMpXmlOutMessage res = null; | ||||
for (WxMpMessageHandler handler : this.handlers) { | for (WxMpMessageHandler handler : this.handlers) { | ||||
// 返回最后handler的结果 | // 返回最后handler的结果 | ||||
res = handler.handle(wxMessage, context, wxMpService); | |||||
res = handler.handle(wxMessage, context, wxMpService, sessionManager); | |||||
} | } | ||||
return res; | return res; | ||||
} | } | ||||
@@ -2,10 +2,12 @@ package me.chanjar.weixin.mp.api; | |||||
import me.chanjar.weixin.common.bean.WxMenu; | import me.chanjar.weixin.common.bean.WxMenu; | ||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import me.chanjar.weixin.common.session.WxSession; | |||||
import me.chanjar.weixin.common.session.WxSessionManager; | |||||
import me.chanjar.weixin.common.util.http.RequestExecutor; | import me.chanjar.weixin.common.util.http.RequestExecutor; | ||||
import me.chanjar.weixin.mp.bean.*; | import me.chanjar.weixin.mp.bean.*; | ||||
import me.chanjar.weixin.mp.bean.result.*; | import me.chanjar.weixin.mp.bean.result.*; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
@@ -28,7 +30,15 @@ public interface WxMpService { | |||||
* @return | * @return | ||||
*/ | */ | ||||
public boolean checkSignature(String timestamp, String nonce, String signature); | public boolean checkSignature(String timestamp, String nonce, String signature); | ||||
/** | |||||
* 获取access_token, 不强制刷新access_token | |||||
* @see #getAccessToken(boolean) | |||||
* @return | |||||
* @throws WxErrorException | |||||
*/ | |||||
public String getAccessToken() throws WxErrorException; | |||||
/** | /** | ||||
* <pre> | * <pre> | ||||
* 获取access_token,本方法线程安全 | * 获取access_token,本方法线程安全 | ||||
@@ -40,20 +50,56 @@ public interface WxMpService { | |||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token | ||||
* </pre> | * </pre> | ||||
* @param forceRefresh 强制刷新 | |||||
* @return | |||||
* @throws me.chanjar.weixin.common.exception.WxErrorException | * @throws me.chanjar.weixin.common.exception.WxErrorException | ||||
*/ | */ | ||||
public void accessTokenRefresh() throws WxErrorException; | |||||
public String getAccessToken(boolean forceRefresh) throws WxErrorException; | |||||
/** | |||||
* 获得jsapi_ticket,不强制刷新jsapi_ticket | |||||
* @see #getJsapiTicket(boolean) | |||||
* @return | |||||
* @throws WxErrorException | |||||
*/ | |||||
public String getJsapiTicket() throws WxErrorException; | |||||
/** | |||||
* <pre> | |||||
* 获得jsapi_ticket | |||||
* 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干 | |||||
* | |||||
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95 | |||||
* </pre> | |||||
* @param forceRefresh 强制刷新 | |||||
* @return | |||||
* @throws WxErrorException | |||||
*/ | |||||
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException; | |||||
/** | |||||
* <pre> | |||||
* 创建调用jsapi时所需要的签名 | |||||
* | |||||
* 详情请见:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html#.E9.99.84.E5.BD.951-JS-SDK.E4.BD.BF.E7.94.A8.E6.9D.83.E9.99.90.E7.AD.BE.E5.90.8D.E7.AE.97.E6.B3.95 | |||||
* </pre> | |||||
* @param timestamp 时间戳 | |||||
* @param noncestr 用户自己生成的随机字符串 | |||||
* @param url url | |||||
* @return | |||||
*/ | |||||
public String createJsapiSignature(long timestamp, String noncestr, String url) throws WxErrorException; | |||||
/** | /** | ||||
* <pre> | * <pre> | ||||
* 上传多媒体文件 | * 上传多媒体文件 | ||||
* | |||||
* | |||||
* 上传的多媒体文件有格式和大小限制,如下: | * 上传的多媒体文件有格式和大小限制,如下: | ||||
* 图片(image): 1M,支持JPG格式 | * 图片(image): 1M,支持JPG格式 | ||||
* 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | * 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式 | ||||
* 视频(video):10MB,支持MP4格式 | * 视频(video):10MB,支持MP4格式 | ||||
* 缩略图(thumb):64KB,支持JPG格式 | * 缩略图(thumb):64KB,支持JPG格式 | ||||
* | |||||
* | |||||
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件 | ||||
* </pre> | * </pre> | ||||
* @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | * @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} | ||||
@@ -412,9 +458,28 @@ public interface WxMpService { | |||||
*/ | */ | ||||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException; | public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException; | ||||
/** | |||||
* 注入 {@link WxMpConfigStorage} 的实现 | |||||
* @param wxConfigProvider | |||||
*/ | |||||
/** | |||||
* 注入 {@link WxMpConfigStorage} 的实现 | |||||
* @param wxConfigProvider | |||||
*/ | |||||
public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider); | public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider); | ||||
/** | |||||
* <pre> | |||||
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试 | |||||
* 默认:1000ms | |||||
* </pre> | |||||
* @param retrySleepMillis | |||||
*/ | |||||
void setRetrySleepMillis(int retrySleepMillis); | |||||
/** | |||||
* <pre> | |||||
* 设置当微信系统响应系统繁忙时,最大重试次数 | |||||
* 默认:5次 | |||||
* </pre> | |||||
* @param maxRetryTimes | |||||
*/ | |||||
void setMaxRetryTimes(int maxRetryTimes); | |||||
} | } |
@@ -10,6 +10,8 @@ import me.chanjar.weixin.common.bean.WxMenu; | |||||
import me.chanjar.weixin.common.bean.result.WxError; | import me.chanjar.weixin.common.bean.result.WxError; | ||||
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | import me.chanjar.weixin.common.exception.WxErrorException; | ||||
import me.chanjar.weixin.common.session.StandardSessionManager; | |||||
import me.chanjar.weixin.common.session.WxSessionManager; | |||||
import me.chanjar.weixin.common.util.StringUtils; | import me.chanjar.weixin.common.util.StringUtils; | ||||
import me.chanjar.weixin.common.util.crypto.SHA1; | import me.chanjar.weixin.common.util.crypto.SHA1; | ||||
import me.chanjar.weixin.common.util.fs.FileUtils; | import me.chanjar.weixin.common.util.fs.FileUtils; | ||||
@@ -31,32 +33,43 @@ import org.apache.http.impl.client.BasicCredentialsProvider; | |||||
import org.apache.http.impl.client.BasicResponseHandler; | import org.apache.http.impl.client.BasicResponseHandler; | ||||
import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||
import org.apache.http.impl.client.HttpClients; | import org.apache.http.impl.client.HttpClients; | ||||
import org.slf4j.Logger; | |||||
import org.slf4j.LoggerFactory; | |||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.io.StringReader; | import java.io.StringReader; | ||||
import java.security.NoSuchAlgorithmException; | |||||
import java.util.List; | import java.util.List; | ||||
import java.util.UUID; | import java.util.UUID; | ||||
import java.util.concurrent.atomic.AtomicBoolean; | |||||
public class WxMpServiceImpl implements WxMpService { | public class WxMpServiceImpl implements WxMpService { | ||||
protected final Logger log = LoggerFactory.getLogger(WxMpServiceImpl.class); | |||||
/** | /** | ||||
* 全局的是否正在刷新Access Token的flag | |||||
* true: 正在刷新 | |||||
* false: 没有刷新 | |||||
* 全局的是否正在刷新access token的锁 | |||||
*/ | */ | ||||
protected static final AtomicBoolean GLOBAL_ACCESS_TOKEN_REFRESH_FLAG = new AtomicBoolean(false); | |||||
protected final Object globalAccessTokenRefreshLock = new Object(); | |||||
/** | |||||
* 全局的是否正在刷新jsapi_ticket的锁 | |||||
*/ | |||||
protected final Object globalJsapiTicketRefreshLock = new Object(); | |||||
protected WxMpConfigStorage wxMpConfigStorage; | protected WxMpConfigStorage wxMpConfigStorage; | ||||
protected final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>(); | |||||
protected CloseableHttpClient httpClient; | protected CloseableHttpClient httpClient; | ||||
protected HttpHost httpProxy; | protected HttpHost httpProxy; | ||||
private int retrySleepMillis = 1000; | |||||
private int maxRetryTimes = 5; | |||||
protected WxSessionManager sessionManager = new StandardSessionManager(); | |||||
public boolean checkSignature(String timestamp, String nonce, String signature) { | public boolean checkSignature(String timestamp, String nonce, String signature) { | ||||
try { | try { | ||||
return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature); | return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature); | ||||
@@ -64,49 +77,86 @@ public class WxMpServiceImpl implements WxMpService { | |||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
public void accessTokenRefresh() throws WxErrorException { | |||||
if (!GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.getAndSet(true)) { | |||||
try { | |||||
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" | |||||
+ "&appid=" + wxMpConfigStorage.getAppId() | |||||
+ "&secret=" + wxMpConfigStorage.getSecret() | |||||
; | |||||
try { | |||||
HttpGet httpGet = new HttpGet(url); | |||||
if (httpProxy != null) { | |||||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||||
httpGet.setConfig(config); | |||||
} | |||||
CloseableHttpClient httpclient = getHttpclient(); | |||||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||||
String resultContent = new BasicResponseHandler().handleResponse(response); | |||||
WxError error = WxError.fromJson(resultContent); | |||||
if (error.getErrorCode() != 0) { | |||||
throw new WxErrorException(error); | |||||
public String getAccessToken() throws WxErrorException { | |||||
return getAccessToken(false); | |||||
} | |||||
public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||||
if (forceRefresh) { | |||||
wxMpConfigStorage.expireAccessToken(); | |||||
} | |||||
if (wxMpConfigStorage.isAccessTokenExpired()) { | |||||
synchronized (globalAccessTokenRefreshLock) { | |||||
if (wxMpConfigStorage.isAccessTokenExpired()) { | |||||
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" | |||||
+ "&appid=" + wxMpConfigStorage.getAppId() | |||||
+ "&secret=" + wxMpConfigStorage.getSecret() | |||||
; | |||||
try { | |||||
HttpGet httpGet = new HttpGet(url); | |||||
if (httpProxy != null) { | |||||
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build(); | |||||
httpGet.setConfig(config); | |||||
} | |||||
CloseableHttpClient httpclient = getHttpclient(); | |||||
CloseableHttpResponse response = httpclient.execute(httpGet); | |||||
String resultContent = new BasicResponseHandler().handleResponse(response); | |||||
WxError error = WxError.fromJson(resultContent); | |||||
if (error.getErrorCode() != 0) { | |||||
throw new WxErrorException(error); | |||||
} | |||||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
wxMpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
} catch (ClientProtocolException e) { | |||||
throw new RuntimeException(e); | |||||
} catch (IOException e) { | |||||
throw new RuntimeException(e); | |||||
} | } | ||||
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
wxMpConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
} catch (ClientProtocolException e) { | |||||
throw new RuntimeException(e); | |||||
} catch (IOException e) { | |||||
throw new RuntimeException(e); | |||||
} | } | ||||
} finally { | |||||
GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.set(false); | |||||
} | } | ||||
} else { | |||||
// 每隔100ms检查一下是否刷新完毕了 | |||||
while (GLOBAL_ACCESS_TOKEN_REFRESH_FLAG.get()) { | |||||
try { | |||||
Thread.sleep(100); | |||||
} catch (InterruptedException e) { | |||||
} | |||||
return wxMpConfigStorage.getAccessToken(); | |||||
} | |||||
public String getJsapiTicket() throws WxErrorException { | |||||
return getJsapiTicket(false); | |||||
} | |||||
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { | |||||
if (forceRefresh) { | |||||
wxMpConfigStorage.expireJsapiTicket(); | |||||
} | |||||
if (wxMpConfigStorage.isJsapiTicketExpired()) { | |||||
synchronized (globalJsapiTicketRefreshLock) { | |||||
if (wxMpConfigStorage.isJsapiTicketExpired()) { | |||||
String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi"; | |||||
String responseContent = execute(new SimpleGetRequestExecutor(), url, null); | |||||
JsonElement tmpJsonElement = Streams.parse(new JsonReader(new StringReader(responseContent))); | |||||
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||||
String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||||
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||||
wxMpConfigStorage.updateJsapiTicket(jsapiTicket, expiresInSeconds); | |||||
} | } | ||||
} | } | ||||
// 刷新完毕了,就没他什么事儿了 | |||||
} | } | ||||
return wxMpConfigStorage.getJsapiTicket(); | |||||
} | } | ||||
public String createJsapiSignature(long timestamp, String noncestr, String url) throws WxErrorException { | |||||
String jsapiTicket = getJsapiTicket(false); | |||||
try { | |||||
return SHA1.genWithAmple( | |||||
"jsapi_ticket=" + jsapiTicket, | |||||
"noncestr=" + noncestr, | |||||
"timestamp=" + timestamp, | |||||
"url=" + url | |||||
); | |||||
} catch (NoSuchAlgorithmException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
} | |||||
public void customMessageSend(WxMpCustomMessage message) throws WxErrorException { | public void customMessageSend(WxMpCustomMessage message) throws WxErrorException { | ||||
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send"; | ||||
execute(new SimplePostRequestExecutor(), url, message.toJson()); | execute(new SimplePostRequestExecutor(), url, message.toJson()); | ||||
@@ -399,7 +449,7 @@ public class WxMpServiceImpl implements WxMpService { | |||||
return execute(new SimplePostRequestExecutor(), url, postData); | return execute(new SimplePostRequestExecutor(), url, postData); | ||||
} | } | ||||
/** | |||||
/** | |||||
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | ||||
* @param executor | * @param executor | ||||
* @param uri | * @param uri | ||||
@@ -408,10 +458,34 @@ public class WxMpServiceImpl implements WxMpService { | |||||
* @throws WxErrorException | * @throws WxErrorException | ||||
*/ | */ | ||||
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | ||||
if (StringUtils.isBlank(wxMpConfigStorage.getAccessToken())) { | |||||
accessTokenRefresh(); | |||||
} | |||||
String accessToken = wxMpConfigStorage.getAccessToken(); | |||||
int retryTimes = 0; | |||||
do { | |||||
try { | |||||
return executeInternal(executor, uri, data); | |||||
} catch (WxErrorException e) { | |||||
WxError error = e.getError(); | |||||
/** | |||||
* -1 系统繁忙, 1000ms后重试 | |||||
*/ | |||||
if (error.getErrorCode() == -1) { | |||||
int sleepMillis = retrySleepMillis * (1 << retryTimes); | |||||
try { | |||||
log.debug("微信系统繁忙,{}ms 后重试(第{}次)", sleepMillis, retryTimes + 1); | |||||
Thread.sleep(sleepMillis); | |||||
} catch (InterruptedException e1) { | |||||
throw new RuntimeException(e1); | |||||
} | |||||
} else { | |||||
throw e; | |||||
} | |||||
} | |||||
} while(++retryTimes < maxRetryTimes); | |||||
throw new RuntimeException("微信服务端异常,超出重试次数"); | |||||
} | |||||
protected <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||||
String accessToken = getAccessToken(false); | |||||
String uriWithAccessToken = uri; | String uriWithAccessToken = uri; | ||||
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; | uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken; | ||||
@@ -426,30 +500,10 @@ public class WxMpServiceImpl implements WxMpService { | |||||
* 42001 access_token超时 | * 42001 access_token超时 | ||||
*/ | */ | ||||
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { | ||||
accessTokenRefresh(); | |||||
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||||
wxMpConfigStorage.expireAccessToken(); | |||||
return execute(executor, uri, data); | return execute(executor, uri, data); | ||||
} | } | ||||
/** | |||||
* -1 系统繁忙, 1000ms后重试 | |||||
*/ | |||||
if (error.getErrorCode() == -1) { | |||||
if(retryTimes.get() == null) { | |||||
retryTimes.set(0); | |||||
} | |||||
if (retryTimes.get() > 4) { | |||||
retryTimes.set(0); | |||||
throw new RuntimeException("微信服务端异常,超出重试次数"); | |||||
} | |||||
int sleepMillis = 1000 * (1 << retryTimes.get()); | |||||
try { | |||||
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试"); | |||||
Thread.sleep(sleepMillis); | |||||
retryTimes.set(retryTimes.get() + 1); | |||||
return execute(executor, uri, data); | |||||
} catch (InterruptedException e1) { | |||||
throw new RuntimeException(e1); | |||||
} | |||||
} | |||||
if (error.getErrorCode() != 0) { | if (error.getErrorCode() != 0) { | ||||
throw new WxErrorException(error); | throw new WxErrorException(error); | ||||
} | } | ||||
@@ -495,4 +549,15 @@ public class WxMpServiceImpl implements WxMpService { | |||||
} | } | ||||
} | } | ||||
@Override | |||||
public void setRetrySleepMillis(int retrySleepMillis) { | |||||
this.retrySleepMillis = retrySleepMillis; | |||||
} | |||||
@Override | |||||
public void setMaxRetryTimes(int maxRetryTimes) { | |||||
this.maxRetryTimes = maxRetryTimes; | |||||
} | |||||
} | } |
@@ -1,32 +0,0 @@ | |||||
package me.chanjar.weixin.mp.bean; | |||||
import me.chanjar.weixin.common.api.WxConsts; | |||||
import me.chanjar.weixin.common.util.xml.MediaIdMarshaller; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public class WxMpMpXmlOutImageMessage extends WxMpXmlOutMessage { | |||||
@XmlElement(name="Image") | |||||
@XmlJavaTypeAdapter(MediaIdMarshaller.class) | |||||
private String mediaId; | |||||
public WxMpMpXmlOutImageMessage() { | |||||
this.msgType = WxConsts.XML_MSG_IMAGE; | |||||
} | |||||
public String getMediaId() { | |||||
return mediaId; | |||||
} | |||||
public void setMediaId(String mediaId) { | |||||
this.mediaId = mediaId; | |||||
} | |||||
} |
@@ -5,9 +5,6 @@ import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | import java.util.List; | ||||
/** | |||||
* Created by qianjia on 14/11/1. | |||||
*/ | |||||
public class WxMpTemplateMessage { | public class WxMpTemplateMessage { | ||||
private String toUser; | private String toUser; | ||||
@@ -1,14 +1,13 @@ | |||||
package me.chanjar.weixin.mp.bean; | package me.chanjar.weixin.mp.bean; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
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.api.WxMpConfigStorage; | ||||
import me.chanjar.weixin.mp.util.crypto.WxMpCryptUtil; | import me.chanjar.weixin.mp.util.crypto.WxMpCryptUtil; | ||||
import me.chanjar.weixin.mp.util.xml.XmlTransformer; | |||||
import me.chanjar.weixin.mp.util.xml.XStreamTransformer; | |||||
import org.apache.commons.io.IOUtils; | import org.apache.commons.io.IOUtils; | ||||
import javax.xml.bind.JAXBException; | |||||
import javax.xml.bind.annotation.*; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
@@ -25,100 +24,99 @@ import java.util.List; | |||||
* | * | ||||
* @author chanjarster | * @author chanjarster | ||||
*/ | */ | ||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxMpXmlMessage { | public class WxMpXmlMessage { | ||||
/////////////////////// | /////////////////////// | ||||
// 以下都是微信推送过来的消息的xml的element所对应的属性 | // 以下都是微信推送过来的消息的xml的element所对应的属性 | ||||
/////////////////////// | /////////////////////// | ||||
@XmlElement(name = "ToUserName") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ToUserName") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String toUserName; | private String toUserName; | ||||
@XmlElement(name = "FromUserName") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("FromUserName") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String fromUserName; | private String fromUserName; | ||||
@XmlElement(name = "CreateTime") | |||||
@XStreamAlias("CreateTime") | |||||
private Long createTime; | private Long createTime; | ||||
@XmlElement(name = "MsgType") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("MsgType") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String msgType; | private String msgType; | ||||
@XmlElement(name = "Content") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Content") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String content; | private String content; | ||||
@XmlElement(name = "MsgId") | |||||
@XStreamAlias("MsgId") | |||||
private Long msgId; | private Long msgId; | ||||
@XmlElement(name = "PicUrl") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("PicUrl") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String picUrl; | private String picUrl; | ||||
@XmlElement(name = "MediaId") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("MediaId") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String mediaId; | private String mediaId; | ||||
@XmlElement(name = "Format") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Format") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String format; | private String format; | ||||
@XmlElement(name = "ThumbMediaId") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ThumbMediaId") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String thumbMediaId; | private String thumbMediaId; | ||||
@XmlElement(name = "Location_X") | |||||
@XStreamAlias("Location_X") | |||||
private Double locationX; | private Double locationX; | ||||
@XmlElement(name = "Location_Y") | |||||
@XStreamAlias("Location_Y") | |||||
private Double locationY; | private Double locationY; | ||||
@XmlElement(name = "Scale") | |||||
@XStreamAlias("Scale") | |||||
private Double scale; | private Double scale; | ||||
@XmlElement(name = "Label") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Label") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String label; | private String label; | ||||
@XmlElement(name = "Title") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Title") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String title; | private String title; | ||||
@XmlElement(name = "Description") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Description") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String description; | private String description; | ||||
@XmlElement(name = "Url") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Url") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String url; | private String url; | ||||
@XmlElement(name = "Event") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Event") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String event; | private String event; | ||||
@XmlElement(name = "EventKey") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("EventKey") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String eventKey; | private String eventKey; | ||||
@XmlElement(name = "Ticket") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Ticket") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String ticket; | private String ticket; | ||||
@XmlElement(name = "Latitude") | |||||
@XStreamAlias("Latitude") | |||||
private Double latitude; | private Double latitude; | ||||
@XmlElement(name = "Longitude") | |||||
@XStreamAlias("Longitude") | |||||
private Double longitude; | private Double longitude; | ||||
@XmlElement(name = "Precision") | |||||
@XStreamAlias("Precision") | |||||
private Double precision; | private Double precision; | ||||
@XmlElement(name = "Recognition") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Recognition") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String recognition; | private String recognition; | ||||
/////////////////////////////////////// | /////////////////////////////////////// | ||||
@@ -127,37 +125,37 @@ public class WxMpXmlMessage { | |||||
/** | /** | ||||
* 群发的结果 | * 群发的结果 | ||||
*/ | */ | ||||
@XmlElement(name = "Status") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Status") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String status; | private String status; | ||||
/** | /** | ||||
* group_id下粉丝数;或者openid_list中的粉丝数 | * group_id下粉丝数;或者openid_list中的粉丝数 | ||||
*/ | */ | ||||
@XmlElement(name = "TotalCount") | |||||
@XStreamAlias("TotalCount") | |||||
private Integer totalCount; | private Integer totalCount; | ||||
/** | /** | ||||
* 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount | * 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount | ||||
*/ | */ | ||||
@XmlElement(name = "FilterCount") | |||||
@XStreamAlias("FilterCount") | |||||
private Integer filterCount; | private Integer filterCount; | ||||
/** | /** | ||||
* 发送成功的粉丝数 | * 发送成功的粉丝数 | ||||
*/ | */ | ||||
@XmlElement(name = "SentCount") | |||||
@XStreamAlias("SentCount") | |||||
private Integer sentCount; | private Integer sentCount; | ||||
/** | /** | ||||
* 发送失败的粉丝数 | * 发送失败的粉丝数 | ||||
*/ | */ | ||||
@XmlElement(name = "ErrorCount") | |||||
@XStreamAlias("ErrorCount") | |||||
private Integer errorCount; | private Integer errorCount; | ||||
@XmlElement(name = "ScanCodeInfo") | |||||
@XStreamAlias("ScanCodeInfo") | |||||
private ScanCodeInfo scanCodeInfo = new ScanCodeInfo(); | private ScanCodeInfo scanCodeInfo = new ScanCodeInfo(); | ||||
@XmlElement(name = "SendPicsInfo") | |||||
@XStreamAlias("SendPicsInfo") | |||||
private SendPicsInfo sendPicsInfo = new SendPicsInfo(); | private SendPicsInfo sendPicsInfo = new SendPicsInfo(); | ||||
@XmlElement(name = "SendLocationInfo") | |||||
@XStreamAlias("SendLocationInfo") | |||||
private SendLocationInfo sendLocationInfo = new SendLocationInfo(); | private SendLocationInfo sendLocationInfo = new SendLocationInfo(); | ||||
public String getToUserName() { | public String getToUserName() { | ||||
@@ -380,21 +378,11 @@ public class WxMpXmlMessage { | |||||
} | } | ||||
public static WxMpXmlMessage fromXml(String xml) { | public static WxMpXmlMessage fromXml(String xml) { | ||||
try { | |||||
// 操蛋的微信,模板消息推送成功的消息是MsgID,其他消息推送过来是MsgId | |||||
xml = xml.replaceAll("<MsgID>", "<MsgId>").replaceAll("</MsgID>", "</MsgId>"); | |||||
return XmlTransformer.fromXml(WxMpXmlMessage.class, xml); | |||||
} catch (JAXBException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
return XStreamTransformer.fromXml(WxMpXmlMessage.class, xml); | |||||
} | } | ||||
public static WxMpXmlMessage fromXml(InputStream is) { | public static WxMpXmlMessage fromXml(InputStream is) { | ||||
try { | |||||
return fromXml(IOUtils.toString(is, "UTF-8")); | |||||
} catch (IOException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
return XStreamTransformer.fromXml(WxMpXmlMessage.class, is); | |||||
} | } | ||||
/** | /** | ||||
@@ -491,16 +479,15 @@ public class WxMpXmlMessage { | |||||
this.sendLocationInfo = sendLocationInfo; | this.sendLocationInfo = sendLocationInfo; | ||||
} | } | ||||
@XmlRootElement(name = "ScanCodeInfo") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("ScanCodeInfo") | |||||
public static class ScanCodeInfo { | public static class ScanCodeInfo { | ||||
@XmlElement(name = "ScanType") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ScanType") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String scanType; | private String scanType; | ||||
@XmlElement(name = "ScanResult") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ScanResult") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String scanResult; | private String scanResult; | ||||
/** | /** | ||||
@@ -530,15 +517,13 @@ public class WxMpXmlMessage { | |||||
} | } | ||||
@XmlRootElement(name = "SendPicsInfo") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("SendPicsInfo") | |||||
public static class SendPicsInfo { | public static class SendPicsInfo { | ||||
@XmlElement(name = "Count") | |||||
@XStreamAlias("Count") | |||||
private Long count; | private Long count; | ||||
@XmlElementWrapper(name="PicList") | |||||
@XmlElement(name = "item") | |||||
@XStreamAlias("PicList") | |||||
protected final List<Item> picList = new ArrayList<Item>(); | protected final List<Item> picList = new ArrayList<Item>(); | ||||
public Long getCount() { | public Long getCount() { | ||||
@@ -553,47 +538,44 @@ public class WxMpXmlMessage { | |||||
return picList; | return picList; | ||||
} | } | ||||
@XmlRootElement(name = "item") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XmlType(name = "WxXmlMessage.SendPicsInfo.Item") | |||||
@XStreamAlias("item") | |||||
public static class Item { | public static class Item { | ||||
@XmlElement(name = "PicMd5Sum") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
private String PicMd5Sum; | |||||
@XStreamAlias("PicMd5Sum") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String picMd5Sum; | |||||
public String getPicMd5Sum() { | public String getPicMd5Sum() { | ||||
return PicMd5Sum; | |||||
return picMd5Sum; | |||||
} | } | ||||
public void setPicMd5Sum(String picMd5Sum) { | public void setPicMd5Sum(String picMd5Sum) { | ||||
PicMd5Sum = picMd5Sum; | |||||
this.picMd5Sum = picMd5Sum; | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@XmlRootElement(name = "SendLocationInfo") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("SendLocationInfo") | |||||
public static class SendLocationInfo { | public static class SendLocationInfo { | ||||
@XmlElement(name = "Location_X") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Location_X") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String locationX; | private String locationX; | ||||
@XmlElement(name = "Location_Y") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Location_Y") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String locationY; | private String locationY; | ||||
@XmlElement(name = "Scale") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Scale") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String scale; | private String scale; | ||||
@XmlElement(name = "Label") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Label") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String label; | private String label; | ||||
@XmlElement(name = "Poiname") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Poiname") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String poiname; | private String poiname; | ||||
public String getLocationX() { | public String getLocationX() { | ||||
@@ -0,0 +1,27 @@ | |||||
package me.chanjar.weixin.mp.bean; | |||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | |||||
import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | |||||
@XStreamAlias("xml") | |||||
public class WxMpXmlOutImageMessage extends WxMpXmlOutMessage { | |||||
@XStreamAlias("Image") | |||||
@XStreamConverter(value = XStreamMediaIdConverter.class) | |||||
private String mediaId; | |||||
public String getMediaId() { | |||||
return mediaId; | |||||
} | |||||
public void setMediaId(String mediaId) { | |||||
this.mediaId = mediaId; | |||||
} | |||||
public WxMpXmlOutImageMessage() { | |||||
this.msgType = WxConsts.XML_MSG_IMAGE; | |||||
} | |||||
} |
@@ -1,35 +1,29 @@ | |||||
package me.chanjar.weixin.mp.bean; | package me.chanjar.weixin.mp.bean; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
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.api.WxMpConfigStorage; | ||||
import me.chanjar.weixin.mp.bean.outxmlbuilder.*; | import me.chanjar.weixin.mp.bean.outxmlbuilder.*; | ||||
import me.chanjar.weixin.mp.util.crypto.WxMpCryptUtil; | import me.chanjar.weixin.mp.util.crypto.WxMpCryptUtil; | ||||
import me.chanjar.weixin.mp.util.xml.XmlTransformer; | |||||
import me.chanjar.weixin.mp.util.xml.XStreamTransformer; | |||||
import javax.xml.bind.JAXBException; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
@XStreamAlias("xml") | |||||
public abstract class WxMpXmlOutMessage { | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
public class WxMpXmlOutMessage { | |||||
@XmlElement(name="ToUserName") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ToUserName") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
protected String toUserName; | protected String toUserName; | ||||
@XmlElement(name="FromUserName") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("FromUserName") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
protected String fromUserName; | protected String fromUserName; | ||||
@XmlElement(name="CreateTime") | |||||
@XStreamAlias("CreateTime") | |||||
protected Long createTime; | protected Long createTime; | ||||
@XmlElement(name="MsgType") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("MsgType") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
protected String msgType; | protected String msgType; | ||||
public String getToUserName() { | public String getToUserName() { | ||||
@@ -65,11 +59,7 @@ public class WxMpXmlOutMessage { | |||||
} | } | ||||
public String toXml() { | public String toXml() { | ||||
try { | |||||
return XmlTransformer.toXml((Class)this.getClass(), this); | |||||
} catch (JAXBException e) { | |||||
throw new RuntimeException(e); | |||||
} | |||||
return XStreamTransformer.toXml((Class) this.getClass(), this); | |||||
} | } | ||||
/** | /** | ||||
@@ -1,19 +1,14 @@ | |||||
package me.chanjar.weixin.mp.bean; | package me.chanjar.weixin.mp.bean; | ||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxMpXmlOutMusicMessage extends WxMpXmlOutMessage { | public class WxMpXmlOutMusicMessage extends WxMpXmlOutMessage { | ||||
@XmlElement(name = "Music") | |||||
@XStreamAlias("Music") | |||||
protected final Music music = new Music(); | protected final Music music = new Music(); | ||||
public WxMpXmlOutMusicMessage() { | public WxMpXmlOutMusicMessage() { | ||||
@@ -60,28 +55,27 @@ public class WxMpXmlOutMusicMessage extends WxMpXmlOutMessage { | |||||
music.setHqMusicUrl(hqMusicUrl); | music.setHqMusicUrl(hqMusicUrl); | ||||
} | } | ||||
@XmlRootElement(name = "Music") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
private static class Music { | |||||
@XStreamAlias("Music") | |||||
public static class Music { | |||||
@XmlElement(name = "Title") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Title") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String title; | private String title; | ||||
@XmlElement(name = "Description") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Description") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String description; | private String description; | ||||
@XmlElement(name="ThumbMediaId") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("ThumbMediaId") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String thumbMediaId; | private String thumbMediaId; | ||||
@XmlElement(name="MusicUrl") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("MusicUrl") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String musicUrl; | private String musicUrl; | ||||
@XmlElement(name="HQMusicUrl") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("HQMusicUrl") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String hqMusicUrl; | private String hqMusicUrl; | ||||
public String getTitle() { | public String getTitle() { | ||||
@@ -1,22 +1,20 @@ | |||||
package me.chanjar.weixin.mp.bean; | package me.chanjar.weixin.mp.bean; | ||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||||
import javax.xml.bind.annotation.*; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.List; | import java.util.List; | ||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxMpXmlOutNewsMessage extends WxMpXmlOutMessage { | public class WxMpXmlOutNewsMessage extends WxMpXmlOutMessage { | ||||
@XmlElement(name = "ArticleCount") | |||||
@XStreamAlias("ArticleCount") | |||||
protected int articleCount; | protected int articleCount; | ||||
@XmlElementWrapper(name="Articles") | |||||
@XmlElement(name = "item") | |||||
@XStreamAlias("Articles") | |||||
protected final List<Item> articles = new ArrayList<Item>(); | protected final List<Item> articles = new ArrayList<Item>(); | ||||
public WxMpXmlOutNewsMessage() { | public WxMpXmlOutNewsMessage() { | ||||
@@ -37,24 +35,23 @@ public class WxMpXmlOutNewsMessage extends WxMpXmlOutMessage { | |||||
} | } | ||||
@XmlRootElement(name = "Item") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("item") | |||||
public static class Item { | public static class Item { | ||||
@XmlElement(name = "Title") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Title") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String Title; | private String Title; | ||||
@XmlElement(name = "Description") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Description") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String Description; | private String Description; | ||||
@XmlElement(name="PicUrl") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("PicUrl") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String PicUrl; | private String PicUrl; | ||||
@XmlElement(name="Url") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Url") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String Url; | private String Url; | ||||
public String getTitle() { | public String getTitle() { | ||||
@@ -1,20 +1,15 @@ | |||||
package me.chanjar.weixin.mp.bean; | package me.chanjar.weixin.mp.bean; | ||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxMpXmlOutTextMessage extends WxMpXmlOutMessage { | public class WxMpXmlOutTextMessage extends WxMpXmlOutMessage { | ||||
@XmlElement(name="Content") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Content") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String content; | private String content; | ||||
public WxMpXmlOutTextMessage() { | public WxMpXmlOutTextMessage() { | ||||
@@ -1,19 +1,14 @@ | |||||
package me.chanjar.weixin.mp.bean; | package me.chanjar.weixin.mp.bean; | ||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.util.xml.AdapterCDATA; | |||||
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxMpXmlOutVideoMessage extends WxMpXmlOutMessage { | public class WxMpXmlOutVideoMessage extends WxMpXmlOutMessage { | ||||
@XmlElement(name = "Video") | |||||
@XStreamAlias("Video") | |||||
protected final Video video = new Video(); | protected final Video video = new Video(); | ||||
public WxMpXmlOutVideoMessage() { | public WxMpXmlOutVideoMessage() { | ||||
@@ -45,20 +40,19 @@ public class WxMpXmlOutVideoMessage extends WxMpXmlOutMessage { | |||||
} | } | ||||
@XmlRootElement(name = "Video") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
private static class Video { | |||||
@XStreamAlias("Video") | |||||
public static class Video { | |||||
@XmlElement(name = "MediaId") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("MediaId") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String mediaId; | private String mediaId; | ||||
@XmlElement(name = "Title") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Title") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String title; | private String title; | ||||
@XmlElement(name = "Description") | |||||
@XmlJavaTypeAdapter(AdapterCDATA.class) | |||||
@XStreamAlias("Description") | |||||
@XStreamConverter(value=XStreamCDataConverter.class) | |||||
private String description; | private String description; | ||||
public String getMediaId() { | public String getMediaId() { | ||||
@@ -1,26 +1,21 @@ | |||||
package me.chanjar.weixin.mp.bean; | package me.chanjar.weixin.mp.bean; | ||||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
import me.chanjar.weixin.common.util.xml.MediaIdMarshaller; | |||||
import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter; | |||||
import javax.xml.bind.annotation.XmlAccessType; | |||||
import javax.xml.bind.annotation.XmlAccessorType; | |||||
import javax.xml.bind.annotation.XmlElement; | |||||
import javax.xml.bind.annotation.XmlRootElement; | |||||
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; | |||||
@XmlRootElement(name = "xml") | |||||
@XmlAccessorType(XmlAccessType.FIELD) | |||||
@XStreamAlias("xml") | |||||
public class WxMpXmlOutVoiceMessage extends WxMpXmlOutMessage { | public class WxMpXmlOutVoiceMessage extends WxMpXmlOutMessage { | ||||
@XmlElement(name="Voice") | |||||
@XmlJavaTypeAdapter(MediaIdMarshaller.class) | |||||
@XStreamAlias("Voice") | |||||
@XStreamConverter(value = XStreamMediaIdConverter.class) | |||||
private String mediaId; | private String mediaId; | ||||
public WxMpXmlOutVoiceMessage() { | public WxMpXmlOutVoiceMessage() { | ||||
this.msgType = WxConsts.XML_MSG_VOICE; | this.msgType = WxConsts.XML_MSG_VOICE; | ||||
} | } | ||||
public String getMediaId() { | public String getMediaId() { | ||||
return mediaId; | return mediaId; | ||||
} | } | ||||
@@ -28,5 +23,5 @@ public class WxMpXmlOutVoiceMessage extends WxMpXmlOutMessage { | |||||
public void setMediaId(String mediaId) { | public void setMediaId(String mediaId) { | ||||
this.mediaId = mediaId; | this.mediaId = mediaId; | ||||
} | } | ||||
} | } |
@@ -1,12 +1,12 @@ | |||||
package me.chanjar.weixin.mp.bean.outxmlbuilder; | package me.chanjar.weixin.mp.bean.outxmlbuilder; | ||||
import me.chanjar.weixin.mp.bean.WxMpMpXmlOutImageMessage; | |||||
import me.chanjar.weixin.mp.bean.WxMpXmlOutImageMessage; | |||||
/** | /** | ||||
* 图片消息builder | * 图片消息builder | ||||
* @author chanjarster | * @author chanjarster | ||||
*/ | */ | ||||
public final class ImageBuilder extends BaseBuilder<ImageBuilder, WxMpMpXmlOutImageMessage> { | |||||
public final class ImageBuilder extends BaseBuilder<ImageBuilder, WxMpXmlOutImageMessage> { | |||||
private String mediaId; | private String mediaId; | ||||
@@ -15,8 +15,8 @@ public final class ImageBuilder extends BaseBuilder<ImageBuilder, WxMpMpXmlOutIm | |||||
return this; | return this; | ||||
} | } | ||||
public WxMpMpXmlOutImageMessage build() { | |||||
WxMpMpXmlOutImageMessage m = new WxMpMpXmlOutImageMessage(); | |||||
public WxMpXmlOutImageMessage build() { | |||||
WxMpXmlOutImageMessage m = new WxMpXmlOutImageMessage(); | |||||
setCommon(m); | setCommon(m); | ||||
m.setMediaId(this.mediaId); | m.setMediaId(this.mediaId); | ||||
return m; | return m; | ||||
@@ -1,11 +1,7 @@ | |||||
package me.chanjar.weixin.mp.bean.result; | package me.chanjar.weixin.mp.bean.result; | ||||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||||
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | ||||
/** | |||||
* Created by qianjia on 14/11/26. | |||||
*/ | |||||
public class WxMpOAuth2AccessToken { | public class WxMpOAuth2AccessToken { | ||||
private String accessToken; | private String accessToken; | ||||
@@ -66,7 +62,7 @@ public class WxMpOAuth2AccessToken { | |||||
public String toString() { | public String toString() { | ||||
return "WxMpOAuth2AccessToken{" + | return "WxMpOAuth2AccessToken{" + | ||||
"accessToken='" + accessToken + '\'' + | "accessToken='" + accessToken + '\'' + | ||||
", expiresIn=" + expiresIn + | |||||
", expiresTime=" + expiresIn + | |||||
", refreshToken='" + refreshToken + '\'' + | ", refreshToken='" + refreshToken + '\'' + | ||||
", openId='" + openId + '\'' + | ", openId='" + openId + '\'' + | ||||
", scope='" + scope + '\'' + | ", scope='" + scope + '\'' + | ||||
@@ -1,12 +1,12 @@ | |||||
package me.chanjar.weixin.mp.util.http; | package me.chanjar.weixin.mp.util.http; | ||||
import me.chanjar.weixin.common.bean.result.WxError; | import me.chanjar.weixin.common.bean.result.WxError; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import me.chanjar.weixin.common.util.fs.FileUtils; | import me.chanjar.weixin.common.util.fs.FileUtils; | ||||
import me.chanjar.weixin.common.util.http.InputStreamResponseHandler; | import me.chanjar.weixin.common.util.http.InputStreamResponseHandler; | ||||
import me.chanjar.weixin.common.util.http.RequestExecutor; | import me.chanjar.weixin.common.util.http.RequestExecutor; | ||||
import me.chanjar.weixin.common.util.http.Utf8ResponseHandler; | import me.chanjar.weixin.common.util.http.Utf8ResponseHandler; | ||||
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; | import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; | ||||
import me.chanjar.weixin.common.exception.WxErrorException; | |||||
import org.apache.http.Header; | import org.apache.http.Header; | ||||
import org.apache.http.HttpHost; | import org.apache.http.HttpHost; | ||||
import org.apache.http.client.ClientProtocolException; | import org.apache.http.client.ClientProtocolException; | ||||
@@ -14,11 +14,6 @@ import me.chanjar.weixin.mp.bean.WxMpCustomMessage; | |||||
import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||
/** | |||||
* | |||||
* @author qianjia | |||||
* | |||||
*/ | |||||
public class WxMpCustomMessageGsonAdapter implements JsonSerializer<WxMpCustomMessage> { | public class WxMpCustomMessageGsonAdapter implements JsonSerializer<WxMpCustomMessage> { | ||||
public JsonElement serialize(WxMpCustomMessage message, Type typeOfSrc, JsonSerializationContext context) { | public JsonElement serialize(WxMpCustomMessage message, Type typeOfSrc, JsonSerializationContext context) { | ||||
@@ -14,11 +14,6 @@ import me.chanjar.weixin.mp.bean.WxMpGroup; | |||||
import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||
/** | |||||
* | |||||
* @author qianjia | |||||
* | |||||
*/ | |||||
public class WxMpGroupGsonAdapter implements JsonSerializer<WxMpGroup>, JsonDeserializer<WxMpGroup> { | public class WxMpGroupGsonAdapter implements JsonSerializer<WxMpGroup>, JsonDeserializer<WxMpGroup> { | ||||
public JsonElement serialize(WxMpGroup group, Type typeOfSrc, JsonSerializationContext context) { | public JsonElement serialize(WxMpGroup group, Type typeOfSrc, JsonSerializationContext context) { | ||||
@@ -4,7 +4,6 @@ import com.google.gson.Gson; | |||||
import com.google.gson.GsonBuilder; | import com.google.gson.GsonBuilder; | ||||
import me.chanjar.weixin.mp.bean.*; | import me.chanjar.weixin.mp.bean.*; | ||||
import me.chanjar.weixin.mp.bean.result.*; | import me.chanjar.weixin.mp.bean.result.*; | ||||
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | |||||
public class WxMpGsonBuilder { | public class WxMpGsonBuilder { | ||||
@@ -17,11 +17,6 @@ import me.chanjar.weixin.mp.bean.WxMpMassGroupMessage; | |||||
import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||
/** | |||||
* | |||||
* @author qianjia | |||||
* | |||||
*/ | |||||
public class WxMpMassGroupMessageGsonAdapter implements JsonSerializer<WxMpMassGroupMessage> { | public class WxMpMassGroupMessageGsonAdapter implements JsonSerializer<WxMpMassGroupMessage> { | ||||
public JsonElement serialize(WxMpMassGroupMessage message, Type typeOfSrc, JsonSerializationContext context) { | public JsonElement serialize(WxMpMassGroupMessage message, Type typeOfSrc, JsonSerializationContext context) { | ||||
@@ -13,11 +13,6 @@ import me.chanjar.weixin.mp.bean.WxMpMassNews; | |||||
import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||
/** | |||||
* | |||||
* @author qianjia | |||||
* | |||||
*/ | |||||
public class WxMpMassNewsGsonAdapter implements JsonSerializer<WxMpMassNews> { | public class WxMpMassNewsGsonAdapter implements JsonSerializer<WxMpMassNews> { | ||||
public JsonElement serialize(WxMpMassNews message, Type typeOfSrc, JsonSerializationContext context) { | public JsonElement serialize(WxMpMassNews message, Type typeOfSrc, JsonSerializationContext context) { | ||||
@@ -14,11 +14,6 @@ import me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage; | |||||
import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||
/** | |||||
* | |||||
* @author qianjia | |||||
* | |||||
*/ | |||||
public class WxMpMassOpenIdsMessageGsonAdapter implements JsonSerializer<WxMpMassOpenIdsMessage> { | public class WxMpMassOpenIdsMessageGsonAdapter implements JsonSerializer<WxMpMassOpenIdsMessage> { | ||||
public JsonElement serialize(WxMpMassOpenIdsMessage message, Type typeOfSrc, JsonSerializationContext context) { | public JsonElement serialize(WxMpMassOpenIdsMessage message, Type typeOfSrc, JsonSerializationContext context) { | ||||
@@ -1,15 +1,11 @@ | |||||
package me.chanjar.weixin.mp.util.json; | package me.chanjar.weixin.mp.util.json; | ||||
import com.google.gson.*; | import com.google.gson.*; | ||||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
import me.chanjar.weixin.common.util.json.GsonHelper; | import me.chanjar.weixin.common.util.json.GsonHelper; | ||||
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | ||||
import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||
/** | |||||
* Created by qianjia on 14/11/28. | |||||
*/ | |||||
public class WxMpOAuth2AccessTokenAdapter implements JsonDeserializer<WxMpOAuth2AccessToken> { | public class WxMpOAuth2AccessTokenAdapter implements JsonDeserializer<WxMpOAuth2AccessToken> { | ||||
public WxMpOAuth2AccessToken deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws | public WxMpOAuth2AccessToken deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws | ||||
@@ -10,8 +10,6 @@ package me.chanjar.weixin.mp.util.json; | |||||
import com.google.gson.*; | import com.google.gson.*; | ||||
import me.chanjar.weixin.common.util.json.GsonHelper; | import me.chanjar.weixin.common.util.json.GsonHelper; | ||||
import me.chanjar.weixin.mp.bean.result.WxMpMassSendResult; | |||||
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; | |||||
import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult; | import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult; | ||||
import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||