Переглянути джерело

Merge branch 'develop'

master
Daniel Qian 10 роки тому
джерело
коміт
da87d14f8a
100 змінених файлів з 3839 додано та 1747 видалено
  1. +201
    -0
      LICENSE
  2. +16
    -2
      README.md
  3. +215
    -212
      pom.xml
  4. +0
    -229
      pom.xml.versionsBackup
  5. +66
    -61
      weixin-java-common/pom.xml
  6. +0
    -68
      weixin-java-common/pom.xml.versionsBackup
  7. +31
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/Constants.java
  8. +68
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java
  9. +113
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java
  10. +80
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/LocalStrings.properties
  11. +346
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSession.java
  12. +45
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionFacade.java
  13. +321
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java
  14. +57
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java
  15. +17
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java
  16. +21
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java
  17. +27
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageDuplicateChecker.java
  18. +98
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateChecker.java
  19. +28
    -6
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
  20. +251
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java
  21. +0
    -24
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/AdapterCDATA.java
  22. +0
    -21
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/MediaIdMarshaller.java
  23. +12
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataConverter.java
  24. +50
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java
  25. +8
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamMediaIdConverter.java
  26. +132
    -0
      weixin-java-common/src/test/java/me/chanjar/weixin/common/session/SessionTest.java
  27. +35
    -0
      weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateCheckerTest.java
  28. +16
    -0
      weixin-java-common/src/test/resources/logback-test.xml
  29. +10
    -8
      weixin-java-common/src/test/resources/testng.xml
  30. +1
    -1
      weixin-java-cp/pom.xml
  31. +0
    -73
      weixin-java-cp/pom.xml.versionsBackup
  32. +13
    -5
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java
  33. +21
    -13
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java
  34. +12
    -7
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageHandler.java
  35. +13
    -7
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageInterceptor.java
  36. +124
    -18
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageRouter.java
  37. +63
    -9
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
  38. +112
    -69
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java
  39. +3
    -3
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java
  40. +18
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
  41. +79
    -95
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java
  42. +6
    -11
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java
  43. +14
    -24
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java
  44. +15
    -18
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessage.java
  45. +6
    -11
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java
  46. +13
    -19
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java
  47. +6
    -11
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java
  48. +3
    -3
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java
  49. +2
    -9
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java
  50. +4
    -3
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
  51. +10
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
  52. +103
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
  53. +0
    -93
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XmlTransformer.java
  54. +11
    -22
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java
  55. +3
    -5
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java
  56. +66
    -0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java
  57. +3
    -5
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java
  58. +6
    -8
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java
  59. +2
    -4
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java
  60. +141
    -4
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java
  61. +12
    -12
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java
  62. +4
    -8
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java
  63. +1
    -2
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java
  64. +9
    -15
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java
  65. +3
    -7
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java
  66. +3
    -5
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpEndpointServlet.java
  67. +0
    -3
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpOAuth2Servlet.java
  68. +16
    -0
      weixin-java-cp/src/test/resources/logback-test.xml
  69. +1
    -1
      weixin-java-cp/src/test/resources/test-config.sample.xml
  70. +24
    -23
      weixin-java-cp/src/test/resources/testng.xml
  71. +1
    -1
      weixin-java-mp/pom.xml
  72. +0
    -72
      weixin-java-mp/pom.xml.versionsBackup
  73. +39
    -7
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java
  74. +44
    -11
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java
  75. +9
    -5
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageHandler.java
  76. +11
    -5
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageInterceptor.java
  77. +126
    -21
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java
  78. +75
    -10
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
  79. +136
    -71
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java
  80. +0
    -32
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMpXmlOutImageMessage.java
  81. +0
    -3
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpTemplateMessage.java
  82. +81
    -99
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlMessage.java
  83. +27
    -0
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessage.java
  84. +14
    -24
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMessage.java
  85. +17
    -23
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMusicMessage.java
  86. +15
    -18
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutNewsMessage.java
  87. +6
    -11
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutTextMessage.java
  88. +13
    -19
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVideoMessage.java
  89. +9
    -14
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVoiceMessage.java
  90. +4
    -4
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/ImageBuilder.java
  91. +1
    -5
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java
  92. +1
    -1
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/QrCodeRequestExecutor.java
  93. +0
    -5
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpCustomMessageGsonAdapter.java
  94. +0
    -5
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGroupGsonAdapter.java
  95. +0
    -1
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java
  96. +0
    -5
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassGroupMessageGsonAdapter.java
  97. +0
    -5
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java
  98. +0
    -5
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassOpenIdsMessageGsonAdapter.java
  99. +0
    -4
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpOAuth2AccessTokenAdapter.java
  100. +0
    -2
      weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpSemanticQueryResultAdapter.java

+ 201
- 0
LICENSE Переглянути файл

@@ -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.

+ 16
- 2
README.md Переглянути файл

@@ -19,7 +19,7 @@ weixin-java-tools
<dependency>
<groupId>me.chanjar</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>1.0.6</version>
<version>1.0.7</version>
</dependency>
```

@@ -29,10 +29,24 @@ weixin-java-tools
<dependency>
<groupId>me.chanjar</groupId>
<artifactId>weixin-java-cp</artifactId>
<version>1.0.6</version>
<version>1.0.7</version>
</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``。需要做的是:


+ 215
- 212
pom.xml Переглянути файл

@@ -1,229 +1,232 @@
<?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.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>
<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>
</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>
<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>
</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>

+ 0
- 229
pom.xml.versionsBackup Переглянути файл

@@ -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>

+ 66
- 61
weixin-java-common/pom.xml Переглянути файл

@@ -1,68 +1,73 @@
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>me.chanjar</groupId>
<artifactId>weixin-java-parent</artifactId>
<version>1.0.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>

+ 0
- 68
weixin-java-common/pom.xml.versionsBackup Переглянути файл

@@ -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>

+ 31
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/Constants.java Переглянути файл

@@ -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";

}

+ 68
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java Переглянути файл

@@ -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);
}

+ 113
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java Переглянути файл

@@ -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);

}

+ 80
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/LocalStrings.properties Переглянути файл

@@ -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

+ 346
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSession.java Переглянути файл

@@ -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;
}

}

+ 45
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionFacade.java Переглянути файл

@@ -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();
}

}

+ 321
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java Переглянути файл

@@ -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;

}

}

+ 57
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java Переглянути файл

@@ -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;
}
}

+ 17
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java Переглянути файл

@@ -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();

}

+ 21
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java Переглянути файл

@@ -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);


}

+ 27
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageDuplicateChecker.java Переглянути файл

@@ -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);

}

+ 98
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateChecker.java Переглянути файл

@@ -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;
}


}

+ 28
- 6
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java Переглянути файл

@@ -10,27 +10,49 @@ import java.util.Arrays;
public class SHA1 {

/**
* 生成SHA1签名
* 串接arr参数,生成sha1 digest
*
* @param arr
* @return
*/
public static String gen(String... arr) throws NoSuchAlgorithmException {
Arrays.sort(arr);
StringBuilder sb = new StringBuilder();
for(String a : arr) {
for (String a : arr) {
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");
sha1.update(sb.toString().getBytes());
sha1.update(str.getBytes());
byte[] output = sha1.digest();
return bytesToHex(output);
}


protected static String bytesToHex(byte[] b) {
char hexDigit[] = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
char hexDigit[] = { '0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
StringBuffer buf = new StringBuffer();
for (int j = 0; j < b.length; j++) {
buf.append(hexDigit[(b[j] >> 4) & 0x0f]);


+ 251
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java Переглянути файл

@@ -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);
}
}

+ 0
- 24
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/AdapterCDATA.java Переглянути файл

@@ -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;
}
}

+ 0
- 21
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/MediaIdMarshaller.java Переглянути файл

@@ -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;
}

}

+ 12
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataConverter.java Переглянути файл

@@ -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) + "]]>";
}

}

+ 50
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java Переглянути файл

@@ -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;
}

}

+ 8
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamMediaIdConverter.java Переглянути файл

@@ -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>";
}
}

+ 132
- 0
weixin-java-common/src/test/java/me/chanjar/weixin/common/session/SessionTest.java Переглянути файл

@@ -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");

}
}

+ 35
- 0
weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateCheckerTest.java Переглянути файл

@@ -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);
}

}

}

+ 16
- 0
weixin-java-common/src/test/resources/logback-test.xml Переглянути файл

@@ -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>

+ 10
- 8
weixin-java-common/src/test/resources/testng.xml Переглянути файл

@@ -1,12 +1,14 @@
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name="Weixin-java-tool-suite" verbose="1">
<test name="Bean_Test">
<classes>
<class name="me.chanjar.weixin.common.bean.WxAccessTokenTest" />
<class name="me.chanjar.weixin.common.bean.WxErrorTest" />
<class name="me.chanjar.weixin.common.bean.WxMenuTest" />
<class name="me.chanjar.weixin.common.util.crypto.WxCryptUtilTest" />
</classes>
</test>
<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>

+ 1
- 1
weixin-java-cp/pom.xml Переглянути файл

@@ -6,7 +6,7 @@
<parent>
<groupId>me.chanjar</groupId>
<artifactId>weixin-java-parent</artifactId>
<version>1.0.6</version>
<version>1.1.0-SNAPSHOT</version>
</parent>

<artifactId>weixin-java-cp</artifactId>


+ 0
- 73
weixin-java-cp/pom.xml.versionsBackup Переглянути файл

@@ -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>

+ 13
- 5
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java Переглянути файл

@@ -9,12 +9,20 @@ import me.chanjar.weixin.common.bean.WxAccessToken;
*/
public interface WxCpConfigStorage {

public String getAccessToken();

public boolean isAccessTokenExpired();

/**
* 强制将access token过期掉
*/
public void expireAccessToken();

public void updateAccessToken(WxAccessToken accessToken);
public void updateAccessToken(String accessToken, int expiresIn);
public String getAccessToken();


public String getCorpId();
public String getCorpSecret();
@@ -25,7 +33,7 @@ public interface WxCpConfigStorage {

public String getAesKey();

public int getExpiresIn();
public long getExpiresTime();

public String getOauth2redirectUri();



+ 21
- 13
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java Переглянути файл

@@ -16,7 +16,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
protected String accessToken;
protected String aesKey;
protected String agentId;
protected int expiresIn;
protected long expiresTime;

protected String oauth2redirectUri;

@@ -25,17 +25,25 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
protected String http_proxy_username;
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());
}
public void updateAccessToken(String accessToken, int expiresIn) {
public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) {
this.accessToken = accessToken;
this.expiresIn = expiresIn;
}

public String getAccessToken() {
return this.accessToken;
this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l;
}

public String getCorpId() {
@@ -50,8 +58,8 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
return this.token;
}

public int getExpiresIn() {
return this.expiresIn;
public long getExpiresTime() {
return this.expiresTime;
}

public void setCorpId(String corpId) {
@@ -78,8 +86,8 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
this.accessToken = accessToken;
}

public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
public void setExpiresTime(long expiresTime) {
this.expiresTime = expiresTime;
}

public String getAgentId() {
@@ -140,7 +148,7 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage {
", accessToken='" + accessToken + '\'' +
", aesKey='" + aesKey + '\'' +
", agentId='" + agentId + '\'' +
", expiresIn=" + expiresIn +
", expiresTime=" + expiresTime +
", http_proxy_host='" + http_proxy_host + '\'' +
", http_proxy_port=" + http_proxy_port +
", http_proxy_username='" + http_proxy_username + '\'' +


+ 12
- 7
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageHandler.java Переглянути файл

@@ -1,24 +1,29 @@
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.WxCpXmlOutMessage;

import java.util.Map;

/**
* 处理微信推送消息的处理器接口
* @author Daniel Qian
*
* @author Daniel Qian
*/
public interface WxCpMessageHandler {

/**
*
* @param wxMessage
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个
* @param wxCpService
* @param sessionManager
* @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);

}

+ 13
- 7
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageInterceptor.java Переглянути файл

@@ -1,23 +1,29 @@
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 java.util.Map;

/**
* 微信消息拦截器,可以用来做验证
* @author Daniel Qian
*
* @author Daniel Qian
*/
public interface WxCpMessageInterceptor {

/**
* 拦截微信消息
*
* @param wxMessage
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个
* @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);

}

+ 124
- 18
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageRouter.java Переглянути файл

@@ -1,16 +1,23 @@
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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Pattern;

import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;

/**
* <pre>
* 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理
@@ -41,22 +48,58 @@ import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
*/
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 ExecutorService executorService;

private WxMessageDuplicateChecker messageDuplicateChecker;

private WxSessionManager sessionManager;

public WxCpMessageRouter(WxCpService wxCpService) {
this.wxCpService = wxCpService;
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
*/
public Rule rule() {
return new Rule(this, wxCpService);
return new Rule(this, wxCpService, sessionManager);
}

/**
@@ -72,6 +115,11 @@ public class WxCpMessageRouter {
* @param wxMessage
*/
public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) {
if (isDuplicateMessage(wxMessage)) {
// 如果是重复消息,那么就不做处理
return null;
}
final List<Rule> matchRules = new ArrayList<Rule>();
// 收集匹配的规则
for (final Rule rule : rules) {
@@ -88,27 +136,84 @@ public class WxCpMessageRouter {
}

WxCpXmlOutMessage res = null;
final List<Future> futures = new ArrayList<Future>();
for (final Rule rule : matchRules) {
// 返回最后一个非异步的rule的执行结果
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 {
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;
}

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 {

private final WxCpMessageRouter routerBuilder;

private final WxCpService wxCpService;

private final WxSessionManager sessionManager;

private boolean async = true;

private String fromUser;
@@ -131,9 +236,10 @@ public class WxCpMessageRouter {

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.wxCpService = wxCpService;
this.sessionManager = sessionManager;
}

/**
@@ -311,7 +417,7 @@ public class WxCpMessageRouter {
Map<String, Object> context = new HashMap<String, Object>();
// 如果拦截器不通过
for (WxCpMessageInterceptor interceptor : this.interceptors) {
if (!interceptor.intercept(wxMessage, context, wxCpService)) {
if (!interceptor.intercept(wxMessage, context, wxCpService, sessionManager)) {
return null;
}
}
@@ -320,7 +426,7 @@ public class WxCpMessageRouter {
WxCpXmlOutMessage res = null;
for (WxCpMessageHandler handler : this.handlers) {
// 返回最后handler的结果
res = handler.handle(wxMessage, context, wxCpService);
res = handler.handle(wxMessage, context, wxCpService, sessionManager);
}
return res;
}


+ 63
- 9
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java Переглянути файл

@@ -1,17 +1,20 @@
package me.chanjar.weixin.cp.api;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;

import me.chanjar.weixin.common.bean.WxMenu;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.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.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;
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
@@ -42,6 +45,14 @@ public interface WxCpService {
*/
public void userAuthenticated(String userId) throws WxErrorException;

/**
* 获取access_token, 不强制刷新access_token
* @see #getAccessToken(boolean)
* @return
* @throws WxErrorException
*/
public String getAccessToken() throws WxErrorException;

/**
* <pre>
* 获取access_token,本方法线程安全
@@ -50,10 +61,11 @@ public interface WxCpService {
* 程序员在非必要情况下尽量不要主动调用此方法
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
* </pre>
*
* @param forceRefresh 强制刷新
* @return
* @throws me.chanjar.weixin.common.exception.WxErrorException
*/
public void accessTokenRefresh() throws WxErrorException;
public String getAccessToken(boolean forceRefresh) throws WxErrorException;

/**
* <pre>
@@ -343,4 +355,46 @@ public interface WxCpService {
* @param 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);
}

+ 112
- 69
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java Переглянути файл

@@ -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.WxMediaUploadResult;
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.crypto.SHA1;
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.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
@@ -41,25 +46,28 @@ import java.io.InputStream;
import java.io.StringReader;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

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 final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>();

protected CloseableHttpClient httpClient;

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) {
try {
return SHA1.gen(wxCpConfigStorage.getToken(), timestamp, nonce, data).equals(msgSignature);
@@ -73,45 +81,44 @@ public class WxCpServiceImpl implements WxCpService {
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 {
@@ -369,10 +376,34 @@ public class WxCpServiceImpl implements WxCpService {
* @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;
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
@@ -387,30 +418,10 @@ public class WxCpServiceImpl implements WxCpService {
* 42001 access_token超时
*/
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
accessTokenRefresh();
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
wxCpConfigStorage.expireAccessToken();
return execute(executor, uri, data);
}
/**
* -1 系统繁忙, 1000ms后重试
*/
if (error.getErrorCode() == -1) {
if (retryTimes.get() == null) {
retryTimes.set(0);
}
if (retryTimes.get() > 4) {
retryTimes.set(0);
throw new RuntimeException("微信服务端异常,超出重试次数");
}
int sleepMillis = 1000 * (1 << retryTimes.get());
try {
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试");
Thread.sleep(sleepMillis);
retryTimes.set(retryTimes.get() + 1);
return execute(executor, uri, data);
} catch (InterruptedException e1) {
throw new RuntimeException(e1);
}
}
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
@@ -421,7 +432,6 @@ public class WxCpServiceImpl implements WxCpService {
throw new RuntimeException(e);
}
}

protected CloseableHttpClient getHttpclient() {
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;
}

}

+ 3
- 3
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java Переглянути файл

@@ -1,11 +1,11 @@
package me.chanjar.weixin.cp.bean;

import java.util.ArrayList;
import java.util.List;

import me.chanjar.weixin.cp.bean.messagebuilder.*;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.util.ArrayList;
import java.util.List;

/**
* 消息
* @author Daniel Qian


+ 18
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java Переглянути файл

@@ -21,6 +21,8 @@ public class WxCpUser {
private String tel;
private String email;
private String weiXinId;
private String avatar;
private Integer status;
private final List<Attr> extAttrs = new ArrayList<Attr>();

public String getUserId() {
@@ -95,6 +97,22 @@ public class WxCpUser {
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) {
this.extAttrs.add(new Attr(name, value));
}


+ 79
- 95
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java Переглянути файл

@@ -1,14 +1,13 @@
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.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 javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -25,103 +24,102 @@ import java.util.List;
*
* @author Daniel Qian
*/
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("xml")
public class WxCpXmlMessage {

///////////////////////
// 以下都是微信推送过来的消息的xml的element所对应的属性
///////////////////////

@XmlElement(name="AgentID")
@XStreamAlias("AgentID")
private Integer agentId;

@XmlElement(name = "ToUserName")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("ToUserName")
@XStreamConverter(value=XStreamCDataConverter.class)
private String toUserName;

@XmlElement(name = "FromUserName")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("FromUserName")
@XStreamConverter(value=XStreamCDataConverter.class)
private String fromUserName;

@XmlElement(name = "CreateTime")
@XStreamAlias("CreateTime")
private Long createTime;

@XmlElement(name = "MsgType")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("MsgType")
@XStreamConverter(value=XStreamCDataConverter.class)
private String msgType;

@XmlElement(name = "Content")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Content")
@XStreamConverter(value=XStreamCDataConverter.class)
private String content;

@XmlElement(name = "MsgId")
@XStreamAlias("MsgId")
private Long msgId;

@XmlElement(name = "PicUrl")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("PicUrl")
@XStreamConverter(value=XStreamCDataConverter.class)
private String picUrl;

@XmlElement(name = "MediaId")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("MediaId")
@XStreamConverter(value=XStreamCDataConverter.class)
private String mediaId;

@XmlElement(name = "Format")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Format")
@XStreamConverter(value=XStreamCDataConverter.class)
private String format;

@XmlElement(name = "ThumbMediaId")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("ThumbMediaId")
@XStreamConverter(value=XStreamCDataConverter.class)
private String thumbMediaId;

@XmlElement(name = "Location_X")
@XStreamAlias("Location_X")
private Double locationX;

@XmlElement(name = "Location_Y")
@XStreamAlias("Location_Y")
private Double locationY;

@XmlElement(name = "Scale")
@XStreamAlias("Scale")
private Double scale;

@XmlElement(name = "Label")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Label")
@XStreamConverter(value=XStreamCDataConverter.class)
private String label;

@XmlElement(name = "Title")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Title")
@XStreamConverter(value=XStreamCDataConverter.class)
private String title;

@XmlElement(name = "Description")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Description")
@XStreamConverter(value=XStreamCDataConverter.class)
private String description;

@XmlElement(name = "Url")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Url")
@XStreamConverter(value=XStreamCDataConverter.class)
private String url;

@XmlElement(name = "Event")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Event")
@XStreamConverter(value=XStreamCDataConverter.class)
private String event;

@XmlElement(name = "EventKey")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("EventKey")
@XStreamConverter(value=XStreamCDataConverter.class)
private String eventKey;

@XmlElement(name = "Ticket")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Ticket")
@XStreamConverter(value=XStreamCDataConverter.class)
private String ticket;

@XmlElement(name = "Latitude")
@XStreamAlias("Latitude")
private Double latitude;

@XmlElement(name = "Longitude")
@XStreamAlias("Longitude")
private Double longitude;

@XmlElement(name = "Precision")
@XStreamAlias("Precision")
private Double precision;

@XmlElement(name = "Recognition")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Recognition")
@XStreamConverter(value=XStreamCDataConverter.class)
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;
/**
* group_id下粉丝数;或者openid_list中的粉丝数
*/
@XmlElement(name = "TotalCount")
@XStreamAlias("TotalCount")
private Integer totalCount;
/**
* 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount
*/
@XmlElement(name = "FilterCount")
@XStreamAlias("FilterCount")
private Integer filterCount;
/**
* 发送成功的粉丝数
*/
@XmlElement(name = "SentCount")
@XStreamAlias("SentCount")
private Integer sentCount;
/**
* 发送失败的粉丝数
*/
@XmlElement(name = "ErrorCount")
@XStreamAlias("ErrorCount")
private Integer errorCount;

@XmlElement(name = "ScanCodeInfo")
@XStreamAlias("ScanCodeInfo")
private ScanCodeInfo scanCodeInfo = new ScanCodeInfo();

@XmlElement(name = "SendPicsInfo")
@XStreamAlias("SendPicsInfo")
private SendPicsInfo sendPicsInfo = new SendPicsInfo();

@XmlElement(name = "SendLocationInfo")
@XStreamAlias("SendLocationInfo")
private SendLocationInfo sendLocationInfo = new SendLocationInfo();

public Integer getAgentId() {
@@ -390,19 +388,11 @@ public class WxCpXmlMessage {
}

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) {
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;
}

@XmlRootElement(name = "ScanCodeInfo")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("ScanCodeInfo")
public static class ScanCodeInfo {

@XmlElement(name = "ScanType")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("ScanType")
@XStreamConverter(value=XStreamCDataConverter.class)
private String scanType;

@XmlElement(name = "ScanResult")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("ScanResult")
@XStreamConverter(value=XStreamCDataConverter.class)
private String scanResult;

/**
@@ -538,15 +527,13 @@ public class WxCpXmlMessage {

}

@XmlRootElement(name = "SendPicsInfo")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("SendPicsInfo")
public static class SendPicsInfo {

@XmlElement(name = "Count")
@XStreamAlias("Count")
private Long count;

@XmlElementWrapper(name="PicList")
@XmlElement(name = "item")
@XStreamAlias("PicList")
protected final List<Item> picList = new ArrayList<Item>();

public Long getCount() {
@@ -561,13 +548,11 @@ public class WxCpXmlMessage {
return picList;
}

@XmlRootElement(name = "item")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "WxXmlMessage.SendPicsInfo.Item")
@XStreamAlias("item")
public static class Item {

@XmlElement(name = "PicMd5Sum")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("PicMd5Sum")
@XStreamConverter(value=XStreamCDataConverter.class)
private String PicMd5Sum;

public String getPicMd5Sum() {
@@ -580,28 +565,27 @@ public class WxCpXmlMessage {
}
}

@XmlRootElement(name = "SendLocationInfo")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("SendLocationInfo")
public static class SendLocationInfo {

@XmlElement(name = "Location_X")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Location_X")
@XStreamConverter(value=XStreamCDataConverter.class)
private String locationX;

@XmlElement(name = "Location_Y")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Location_Y")
@XStreamConverter(value=XStreamCDataConverter.class)
private String locationY;

@XmlElement(name = "Scale")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Scale")
@XStreamConverter(value=XStreamCDataConverter.class)
private String scale;

@XmlElement(name = "Label")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Label")
@XStreamConverter(value=XStreamCDataConverter.class)
private String label;

@XmlElement(name = "Poiname")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Poiname")
@XStreamConverter(value=XStreamCDataConverter.class)
private String poiname;

public String getLocationX() {


+ 6
- 11
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java Переглянути файл

@@ -1,20 +1,15 @@
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.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 {
@XmlElement(name="Image")
@XmlJavaTypeAdapter(MediaIdMarshaller.class)
@XStreamAlias("Image")
@XStreamConverter(value=XStreamMediaIdConverter.class)
private String mediaId;

public WxCpXmlOutImageMessage() {


+ 14
- 24
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java Переглянути файл

@@ -1,35 +1,29 @@
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.bean.outxmlbuilder.*;
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;
@XmlElement(name="FromUserName")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("FromUserName")
@XStreamConverter(value=XStreamCDataConverter.class)
protected String fromUserName;
@XmlElement(name="CreateTime")
@XStreamAlias("CreateTime")
protected Long createTime;
@XmlElement(name="MsgType")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("MsgType")
@XStreamConverter(value=XStreamCDataConverter.class)
protected String msgType;

public String getToUserName() {
@@ -65,11 +59,7 @@ public class WxCpXmlOutMessage {
}
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);
}

/**


+ 15
- 18
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessage.java Переглянути файл

@@ -1,22 +1,20 @@
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.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.List;

@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("xml")
public class WxCpXmlOutNewsMessage extends WxCpXmlOutMessage {

@XmlElement(name = "ArticleCount")
@XStreamAlias("ArticleCount")
protected int articleCount;
@XmlElementWrapper(name="Articles")
@XmlElement(name = "item")
@XStreamAlias("Articles")
protected final List<Item> articles = new ArrayList<Item>();
public WxCpXmlOutNewsMessage() {
@@ -37,24 +35,23 @@ public class WxCpXmlOutNewsMessage extends WxCpXmlOutMessage {
}
@XmlRootElement(name = "Item")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("item")
public static class Item {
@XmlElement(name = "Title")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Title")
@XStreamConverter(value=XStreamCDataConverter.class)
private String Title;

@XmlElement(name = "Description")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Description")
@XStreamConverter(value=XStreamCDataConverter.class)
private String Description;

@XmlElement(name="PicUrl")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("PicUrl")
@XStreamConverter(value=XStreamCDataConverter.class)
private String PicUrl;
@XmlElement(name="Url")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Url")
@XStreamConverter(value=XStreamCDataConverter.class)
private String Url;
public String getTitle() {


+ 6
- 11
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java Переглянути файл

@@ -1,20 +1,15 @@
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.util.xml.AdapterCDATA;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;

@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("xml")
public class WxCpXmlOutTextMessage extends WxCpXmlOutMessage {
@XmlElement(name="Content")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Content")
@XStreamConverter(value=XStreamCDataConverter.class)
private String content;

public WxCpXmlOutTextMessage() {


+ 13
- 19
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java Переглянути файл

@@ -1,19 +1,14 @@
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.util.xml.AdapterCDATA;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;

@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("xml")
public class WxCpXmlOutVideoMessage extends WxCpXmlOutMessage {

@XmlElement(name = "Video")
@XStreamAlias("Video")
protected final Video video = new Video();

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;

@XmlElement(name = "Title")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Title")
@XStreamConverter(value=XStreamCDataConverter.class)
private String title;

@XmlElement(name = "Description")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Description")
@XStreamConverter(value=XStreamCDataConverter.class)
private String description;

public String getMediaId() {


+ 6
- 11
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java Переглянути файл

@@ -1,20 +1,15 @@
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.util.xml.MediaIdMarshaller;
import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter;

@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("xml")
public class WxCpXmlOutVoiceMessage extends WxCpXmlOutMessage {
@XmlElement(name="Voice")
@XmlJavaTypeAdapter(MediaIdMarshaller.class)
@XStreamAlias("Voice")
@XStreamConverter(value=XStreamMediaIdConverter.class)
private String mediaId;

public WxCpXmlOutVoiceMessage() {


+ 3
- 3
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java Переглянути файл

@@ -1,11 +1,11 @@
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.Item;

import java.util.ArrayList;
import java.util.List;

/**
* 图文消息builder
* @author Daniel Qian


+ 2
- 9
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java Переглянути файл

@@ -8,18 +8,11 @@
*/
package me.chanjar.weixin.cp.util.json;

import java.lang.reflect.Type;

import com.google.gson.*;
import me.chanjar.weixin.common.util.json.GsonHelper;
import me.chanjar.weixin.cp.bean.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


+ 4
- 3
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java Переглянути файл

@@ -2,11 +2,12 @@ package me.chanjar.weixin.cp.util.json;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.util.json.WxMediaUploadResultAdapter;
import me.chanjar.weixin.common.util.json.WxErrorAdapter;
import me.chanjar.weixin.cp.bean.*;
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 {



+ 10
- 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java Переглянути файл

@@ -8,12 +8,12 @@
*/
package me.chanjar.weixin.cp.util.json;

import java.lang.reflect.Type;

import com.google.gson.*;
import me.chanjar.weixin.common.util.json.GsonHelper;
import me.chanjar.weixin.cp.bean.WxCpUser;

import java.lang.reflect.Type;

/**
* @author Daniel Qian
*/
@@ -49,6 +49,8 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
user.setTel(GsonHelper.getString(o, "tel"));
user.setEmail(GsonHelper.getString(o, "email"));
user.setWeiXinId(GsonHelper.getString(o, "weixinid"));
user.setAvatar(GsonHelper.getString(o, "avatar"));
user.setStatus(GsonHelper.getInteger(o, "status"));

if (GsonHelper.isNotNull(o.get("extattr"))) {
JsonArray attrJsonElements = o.get("extattr").getAsJsonObject().get("attrs").getAsJsonArray();
@@ -97,6 +99,12 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
if (user.getWeiXinId() != null) {
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) {
JsonArray attrsJsonArray = new JsonArray();
for (WxCpUser.Attr attr : user.getExtAttrs()) {


+ 103
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java Переглянути файл

@@ -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;
}

}

+ 0
- 93
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XmlTransformer.java Переглянути файл

@@ -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);
}
}

}

+ 11
- 22
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java Переглянути файл

@@ -1,23 +1,17 @@
package me.chanjar.weixin.cp.api;

import java.io.InputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;

import com.google.inject.Binder;
import com.google.inject.Module;
import org.xml.sax.InputSource;
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 {

@Override
public void configure(Binder binder) {
try {
InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml");
WxXmlCpInMemoryConfigStorage config = fromXml(WxXmlCpInMemoryConfigStorage.class, is1);
WxCpServiceImpl wxService = new WxCpServiceImpl();
@@ -25,21 +19,16 @@ public class ApiTestModule implements Module {

binder.bind(WxCpServiceImpl.class).toInstance(wxService);
binder.bind(WxCpConfigStorage.class).toInstance(config);
} catch (JAXBException e) {
throw new RuntimeException(e);
}
}

public static <T> T fromXml(Class<T> clazz, InputStream is) throws JAXBException {
Unmarshaller um = JAXBContext.newInstance(clazz).createUnmarshaller();
InputSource inputSource = new InputSource(is);
inputSource.setEncoding("utf-8");
T object = (T) um.unmarshal(inputSource);
return object;
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 {
protected String userId;


+ 3
- 5
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java Переглянути файл

@@ -1,14 +1,12 @@
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 org.testng.Assert;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import me.chanjar.weixin.common.exception.WxErrorException;

import com.google.inject.Inject;

/**
* 基础API测试
* @author Daniel Qian
@@ -24,7 +22,7 @@ public class WxCpBaseAPITest {
public void testRefreshAccessToken() throws WxErrorException {
WxCpConfigStorage configStorage = wxService.wxCpConfigStorage;
String before = configStorage.getAccessToken();
wxService.accessTokenRefresh();
wxService.getAccessToken(false);

String after = configStorage.getAccessToken();



+ 66
- 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java Переглянути файл

@@ -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();
}

}

+ 3
- 5
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java Переглянути файл

@@ -1,15 +1,13 @@
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 org.testng.Assert;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import me.chanjar.weixin.common.exception.WxErrorException;

import com.google.inject.Inject;
import java.util.List;

/**
* 测试部门接口


+ 6
- 8
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java Переглянути файл

@@ -1,20 +1,18 @@
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.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.exception.WxErrorException;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import me.chanjar.weixin.common.exception.WxErrorException;

import com.google.inject.Inject;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

/**
* 测试多媒体文件上传下载


+ 2
- 4
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java Переглянути файл

@@ -1,14 +1,12 @@
package me.chanjar.weixin.cp.api;

import com.google.inject.Inject;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpMessage;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import me.chanjar.weixin.common.exception.WxErrorException;

import com.google.inject.Inject;

/***
* 测试发送消息
* @author Daniel Qian


+ 141
- 4
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java Переглянути файл

@@ -1,14 +1,16 @@
package me.chanjar.weixin.cp.api;

import java.util.Map;

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.WxCpXmlOutMessage;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.Map;

/**
* 测试消息路由器
* @author Daniel Qian
@@ -67,7 +69,8 @@ public class WxCpMessageRouterTest {
final WxCpMessageRouter router = new WxCpMessageRouter(null);
router.rule().handler(new WxCpMessageHandler() {
@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;
}
}).end();
@@ -149,11 +152,145 @@ public class WxCpMessageRouterTest {
}

@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(',');
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;
}


}

}

+ 12
- 12
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java Переглянути файл

@@ -1,9 +1,9 @@
package me.chanjar.weixin.cp.api;

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.WxCpUser;
import me.chanjar.weixin.common.exception.WxErrorException;
import org.testng.Assert;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;
@@ -26,30 +26,30 @@ public class WxCpUserAPITest {

public void testUserCreate() throws WxErrorException {
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.setEmail("yangxiaohe@ddd.com");
user.setEmail("none@none.com");
user.setGender("女");
user.setMobile("13564684979");
user.setPosition("老婆");
user.setMobile("13560084979");
user.setPosition("woman");
user.setTel("3300393");
user.addExtAttr("爱好", "老公");
user.addExtAttr("爱好", "table");
wxCpService.userCreate(user);
}

@Test(dependsOnMethods = "testUserCreate")
public void testUserUpdate() throws WxErrorException {
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);
}

@Test(dependsOnMethods = "testUserUpdate")
public void testUserGet() throws WxErrorException {
WxCpUser user = wxCpService.userGet("xiaohe.yang");
WxCpUser user = wxCpService.userGet("some.woman");
Assert.assertNotNull(user);
}

@@ -61,6 +61,6 @@ public class WxCpUserAPITest {

@Test(dependsOnMethods = "testDepartGetUsers")
public void testUserDelete() throws WxErrorException {
wxCpService.userDelete("xiaohe.yang");
wxCpService.userDelete("some.woman");
}
}

+ 4
- 8
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java Переглянути файл

@@ -1,19 +1,15 @@
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.bean.WxMenu;
import me.chanjar.weixin.common.bean.WxMenu.WxMenuButton;
import me.chanjar.weixin.common.exception.WxErrorException;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Guice;
import org.testng.annotations.Test;

import com.google.inject.Inject;

import me.chanjar.weixin.common.bean.WxMenu.WxMenuButton;
import me.chanjar.weixin.common.exception.WxErrorException;

/**
* 测试菜单
* @author Daniel Qian
@@ -42,7 +38,7 @@ public class WxMenuAPITest {
}
@DataProvider(name="menu")
public Object[][] getMenu() throws JAXBException {
public Object[][] getMenu() {
WxMenu menu = new WxMenu();
WxMenuButton button1 = new WxMenuButton();
button1.setType(WxConsts.BUTTON_CLICK);


+ 1
- 2
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java Переглянути файл

@@ -1,11 +1,10 @@
package me.chanjar.weixin.cp.bean;

import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.cp.bean.WxCpMessage.WxArticle;
import org.testng.Assert;
import org.testng.annotations.Test;

import me.chanjar.weixin.cp.bean.WxCpMessage.WxArticle;

@Test
public class WxCpMessageTest {



+ 9
- 15
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java Переглянути файл

@@ -1,35 +1,29 @@
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 org.xml.sax.InputSource;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import java.io.InputStream;

/**
* @author Daniel Qian
*/
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("xml")
class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage {

@Override
public String toString() {
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);
}

}

+ 3
- 7
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java Переглянути файл

@@ -1,5 +1,6 @@
package me.chanjar.weixin.cp.demo;

import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.*;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
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.ServletHolder;

import javax.xml.bind.JAXBException;
import java.io.InputStream;
import java.util.Map;

@@ -37,7 +37,6 @@ public class WxCpDemoServer {
}

private static void initWeixin() {
try {
InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml");
WxCpDemoInMemoryConfigStorage config = WxCpDemoInMemoryConfigStorage.fromXml(is1);

@@ -48,7 +47,7 @@ public class WxCpDemoServer {
WxCpMessageHandler handler = new WxCpMessageHandler() {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context,
WxCpService wxCpService) {
WxCpService wxCpService, WxSessionManager sessionManager) {
WxCpXmlOutTextMessage m = WxCpXmlOutMessage
.TEXT()
.content("测试加密消息")
@@ -62,7 +61,7 @@ public class WxCpDemoServer {
WxCpMessageHandler oauth2handler = new WxCpMessageHandler() {
@Override
public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context,
WxCpService wxCpService) {
WxCpService wxCpService, WxSessionManager sessionManager) {
String href = "<a href=\"" + wxCpService.oauth2buildAuthorizationUrl(null)
+ "\">测试oauth2</a>";
return WxCpXmlOutMessage
@@ -87,8 +86,5 @@ public class WxCpDemoServer {
.end()
;

} catch (JAXBException e) {
throw new RuntimeException(e);
}
}
}

+ 3
- 5
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpEndpointServlet.java Переглянути файл

@@ -1,20 +1,18 @@
package me.chanjar.weixin.cp.demo;

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.WxCpXmlOutMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage;
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

/**
* @author Daniel Qian


+ 0
- 3
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpOAuth2Servlet.java Переглянути файл

@@ -10,9 +10,6 @@ import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

/**
* Created by qianjia on 14/11/28.
*/
public class WxCpOAuth2Servlet extends HttpServlet {

protected WxCpService wxCpService;


+ 16
- 0
weixin-java-cp/src/test/resources/logback-test.xml Переглянути файл

@@ -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>

+ 1
- 1
weixin-java-cp/src/test/resources/test-config.sample.xml Переглянути файл

@@ -5,7 +5,7 @@
<token>企业号应用Token</token>
<aesKey>企业号应用EncodingAESKey</aesKey>
<accessToken>可以不填写</accessToken>
<expiresIn>可以不填写</expiresIn>
<expiresTime>可以不填写</expiresTime>
<userId>企业号通讯录里的某个userid</userId>
<departmentId>企业号通讯录的某个部门id</departmentId>
<tagId>企业号通讯录里的某个tagid</tagId>


+ 24
- 23
weixin-java-cp/src/test/resources/testng.xml Переглянути файл

@@ -1,28 +1,29 @@
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >

<suite name="Weixin-java-tool-suite" verbose="1">
<test name="API_Test">
<classes>
<class name="me.chanjar.weixin.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>

+ 1
- 1
weixin-java-mp/pom.xml Переглянути файл

@@ -6,7 +6,7 @@
<parent>
<groupId>me.chanjar</groupId>
<artifactId>weixin-java-parent</artifactId>
<version>1.0.6</version>
<version>1.1.0-SNAPSHOT</version>
</parent>
<artifactId>weixin-java-mp</artifactId>
<name>WeiXin Java Tools - MP</name>


+ 0
- 72
weixin-java-mp/pom.xml.versionsBackup Переглянути файл

@@ -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>

+ 39
- 7
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java Переглянути файл

@@ -9,21 +9,52 @@ import me.chanjar.weixin.common.bean.WxAccessToken;
*/
public interface WxMpConfigStorage {

public String getAccessToken();

public boolean isAccessTokenExpired();

/**
* 强制将access token过期掉
*/
public void expireAccessToken();

/**
* 应该是线程安全的
* @param accessToken
*/
public void updateAccessToken(WxAccessToken accessToken);

/**
* 应该是线程安全的
* @param accessToken
* @param 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 getSecret();
public String getToken();

public String getAesKey();

public int getExpiresIn();
public long getExpiresTime();

public String getOauth2redirectUri();

@@ -33,6 +64,7 @@ public interface WxMpConfigStorage {

public String getHttp_proxy_username();


public String getHttp_proxy_password();

}

+ 44
- 11
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java Переглянути файл

@@ -9,12 +9,14 @@ import me.chanjar.weixin.common.bean.WxAccessToken;
*/
public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {

private static final long l = 7000 * 1000l;

protected String appId;
protected String secret;
protected String token;
protected String accessToken;
protected String aesKey;
protected int expiresIn;
protected long expiresTime;

protected String oauth2redirectUri;

@@ -23,17 +25,46 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
protected String http_proxy_username;
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());
}
public void updateAccessToken(String accessToken, int expiresIn) {
public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) {
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() {
@@ -48,8 +79,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
return this.token;
}

public int getExpiresIn() {
return this.expiresIn;
public long getExpiresTime() {
return this.expiresTime;
}

public void setAppId(String appId) {
@@ -76,8 +107,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
this.accessToken = accessToken;
}

public void setExpiresIn(int expiresIn) {
this.expiresIn = expiresIn;
public void setExpiresTime(long expiresTime) {
this.expiresTime = expiresTime;
}

@Override
@@ -129,11 +160,13 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage {
", token='" + token + '\'' +
", accessToken='" + accessToken + '\'' +
", aesKey='" + aesKey + '\'' +
", expiresIn=" + expiresIn +
", expiresTime=" + expiresTime +
", http_proxy_host='" + http_proxy_host + '\'' +
", http_proxy_port=" + http_proxy_port +
", http_proxy_username='" + http_proxy_username + '\'' +
", http_proxy_password='" + http_proxy_password + '\'' +
", jsapiTicket='" + jsapiTicket + '\'' +
", jsapiTicketExpiresTime='" + jsapiTicketExpiresTime + '\'' +
'}';
}



+ 9
- 5
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageHandler.java Переглянути файл

@@ -1,5 +1,6 @@
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.WxMpXmlOutMessage;

@@ -7,18 +8,21 @@ import java.util.Map;

/**
* 处理微信推送消息的处理器接口
* @author chanjarster
*
* @author Daniel Qian
*/
public interface WxMpMessageHandler {

/**
*
* @param wxMessage
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个
* @param wxMpService
* @param sessionManager
* @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);

}

+ 11
- 5
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageInterceptor.java Переглянути файл

@@ -1,23 +1,29 @@
package me.chanjar.weixin.mp.api;

import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.bean.WxMpXmlMessage;

import java.util.Map;

/**
* 微信消息拦截器,可以用来做验证
* @author qianjia
*
* @author Daniel Qian
*/
public interface WxMpMessageInterceptor {

/**
* 拦截微信消息
*
* @param wxMessage
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个
* @param context 上下文,如果handler或interceptor之间有信息要传递,可以用这个
* @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);

}

+ 126
- 21
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java Переглянути файл

@@ -1,14 +1,22 @@
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.WxMpXmlOutMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.regex.Pattern;

/**
@@ -36,27 +44,61 @@ import java.util.regex.Pattern;
* router.route(message);
*
* </pre>
* @author qianjia
*
*/
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 ExecutorService executorService;

private WxMessageDuplicateChecker messageDuplicateChecker;

private WxSessionManager sessionManager;

public WxMpMessageRouter(WxMpService wxMpService) {
this.wxMpService = wxMpService;
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
*/
public Rule rule() {
return new Rule(this, wxMpService);
return new Rule(this, wxMpService, sessionManager);
}

/**
@@ -72,6 +114,11 @@ public class WxMpMessageRouter {
* @param wxMessage
*/
public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) {
if (isDuplicateMessage(wxMessage)) {
// 如果是重复消息,那么就不做处理
return null;
}

final List<Rule> matchRules = new ArrayList<Rule>();
// 收集匹配的规则
for (final Rule rule : rules) {
@@ -82,33 +129,90 @@ public class WxMpMessageRouter {
}
}
}
if (matchRules.size() == 0) {
return null;
}
WxMpXmlOutMessage res = null;
final List<Future> futures = new ArrayList<Future>();
for (final Rule rule : matchRules) {
// 返回最后一个非异步的rule的执行结果
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 {
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;
}

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 {
private final WxMpMessageRouter routerBuilder;

private final WxMpService wxMpService;

private final WxSessionManager sessionManager;

private boolean async = true;

private String fromUser;
@@ -128,10 +232,11 @@ public class WxMpMessageRouter {
private List<WxMpMessageHandler> handlers = new ArrayList<WxMpMessageHandler>();
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.wxMpService = wxMpService;
this.sessionManager = sessionManager;
}
/**
@@ -297,7 +402,7 @@ public class WxMpMessageRouter {
Map<String, Object> context = new HashMap<String, Object>();
// 如果拦截器不通过
for (WxMpMessageInterceptor interceptor : this.interceptors) {
if (!interceptor.intercept(wxMessage, context, wxMpService)) {
if (!interceptor.intercept(wxMessage, context, wxMpService, sessionManager)) {
return null;
}
}
@@ -306,7 +411,7 @@ public class WxMpMessageRouter {
WxMpXmlOutMessage res = null;
for (WxMpMessageHandler handler : this.handlers) {
// 返回最后handler的结果
res = handler.handle(wxMessage, context, wxMpService);
res = handler.handle(wxMessage, context, wxMpService, sessionManager);
}
return res;
}


+ 75
- 10
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java Переглянути файл

@@ -2,10 +2,12 @@ package me.chanjar.weixin.mp.api;

import me.chanjar.weixin.common.bean.WxMenu;
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.mp.bean.*;
import me.chanjar.weixin.mp.bean.result.*;
import me.chanjar.weixin.common.exception.WxErrorException;

import java.io.File;
import java.io.IOException;
@@ -28,7 +30,15 @@ public interface WxMpService {
* @return
*/
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>
* 获取access_token,本方法线程安全
@@ -40,20 +50,56 @@ public interface WxMpService {

* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
* </pre>
* @param forceRefresh 强制刷新
* @return
* @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>
* 上传多媒体文件
*
*
* 上传的多媒体文件有格式和大小限制,如下:
* 图片(image): 1M,支持JPG格式
* 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
* 视频(video):10MB,支持MP4格式
* 缩略图(thumb):64KB,支持JPG格式
*
*
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
* </pre>
* @param mediaType 媒体类型, 请看{@link 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;

/**
* 注入 {@link WxMpConfigStorage} 的实现
* @param wxConfigProvider
*/
/**
* 注入 {@link WxMpConfigStorage} 的实现
* @param 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);

}

+ 136
- 71
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java Переглянути файл

@@ -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.WxMediaUploadResult;
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.crypto.SHA1;
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.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

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 final ThreadLocal<Integer> retryTimes = new ThreadLocal<Integer>();

protected CloseableHttpClient httpClient;

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) {
try {
return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature);
@@ -64,49 +77,86 @@ public class WxMpServiceImpl implements WxMpService {
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 {
String url = "https://api.weixin.qq.com/cgi-bin/message/custom/send";
execute(new SimplePostRequestExecutor(), url, message.toJson());
@@ -399,7 +449,7 @@ public class WxMpServiceImpl implements WxMpService {
return execute(new SimplePostRequestExecutor(), url, postData);
}

/**
/**
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
* @param executor
* @param uri
@@ -408,10 +458,34 @@ public class WxMpServiceImpl implements WxMpService {
* @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;
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
@@ -426,30 +500,10 @@ public class WxMpServiceImpl implements WxMpService {
* 42001 access_token超时
*/
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
accessTokenRefresh();
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
wxMpConfigStorage.expireAccessToken();
return execute(executor, uri, data);
}
/**
* -1 系统繁忙, 1000ms后重试
*/
if (error.getErrorCode() == -1) {
if(retryTimes.get() == null) {
retryTimes.set(0);
}
if (retryTimes.get() > 4) {
retryTimes.set(0);
throw new RuntimeException("微信服务端异常,超出重试次数");
}
int sleepMillis = 1000 * (1 << retryTimes.get());
try {
System.out.println("微信系统繁忙," + sleepMillis + "ms后重试");
Thread.sleep(sleepMillis);
retryTimes.set(retryTimes.get() + 1);
return execute(executor, uri, data);
} catch (InterruptedException e1) {
throw new RuntimeException(e1);
}
}
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
@@ -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;
}

}

+ 0
- 32
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMpXmlOutImageMessage.java Переглянути файл

@@ -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;
}
}

+ 0
- 3
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpTemplateMessage.java Переглянути файл

@@ -5,9 +5,6 @@ import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
import java.util.ArrayList;
import java.util.List;

/**
* Created by qianjia on 14/11/1.
*/
public class WxMpTemplateMessage {

private String toUser;


+ 81
- 99
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlMessage.java Переглянути файл

@@ -1,14 +1,13 @@
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.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 javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
@@ -25,100 +24,99 @@ import java.util.List;
*
* @author chanjarster
*/
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("xml")
public class WxMpXmlMessage {

///////////////////////
// 以下都是微信推送过来的消息的xml的element所对应的属性
///////////////////////

@XmlElement(name = "ToUserName")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("ToUserName")
@XStreamConverter(value=XStreamCDataConverter.class)
private String toUserName;

@XmlElement(name = "FromUserName")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("FromUserName")
@XStreamConverter(value=XStreamCDataConverter.class)
private String fromUserName;

@XmlElement(name = "CreateTime")
@XStreamAlias("CreateTime")
private Long createTime;

@XmlElement(name = "MsgType")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("MsgType")
@XStreamConverter(value=XStreamCDataConverter.class)
private String msgType;

@XmlElement(name = "Content")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Content")
@XStreamConverter(value=XStreamCDataConverter.class)
private String content;

@XmlElement(name = "MsgId")
@XStreamAlias("MsgId")
private Long msgId;

@XmlElement(name = "PicUrl")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("PicUrl")
@XStreamConverter(value=XStreamCDataConverter.class)
private String picUrl;

@XmlElement(name = "MediaId")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("MediaId")
@XStreamConverter(value=XStreamCDataConverter.class)
private String mediaId;

@XmlElement(name = "Format")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Format")
@XStreamConverter(value=XStreamCDataConverter.class)
private String format;

@XmlElement(name = "ThumbMediaId")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("ThumbMediaId")
@XStreamConverter(value=XStreamCDataConverter.class)
private String thumbMediaId;

@XmlElement(name = "Location_X")
@XStreamAlias("Location_X")
private Double locationX;

@XmlElement(name = "Location_Y")
@XStreamAlias("Location_Y")
private Double locationY;

@XmlElement(name = "Scale")
@XStreamAlias("Scale")
private Double scale;

@XmlElement(name = "Label")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Label")
@XStreamConverter(value=XStreamCDataConverter.class)
private String label;

@XmlElement(name = "Title")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Title")
@XStreamConverter(value=XStreamCDataConverter.class)
private String title;

@XmlElement(name = "Description")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Description")
@XStreamConverter(value=XStreamCDataConverter.class)
private String description;

@XmlElement(name = "Url")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Url")
@XStreamConverter(value=XStreamCDataConverter.class)
private String url;

@XmlElement(name = "Event")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Event")
@XStreamConverter(value=XStreamCDataConverter.class)
private String event;

@XmlElement(name = "EventKey")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("EventKey")
@XStreamConverter(value=XStreamCDataConverter.class)
private String eventKey;

@XmlElement(name = "Ticket")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Ticket")
@XStreamConverter(value=XStreamCDataConverter.class)
private String ticket;

@XmlElement(name = "Latitude")
@XStreamAlias("Latitude")
private Double latitude;

@XmlElement(name = "Longitude")
@XStreamAlias("Longitude")
private Double longitude;

@XmlElement(name = "Precision")
@XStreamAlias("Precision")
private Double precision;

@XmlElement(name = "Recognition")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Recognition")
@XStreamConverter(value=XStreamCDataConverter.class)
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;
/**
* group_id下粉丝数;或者openid_list中的粉丝数
*/
@XmlElement(name = "TotalCount")
@XStreamAlias("TotalCount")
private Integer totalCount;
/**
* 过滤(过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount
*/
@XmlElement(name = "FilterCount")
@XStreamAlias("FilterCount")
private Integer filterCount;
/**
* 发送成功的粉丝数
*/
@XmlElement(name = "SentCount")
@XStreamAlias("SentCount")
private Integer sentCount;
/**
* 发送失败的粉丝数
*/
@XmlElement(name = "ErrorCount")
@XStreamAlias("ErrorCount")
private Integer errorCount;

@XmlElement(name = "ScanCodeInfo")
@XStreamAlias("ScanCodeInfo")
private ScanCodeInfo scanCodeInfo = new ScanCodeInfo();

@XmlElement(name = "SendPicsInfo")
@XStreamAlias("SendPicsInfo")
private SendPicsInfo sendPicsInfo = new SendPicsInfo();

@XmlElement(name = "SendLocationInfo")
@XStreamAlias("SendLocationInfo")
private SendLocationInfo sendLocationInfo = new SendLocationInfo();

public String getToUserName() {
@@ -380,21 +378,11 @@ public class WxMpXmlMessage {
}

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) {
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;
}

@XmlRootElement(name = "ScanCodeInfo")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("ScanCodeInfo")
public static class ScanCodeInfo {

@XmlElement(name = "ScanType")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("ScanType")
@XStreamConverter(value=XStreamCDataConverter.class)
private String scanType;

@XmlElement(name = "ScanResult")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("ScanResult")
@XStreamConverter(value=XStreamCDataConverter.class)
private String scanResult;

/**
@@ -530,15 +517,13 @@ public class WxMpXmlMessage {

}

@XmlRootElement(name = "SendPicsInfo")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("SendPicsInfo")
public static class SendPicsInfo {

@XmlElement(name = "Count")
@XStreamAlias("Count")
private Long count;

@XmlElementWrapper(name="PicList")
@XmlElement(name = "item")
@XStreamAlias("PicList")
protected final List<Item> picList = new ArrayList<Item>();

public Long getCount() {
@@ -553,47 +538,44 @@ public class WxMpXmlMessage {
return picList;
}

@XmlRootElement(name = "item")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "WxXmlMessage.SendPicsInfo.Item")
@XStreamAlias("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() {
return PicMd5Sum;
return picMd5Sum;
}

public void setPicMd5Sum(String picMd5Sum) {
PicMd5Sum = picMd5Sum;
this.picMd5Sum = picMd5Sum;
}
}
}

@XmlRootElement(name = "SendLocationInfo")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("SendLocationInfo")
public static class SendLocationInfo {

@XmlElement(name = "Location_X")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Location_X")
@XStreamConverter(value=XStreamCDataConverter.class)
private String locationX;

@XmlElement(name = "Location_Y")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Location_Y")
@XStreamConverter(value=XStreamCDataConverter.class)
private String locationY;

@XmlElement(name = "Scale")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Scale")
@XStreamConverter(value=XStreamCDataConverter.class)
private String scale;

@XmlElement(name = "Label")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Label")
@XStreamConverter(value=XStreamCDataConverter.class)
private String label;

@XmlElement(name = "Poiname")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Poiname")
@XStreamConverter(value=XStreamCDataConverter.class)
private String poiname;

public String getLocationX() {


+ 27
- 0
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessage.java Переглянути файл

@@ -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;
}

}

+ 14
- 24
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMessage.java Переглянути файл

@@ -1,35 +1,29 @@
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.bean.outxmlbuilder.*;
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;
@XmlElement(name="FromUserName")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("FromUserName")
@XStreamConverter(value=XStreamCDataConverter.class)
protected String fromUserName;
@XmlElement(name="CreateTime")
@XStreamAlias("CreateTime")
protected Long createTime;
@XmlElement(name="MsgType")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("MsgType")
@XStreamConverter(value=XStreamCDataConverter.class)
protected String msgType;

public String getToUserName() {
@@ -65,11 +59,7 @@ public class WxMpXmlOutMessage {
}
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);
}

/**


+ 17
- 23
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMusicMessage.java Переглянути файл

@@ -1,19 +1,14 @@
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.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 {

@XmlElement(name = "Music")
@XStreamAlias("Music")
protected final Music music = new Music();

public WxMpXmlOutMusicMessage() {
@@ -60,28 +55,27 @@ public class WxMpXmlOutMusicMessage extends WxMpXmlOutMessage {
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;

@XmlElement(name = "Description")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Description")
@XStreamConverter(value=XStreamCDataConverter.class)
private String description;

@XmlElement(name="ThumbMediaId")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("ThumbMediaId")
@XStreamConverter(value=XStreamCDataConverter.class)
private String thumbMediaId;
@XmlElement(name="MusicUrl")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("MusicUrl")
@XStreamConverter(value=XStreamCDataConverter.class)
private String musicUrl;
@XmlElement(name="HQMusicUrl")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("HQMusicUrl")
@XStreamConverter(value=XStreamCDataConverter.class)
private String hqMusicUrl;
public String getTitle() {


+ 15
- 18
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutNewsMessage.java Переглянути файл

@@ -1,22 +1,20 @@
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.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.List;

@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("xml")
public class WxMpXmlOutNewsMessage extends WxMpXmlOutMessage {

@XmlElement(name = "ArticleCount")
@XStreamAlias("ArticleCount")
protected int articleCount;
@XmlElementWrapper(name="Articles")
@XmlElement(name = "item")
@XStreamAlias("Articles")
protected final List<Item> articles = new ArrayList<Item>();
public WxMpXmlOutNewsMessage() {
@@ -37,24 +35,23 @@ public class WxMpXmlOutNewsMessage extends WxMpXmlOutMessage {
}
@XmlRootElement(name = "Item")
@XmlAccessorType(XmlAccessType.FIELD)
@XStreamAlias("item")
public static class Item {
@XmlElement(name = "Title")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Title")
@XStreamConverter(value=XStreamCDataConverter.class)
private String Title;

@XmlElement(name = "Description")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Description")
@XStreamConverter(value=XStreamCDataConverter.class)
private String Description;

@XmlElement(name="PicUrl")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("PicUrl")
@XStreamConverter(value=XStreamCDataConverter.class)
private String PicUrl;
@XmlElement(name="Url")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Url")
@XStreamConverter(value=XStreamCDataConverter.class)
private String Url;
public String getTitle() {


+ 6
- 11
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutTextMessage.java Переглянути файл

@@ -1,20 +1,15 @@
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.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 {
@XmlElement(name="Content")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Content")
@XStreamConverter(value=XStreamCDataConverter.class)
private String content;

public WxMpXmlOutTextMessage() {


+ 13
- 19
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVideoMessage.java Переглянути файл

@@ -1,19 +1,14 @@
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.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 {

@XmlElement(name = "Video")
@XStreamAlias("Video")
protected final Video video = new Video();

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;

@XmlElement(name = "Title")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Title")
@XStreamConverter(value=XStreamCDataConverter.class)
private String title;

@XmlElement(name = "Description")
@XmlJavaTypeAdapter(AdapterCDATA.class)
@XStreamAlias("Description")
@XStreamConverter(value=XStreamCDataConverter.class)
private String description;

public String getMediaId() {


+ 9
- 14
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVoiceMessage.java Переглянути файл

@@ -1,26 +1,21 @@
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.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 {
@XmlElement(name="Voice")
@XmlJavaTypeAdapter(MediaIdMarshaller.class)

@XStreamAlias("Voice")
@XStreamConverter(value = XStreamMediaIdConverter.class)
private String mediaId;

public WxMpXmlOutVoiceMessage() {
this.msgType = WxConsts.XML_MSG_VOICE;
}
public String getMediaId() {
return mediaId;
}
@@ -28,5 +23,5 @@ public class WxMpXmlOutVoiceMessage extends WxMpXmlOutMessage {
public void setMediaId(String mediaId) {
this.mediaId = mediaId;
}
}

+ 4
- 4
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/ImageBuilder.java Переглянути файл

@@ -1,12 +1,12 @@
package me.chanjar.weixin.mp.bean.outxmlbuilder;

import me.chanjar.weixin.mp.bean.WxMpMpXmlOutImageMessage;
import me.chanjar.weixin.mp.bean.WxMpXmlOutImageMessage;

/**
* 图片消息builder
* @author chanjarster
*/
public final class ImageBuilder extends BaseBuilder<ImageBuilder, WxMpMpXmlOutImageMessage> {
public final class ImageBuilder extends BaseBuilder<ImageBuilder, WxMpXmlOutImageMessage> {

private String mediaId;

@@ -15,8 +15,8 @@ public final class ImageBuilder extends BaseBuilder<ImageBuilder, WxMpMpXmlOutIm
return this;
}

public WxMpMpXmlOutImageMessage build() {
WxMpMpXmlOutImageMessage m = new WxMpMpXmlOutImageMessage();
public WxMpXmlOutImageMessage build() {
WxMpXmlOutImageMessage m = new WxMpXmlOutImageMessage();
setCommon(m);
m.setMediaId(this.mediaId);
return m;


+ 1
- 5
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java Переглянути файл

@@ -1,11 +1,7 @@
package me.chanjar.weixin.mp.bean.result;

import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;

/**
* Created by qianjia on 14/11/26.
*/
public class WxMpOAuth2AccessToken {

private String accessToken;
@@ -66,7 +62,7 @@ public class WxMpOAuth2AccessToken {
public String toString() {
return "WxMpOAuth2AccessToken{" +
"accessToken='" + accessToken + '\'' +
", expiresIn=" + expiresIn +
", expiresTime=" + expiresIn +
", refreshToken='" + refreshToken + '\'' +
", openId='" + openId + '\'' +
", scope='" + scope + '\'' +


+ 1
- 1
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/http/QrCodeRequestExecutor.java Переглянути файл

@@ -1,12 +1,12 @@
package me.chanjar.weixin.mp.util.http;

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.http.InputStreamResponseHandler;
import me.chanjar.weixin.common.util.http.RequestExecutor;
import me.chanjar.weixin.common.util.http.Utf8ResponseHandler;
import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;
import me.chanjar.weixin.common.exception.WxErrorException;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.client.ClientProtocolException;


+ 0
- 5
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpCustomMessageGsonAdapter.java Переглянути файл

@@ -14,11 +14,6 @@ import me.chanjar.weixin.mp.bean.WxMpCustomMessage;

import java.lang.reflect.Type;

/**
*
* @author qianjia
*
*/
public class WxMpCustomMessageGsonAdapter implements JsonSerializer<WxMpCustomMessage> {

public JsonElement serialize(WxMpCustomMessage message, Type typeOfSrc, JsonSerializationContext context) {


+ 0
- 5
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGroupGsonAdapter.java Переглянути файл

@@ -14,11 +14,6 @@ import me.chanjar.weixin.mp.bean.WxMpGroup;

import java.lang.reflect.Type;

/**
*
* @author qianjia
*
*/
public class WxMpGroupGsonAdapter implements JsonSerializer<WxMpGroup>, JsonDeserializer<WxMpGroup> {

public JsonElement serialize(WxMpGroup group, Type typeOfSrc, JsonSerializationContext context) {


+ 0
- 1
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java Переглянути файл

@@ -4,7 +4,6 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import me.chanjar.weixin.mp.bean.*;
import me.chanjar.weixin.mp.bean.result.*;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;

public class WxMpGsonBuilder {



+ 0
- 5
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassGroupMessageGsonAdapter.java Переглянути файл

@@ -17,11 +17,6 @@ import me.chanjar.weixin.mp.bean.WxMpMassGroupMessage;

import java.lang.reflect.Type;

/**
*
* @author qianjia
*
*/
public class WxMpMassGroupMessageGsonAdapter implements JsonSerializer<WxMpMassGroupMessage> {

public JsonElement serialize(WxMpMassGroupMessage message, Type typeOfSrc, JsonSerializationContext context) {


+ 0
- 5
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java Переглянути файл

@@ -13,11 +13,6 @@ import me.chanjar.weixin.mp.bean.WxMpMassNews;

import java.lang.reflect.Type;

/**
*
* @author qianjia
*
*/
public class WxMpMassNewsGsonAdapter implements JsonSerializer<WxMpMassNews> {

public JsonElement serialize(WxMpMassNews message, Type typeOfSrc, JsonSerializationContext context) {


+ 0
- 5
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassOpenIdsMessageGsonAdapter.java Переглянути файл

@@ -14,11 +14,6 @@ import me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage;

import java.lang.reflect.Type;

/**
*
* @author qianjia
*
*/
public class WxMpMassOpenIdsMessageGsonAdapter implements JsonSerializer<WxMpMassOpenIdsMessage> {

public JsonElement serialize(WxMpMassOpenIdsMessage message, Type typeOfSrc, JsonSerializationContext context) {


+ 0
- 4
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpOAuth2AccessTokenAdapter.java Переглянути файл

@@ -1,15 +1,11 @@
package me.chanjar.weixin.mp.util.json;

import com.google.gson.*;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.json.GsonHelper;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;

import java.lang.reflect.Type;

/**
* Created by qianjia on 14/11/28.
*/
public class WxMpOAuth2AccessTokenAdapter implements JsonDeserializer<WxMpOAuth2AccessToken> {

public WxMpOAuth2AccessToken deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws


+ 0
- 2
weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpSemanticQueryResultAdapter.java Переглянути файл

@@ -10,8 +10,6 @@ package me.chanjar.weixin.mp.util.json;

import com.google.gson.*;
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 java.lang.reflect.Type;


Деякі файли не було показано, через те що забагато файлів було змінено

Завантаження…
Відмінити
Зберегти