diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..5c304d1a --- /dev/null +++ b/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. diff --git a/README.md b/README.md index 138e4670..abc1a16e 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ weixin-java-tools me.chanjar weixin-java-mp - 1.0.6 + 1.0.7 ``` @@ -29,10 +29,24 @@ weixin-java-tools me.chanjar weixin-java-cp - 1.0.6 + 1.0.7 ``` +## SNAPSHOT版 + +本项目的BUG修复和新特性一般会先发布在*-SNAPSHOT版里供大家预览,如果要使用*-SNAPSHOT版,则需要在你的pom.xml中添加这段: + +```xml + + + + sonatype snapshots + https://oss.sonatype.org/content/repositories/snapshots/ + + +``` + ## 升级指南 原``1.0.0~1.0.2``版本用户无法平滑升级到``1.0.3``。需要做的是: diff --git a/pom.xml b/pom.xml index 65107a6b..056d4828 100644 --- a/pom.xml +++ b/pom.xml @@ -1,229 +1,232 @@ - 4.0.0 - me.chanjar - weixin-java-parent - 1.0.6 - pom - WeiXin Java Tools - Parent - 微信公众号、企业号上级POM - https://github.com/chanjarster/weixin-java-tools + 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"> + 4.0.0 + me.chanjar + weixin-java-parent + 1.1.0-SNAPSHOT + pom + WeiXin Java Tools - Parent + 微信公众号、企业号上级POM + https://github.com/chanjarster/weixin-java-tools + + + + The Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + + - - - The Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - + + + Daniel Qian + chanjarster@gmail.com + + - - - Daniel Qian - chanjarster@gmail.com - - + + scm:git:https://github.com/chanjarster/weixin-java-tools.git + scm:git:git@github.com:chanjarster/weixin-java-tools.git + https://github.com/chanjarster/weixin-java-tools + - - scm:git:https://github.com/chanjarster/weixin-java-tools.git - scm:git:git@github.com:chanjarster/weixin-java-tools.git - https://github.com/chanjarster/weixin-java-tools - + + weixin-java-common + weixin-java-cp + weixin-java-mp + - - weixin-java-common - weixin-java-cp - weixin-java-mp - + + UTF-8 + true + true + 4.3.5 + 1.7.10 + 1.1.2 + - - UTF-8 - true - true - 4.3.5 - + + + org.slf4j + slf4j-api + ${slf4j.version} + + + ch.qos.logback + logback-classic + ${logback.version} + test + + + org.apache.httpcomponents + fluent-hc + ${httpclient.version} + + + org.apache.httpcomponents + httpmime + ${httpclient.version} + + + com.google.code.gson + gson + 2.2.2 + + + commons-codec + commons-codec + 1.9 + + + commons-io + commons-io + 2.4 + + + - - org.apache.httpcomponents - fluent-hc - ${httpclient.version} - - - org.apache.httpcomponents - httpmime - ${httpclient.version} - - - javax.xml.bind - jaxb-api - 2.2.7 - - - com.sun.xml.bind - jaxb-impl - 2.2.7 - - - com.google.code.gson - gson - 2.2.2 - - - commons-codec - commons-codec - 1.9 - - - commons-io - commons-io - 2.4 - + + junit + junit + 4.11 + test + + + com.google.inject + guice + 3.0 + test + + + org.testng + testng + 6.8.7 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + org.eclipse.jetty + jetty-server + 9.3.0.M0 + test + + + org.eclipse.jetty + jetty-servlet + 9.3.0.M0 + test + + - - - - junit - junit - 4.11 - test - - - com.google.inject - guice - 3.0 - test - - - org.testng - testng - 6.8.7 - test - - - org.mockito - mockito-all - 1.9.5 - test - - - org.eclipse.jetty - jetty-server - 9.3.0.M0 - test - - - org.eclipse.jetty - jetty-servlet - 9.3.0.M0 - test - - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - release - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - - jar - - - - - UTF-8 - zh_CN - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.17 - - - + + + ossrh + https://oss.sonatype.org/content/repositories/snapshots + + + ossrh + https://oss.sonatype.org/service/local/staging/deploy/maven2/ + + + + + release + - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 - true - - ossrh - https://oss.sonatype.org/ - false - - - - org.apache.maven.plugins - maven-release-plugin - 2.5 - - true - false - release - deploy - - + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9.1 + + + attach-javadocs + + jar + + + + + UTF-8 + zh_CN + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.5 + + + sign-artifacts + verify + + sign + + + + - + + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.17 + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.3 + true + + ossrh + https://oss.sonatype.org/ + false + + + + org.apache.maven.plugins + maven-release-plugin + 2.5 + + true + false + release + deploy + + + + diff --git a/pom.xml.versionsBackup b/pom.xml.versionsBackup deleted file mode 100644 index 3e038387..00000000 --- a/pom.xml.versionsBackup +++ /dev/null @@ -1,229 +0,0 @@ - - - 4.0.0 - me.chanjar - weixin-java-parent - 1.0.5-SNAPSHOT - pom - WeiXin Java Tools - Parent - 微信公众号、企业号上级POM - https://github.com/chanjarster/weixin-java-tools - - - - The Apache License, Version 2.0 - http://www.apache.org/licenses/LICENSE-2.0.txt - - - - - - Daniel Qian - chanjarster@gmail.com - - - - - scm:git:https://github.com/chanjarster/weixin-java-tools.git - scm:git:git@github.com:chanjarster/weixin-java-tools.git - https://github.com/chanjarster/weixin-java-tools - - - - weixin-java-common - weixin-java-cp - weixin-java-mp - - - - UTF-8 - true - true - 4.3.5 - - - - - org.apache.httpcomponents - fluent-hc - ${httpclient.version} - - - org.apache.httpcomponents - httpmime - ${httpclient.version} - - - javax.xml.bind - jaxb-api - 2.2.7 - - - com.sun.xml.bind - jaxb-impl - 2.2.7 - - - com.google.code.gson - gson - 2.2.2 - - - commons-codec - commons-codec - 1.9 - - - commons-io - commons-io - 2.4 - - - - - - - junit - junit - 4.11 - test - - - com.google.inject - guice - 3.0 - test - - - org.testng - testng - 6.8.7 - test - - - org.mockito - mockito-all - 1.9.5 - test - - - org.eclipse.jetty - jetty-server - 9.3.0.M0 - test - - - org.eclipse.jetty - jetty-servlet - 9.3.0.M0 - test - - - - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - https://oss.sonatype.org/service/local/staging/deploy/maven2/ - - - - - - release - - - - org.apache.maven.plugins - maven-source-plugin - 2.2.1 - - - attach-sources - - jar-no-fork - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - 2.9.1 - - - attach-javadocs - - jar - - - - - UTF-8 - zh_CN - - - - org.apache.maven.plugins - maven-gpg-plugin - 1.5 - - - sign-artifacts - verify - - sign - - - - - - - - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.17 - - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.3 - true - - ossrh - https://oss.sonatype.org/ - false - - - - org.apache.maven.plugins - maven-release-plugin - 2.5 - - true - false - release - deploy - - - - - - - diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index 0e14fcae..647886fe 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -1,68 +1,73 @@ - 4.0.0 - - me.chanjar - weixin-java-parent - 1.0.6 - + 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"> + 4.0.0 + + me.chanjar + weixin-java-parent + 1.1.0-SNAPSHOT + - weixin-java-common - WeiXin Java Tools - Common - 微信公众号、企业号Java SDK Common + weixin-java-common + WeiXin Java Tools - Common + 微信公众号、企业号Java SDK Common - - - junit - junit - test - - - org.testng - testng - 6.8.7 - test - - - org.mockito - mockito-all - 1.9.5 - test - - - com.google.inject - guice - 3.0 - test - - - org.eclipse.jetty - jetty-server - 9.3.0.M0 - test - - - org.eclipse.jetty - jetty-servlet - 9.3.0.M0 - test - - + + + com.thoughtworks.xstream + xstream + 1.4.7 + + + junit + junit + test + + + org.testng + testng + 6.8.7 + test + + + org.mockito + mockito-all + 1.9.5 + test + + + com.google.inject + guice + 3.0 + test + + + org.eclipse.jetty + jetty-server + 9.3.0.M0 + test + + + org.eclipse.jetty + jetty-servlet + 9.3.0.M0 + test + + - - - - org.apache.maven.plugins - maven-surefire-plugin - - - src/test/resources/testng.xml - - - - - + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/resources/testng.xml + + + + + diff --git a/weixin-java-common/pom.xml.versionsBackup b/weixin-java-common/pom.xml.versionsBackup deleted file mode 100644 index 47e4ec22..00000000 --- a/weixin-java-common/pom.xml.versionsBackup +++ /dev/null @@ -1,68 +0,0 @@ - - - 4.0.0 - - me.chanjar - weixin-java-parent - 1.0.5-SNAPSHOT - - - weixin-java-common - WeiXin Java Tools - Common - 微信公众号、企业号Java SDK Common - - - - junit - junit - test - - - org.testng - testng - 6.8.7 - test - - - org.mockito - mockito-all - 1.9.5 - test - - - com.google.inject - guice - 3.0 - test - - - org.eclipse.jetty - jetty-server - 9.3.0.M0 - test - - - org.eclipse.jetty - jetty-servlet - 9.3.0.M0 - test - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - src/test/resources/testng.xml - - - - - - - diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/Constants.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/Constants.java new file mode 100644 index 00000000..33518f17 --- /dev/null +++ b/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 org.apache.catalina.session + * package. + * + * @author Craig R. McClanahan + */ + +public class Constants { + + public static final String Package = "me.chanjar.weixin.common.session"; + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java new file mode 100644 index 00000000..79a7f4db --- /dev/null +++ b/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 HttpSession for which this object + * is the facade. + */ + WxSession getSession(); + + /** + * Set the isValid flag for this session. + * + * @param isValid The new value for the isValid flag + */ + public void setValid(boolean isValid); + + /** + * Return the isValid 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); +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java new file mode 100644 index 00000000..556d8ab8 --- /dev/null +++ b/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 null. + * + * @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 + * null. + * + * @param sessionId The session id which should be used to create the + * new session; if null, 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); + + /** + *
+   * Set the manager checks frequency.
+   * 设置每尝试多少次清理过期session,才真的会执行一次清理动作
+   * 要和{@link #setBackgroundProcessorDelay(int)}联合起来看
+   * 如果把这个数字设置为6(默认),那么就是说manager要等待 6 * backgroundProcessorDay的时间才会清理过期session
+   * 
+ * @param processExpiresFrequency the new manager checks frequency + */ + void setProcessExpiresFrequency(int processExpiresFrequency); + + /** + *
+   * Set the manager background processor delay
+   * 设置manager sleep几秒,尝试执行一次background操作(清理过期session)
+   * 
+ * @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); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/LocalStrings.properties b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/LocalStrings.properties new file mode 100644 index 00000000..ede528f7 --- /dev/null +++ b/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 diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSession.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSession.java new file mode 100644 index 00000000..d9827308 --- /dev/null +++ b/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 attributes = new ConcurrentHashMap(); + + @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 getAttributeNames() { + if (!isValidInternal()) + throw new IllegalStateException + (sm.getString("sessionImpl.getAttributeNames.ise")); + + Set names = new HashSet(); + 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 isValid flag for this session without any expiration + * check. + */ + protected boolean isValidInternal() { + return this.isValid; + } + + /** + * Set the isValid flag for this session. + * + * @param isValid The new value for the isValid 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; + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionFacade.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionFacade.java new file mode 100644 index 00000000..a9631de6 --- /dev/null +++ b/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 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(); + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java new file mode 100644 index 00000000..f3a02695 --- /dev/null +++ b/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 sessions = new ConcurrentHashMap(); + + @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; + + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java new file mode 100644 index 00000000..bd0c0f2b --- /dev/null +++ b/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; + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java new file mode 100644 index 00000000..816b172b --- /dev/null +++ b/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 getAttributeNames(); + + public void setAttribute(String name, Object value); + + public void removeAttribute(String name); + + public void invalidate(); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java new file mode 100644 index 00000000..262d9fe4 --- /dev/null +++ b/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); + + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageDuplicateChecker.java new file mode 100644 index 00000000..1abe588b --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageDuplicateChecker.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.common.util; + +/** + *
+ * 消息重复检查器
+ * 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
+ * 
+ */ +public interface WxMessageDuplicateChecker { + + /** + *

公众号的排重方式

+ * + *

普通消息:关于重试的消息排重,推荐使用msgid排重。文档参考

+ *

事件消息:关于重试的消息排重,推荐使用FromUserName + CreateTime 排重。文档参考

+ * + *

企业号的排重方式

+ * + * 官方文档完全没有写,参照公众号的方式排重。 + * + *

或者可以采取更简单的方式,如果有MsgId就用MsgId排重,如果没有就用FromUserName+CreateTime排重

+ * @param messageId messageId需要根据上面讲的方式构造 + * @return 如果是重复消息,返回true,否则返回false + */ + public boolean isDuplicate(String messageId); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateChecker.java new file mode 100644 index 00000000..ae0e0623 --- /dev/null +++ b/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; + +/** + *
+ * 默认消息重复检查器
+ * 将每个消息id保存在内存里,每隔5秒清理已经过期的消息id,每个消息id的过期时间是15秒
+ * 
+ */ +public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChecker { + + /** + * 一个消息ID在内存的过期时间:15秒 + */ + private final Long timeToLive; + + /** + * 每隔多少周期检查消息ID是否过期:5秒 + */ + private final Long clearPeriod; + + /** + * 消息id->消息时间戳的map + */ + private final ConcurrentHashMap msgId2Timestamp = new ConcurrentHashMap(); + + /** + * 后台清理线程是否已经开启 + */ + private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false); + + /** + * WxMsgIdInMemoryDuplicateChecker构造函数 + *
+   * 一个消息ID在内存的过期时间:15秒
+   * 每隔多少周期检查消息ID是否过期:5秒
+   * 
+ */ + 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 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; + } + + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java index cc233308..c97facd3 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java +++ b/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]); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java new file mode 100644 index 00000000..4668ec25 --- /dev/null +++ b/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. + * + *

The StringManager operates on a package basis. One StringManager + * per package can be created and accessed via the getManager method + * call. + * + *

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. + * + *

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 key from underlying + bundle or null if not found. + @throws IllegalArgumentException if key 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> managers = + new Hashtable>(); + + /** + * 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 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_CACHE_SIZE, 1, true) { + private static final long serialVersionUID = 1L; + @Override + protected boolean removeEldestEntry( + Map.Entry 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 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); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/AdapterCDATA.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/AdapterCDATA.java deleted file mode 100644 index c38c2795..00000000 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/AdapterCDATA.java +++ /dev/null @@ -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 { - - @Override - public String marshal(String arg0) throws Exception { - return ""; - } - - @Override - public String unmarshal(String arg0) throws Exception { - return arg0; - } - -} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/MediaIdMarshaller.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/MediaIdMarshaller.java deleted file mode 100644 index 10e6a3aa..00000000 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/MediaIdMarshaller.java +++ /dev/null @@ -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 { - - @Override - public String marshal(String arg0) throws Exception { - return ""; - } - - @Override - public String unmarshal(String arg0) throws Exception { - // do nothing - return arg0; - } - -} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataConverter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataConverter.java new file mode 100644 index 00000000..aa948b34 --- /dev/null +++ b/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 ""; + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java new file mode 100644 index 00000000..d545a796 --- /dev/null +++ b/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 = ""; + protected String PREFIX_MEDIA_ID = ""; + protected String SUFFIX_MEDIA_ID = ""; + @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; + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamMediaIdConverter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamMediaIdConverter.java new file mode 100644 index 00000000..f67a3d9d --- /dev/null +++ b/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 "" + super.toString(obj) + ""; + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/session/SessionTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/session/SessionTest.java new file mode 100644 index 00000000..bcb029b1 --- /dev/null +++ b/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"); + + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateCheckerTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateCheckerTest.java new file mode 100644 index 00000000..28642c44 --- /dev/null +++ b/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); + } + + } + +} diff --git a/weixin-java-common/src/test/resources/logback-test.xml b/weixin-java-common/src/test/resources/logback-test.xml new file mode 100644 index 00000000..c0d6fc3c --- /dev/null +++ b/weixin-java-common/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + diff --git a/weixin-java-common/src/test/resources/testng.xml b/weixin-java-common/src/test/resources/testng.xml index f0058b53..f2222275 100644 --- a/weixin-java-common/src/test/resources/testng.xml +++ b/weixin-java-common/src/test/resources/testng.xml @@ -1,12 +1,14 @@ - - - - - - - - + + + + + + + + + + diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml index 2ec3e36a..50d8ebe4 100644 --- a/weixin-java-cp/pom.xml +++ b/weixin-java-cp/pom.xml @@ -6,7 +6,7 @@ me.chanjar weixin-java-parent - 1.0.6 + 1.1.0-SNAPSHOT weixin-java-cp diff --git a/weixin-java-cp/pom.xml.versionsBackup b/weixin-java-cp/pom.xml.versionsBackup deleted file mode 100644 index fe491f91..00000000 --- a/weixin-java-cp/pom.xml.versionsBackup +++ /dev/null @@ -1,73 +0,0 @@ - - - 4.0.0 - - me.chanjar - weixin-java-parent - 1.0.5-SNAPSHOT - - - weixin-java-cp - WeiXin Java Tools - CP - 微信企业号Java SDK - - - - me.chanjar - weixin-java-common - ${project.version} - - - junit - junit - test - - - org.testng - testng - 6.8.7 - test - - - org.mockito - mockito-all - 1.9.5 - test - - - com.google.inject - guice - 3.0 - test - - - org.eclipse.jetty - jetty-server - 9.3.0.M0 - test - - - org.eclipse.jetty - jetty-servlet - 9.3.0.M0 - test - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - src/test/resources/testng.xml - - - - - - - diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java index 36c9cfba..44123a36 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java +++ b/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(); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java index 9ff1f187..0a35fcd9 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java +++ b/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 + '\'' + diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageHandler.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageHandler.java index 861c610d..f9a3c289 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageHandler.java +++ b/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 context, WxCpService wxCpService); - + public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, + Map context, + WxCpService wxCpService, + WxSessionManager sessionManager); + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageInterceptor.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageInterceptor.java index c1e7ee9d..92fa41f1 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageInterceptor.java +++ b/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 context, WxCpService wxCpService); - + public boolean intercept(WxCpXmlMessage wxMessage, + Map context, + WxCpService wxCpService, + WxSessionManager sessionManager); + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageRouter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageRouter.java index bf5bf86e..34b36ec3 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageRouter.java +++ b/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; - /** *

  * 微信消息路由器,通过代码化的配置,把来自微信的消息交给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 rules = new ArrayList();
+  private static final int DEFAULT_THREAD_POOL_SIZE = 100;
 
-  private final ExecutorService executorService;
+  private final List rules = new ArrayList();
 
   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);
+  /**
+   * 
+   * 设置自定义的 {@link ExecutorService}
+   * 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100)
+   * 
+ * @param executorService + */ + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + /** + *
+   * 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker}
+   * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker}
+   * 
+ * @param messageDuplicateChecker + */ + public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) { + this.messageDuplicateChecker = messageDuplicateChecker; + } + + /** + *
+   * 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager}
+   * 如果不调用该方法,默认使用 {@linke SessionManagerImpl}
+   * 
+ * @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 matchRules = new ArrayList(); // 收集匹配的规则 for (final Rule rule : rules) { @@ -88,27 +136,84 @@ public class WxCpMessageRouter { } WxCpXmlOutMessage res = null; + final List futures = new ArrayList(); 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 interceptors = new ArrayList(); - 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 context = new HashMap(); // 如果拦截器不通过 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; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java index cc520f90..21669b19 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java +++ b/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; + /** *
    * 获取access_token,本方法线程安全
@@ -50,10 +61,11 @@ public interface WxCpService {
    * 程序员在非必要情况下尽量不要主动调用此方法
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
    * 
- * + * @param forceRefresh 强制刷新 + * @return * @throws me.chanjar.weixin.common.exception.WxErrorException */ - public void accessTokenRefresh() throws WxErrorException; + public String getAccessToken(boolean forceRefresh) throws WxErrorException; /** *
@@ -343,4 +355,46 @@ public interface WxCpService {
    * @param wxConfigProvider
    */
   public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider);
+
+  /**
+   * 
+   * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试
+   * 默认:1000ms
+   * 
+ * @param retrySleepMillis + */ + void setRetrySleepMillis(int retrySleepMillis); + + /** + *
+   * 设置当微信系统响应系统繁忙时,最大重试次数
+   * 默认:5次
+   * 
+ * @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); + + /** + *
+   * 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法,
+   * WxCpService默认使用的是{@link me.chanjar.weixin.common.session.StandardSessionManager}
+   * 
+ * @param sessionManager + */ + void setSessionManager(WxSessionManager sessionManager); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java index 2e0738c7..f0da30b5 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java +++ b/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 retryTimes = new ThreadLocal(); - 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 execute(RequestExecutor 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 executeInternal(RequestExecutor 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; + } + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java index 40f5c840..0f5c4f82 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java +++ b/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 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java index 90125a5d..2e8547d2 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java +++ b/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 extAttrs = new ArrayList(); 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)); } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java index 467c6a1f..db86e6f3 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java +++ b/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 picList = new ArrayList(); 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() { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java index 06ba88c3..9706a5aa 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java +++ b/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() { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java index 1ccacb00..641a572d 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java +++ b/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); } /** diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessage.java index 76f28e2e..79c35ba9 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessage.java +++ b/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 articles = new ArrayList(); 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() { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java index d6466fd0..f2096ba8 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java +++ b/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() { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java index 83345858..537147b6 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java +++ b/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() { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java index a4fa91a5..54c53dc6 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java +++ b/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() { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java index 5f0fa614..383904df 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java +++ b/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 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java index 022333f8..723d50db 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java +++ b/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 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java index 3a8b739b..32358648 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java +++ b/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 { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java index bf04d50c..4a920b54 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java +++ b/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, 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, 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()) { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java new file mode 100644 index 00000000..c6c5a5f1 --- /dev/null +++ b/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_2_XSTREAM_INSTANCE = configXStreamInstance(); + + /** + * xml -> pojo + * + * @param clazz + * @param xml + * @return + */ + @SuppressWarnings("unchecked") + public static T fromXml(Class clazz, String xml) { + T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(xml); + return object; + } + + @SuppressWarnings("unchecked") + public static T fromXml(Class 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 String toXml(Class clazz, T object) { + return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object); + } + + private static Map configXStreamInstance() { + Map map = new HashMap(); + 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; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XmlTransformer.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XmlTransformer.java deleted file mode 100644 index 21bb7a77..00000000 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XmlTransformer.java +++ /dev/null @@ -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 fromXml(Class 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 fromXml(Class 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 String toXml(Class clazz, T object) throws JAXBException { - StringWriter stringWriter = new StringWriter(); - toXml(clazz, object, stringWriter); - return stringWriter.getBuffer().toString(); - } - - public static void toXml(Class 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); - } - } - -} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java index 00b890d9..bb3690bc 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java +++ b/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 fromXml(Class 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 fromXml(Class 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; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java index 032165eb..d8757f34 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java +++ b/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(); diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java new file mode 100644 index 00000000..cf129088 --- /dev/null +++ b/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 executeInternal(RequestExecutor 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(); + } + +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java index 28cf15b0..28779e65 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpDepartAPITest.java +++ b/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; /** * 测试部门接口 diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java index f04b1fd4..c67acab1 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMediaAPITest.java +++ b/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; /** * 测试多媒体文件上传下载 diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java index 1672faaa..ee16f56f 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java +++ b/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 diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java index 6b178f9b..7394bbfa 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java +++ b/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 context, WxCpService wxCpService) { + public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService wxCpService, + WxSessionManager sessionManager) { return null; } }).end(); @@ -149,11 +152,145 @@ public class WxCpMessageRouterTest { } @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService wxCpService) { + public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map 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 context, WxCpService wxCpService, + WxSessionManager sessionManager) { + sessionManager.getSession(wxMessage.getFromUserName()); + return null; + } + + + } + } diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java index 9efb8a09..c444eec5 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpUserAPITest.java +++ b/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"); } } diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java index c00d73e2..ca5b5991 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxMenuAPITest.java +++ b/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); diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java index 72169d2b..f9dbe657 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java +++ b/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 { diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java index 0c0d3961..52219868 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoInMemoryConfigStorage.java +++ b/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); } } diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java index 2a248f98..0dadee91 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java +++ b/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 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 context, - WxCpService wxCpService) { + WxCpService wxCpService, WxSessionManager sessionManager) { String href = "测试oauth2"; return WxCpXmlOutMessage @@ -87,8 +86,5 @@ public class WxCpDemoServer { .end() ; - } catch (JAXBException e) { - throw new RuntimeException(e); - } } } diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpEndpointServlet.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpEndpointServlet.java index 320cc540..1fe8f592 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpEndpointServlet.java +++ b/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 diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpOAuth2Servlet.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpOAuth2Servlet.java index 02b1bf5d..e0800061 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpOAuth2Servlet.java +++ b/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; diff --git a/weixin-java-cp/src/test/resources/logback-test.xml b/weixin-java-cp/src/test/resources/logback-test.xml new file mode 100644 index 00000000..9c2ec6ae --- /dev/null +++ b/weixin-java-cp/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + diff --git a/weixin-java-cp/src/test/resources/test-config.sample.xml b/weixin-java-cp/src/test/resources/test-config.sample.xml index 02c98f3f..aa99a962 100644 --- a/weixin-java-cp/src/test/resources/test-config.sample.xml +++ b/weixin-java-cp/src/test/resources/test-config.sample.xml @@ -5,7 +5,7 @@ 企业号应用Token 企业号应用EncodingAESKey 可以不填写 - 可以不填写 + 可以不填写 企业号通讯录里的某个userid 企业号通讯录的某个部门id 企业号通讯录里的某个tagid diff --git a/weixin-java-cp/src/test/resources/testng.xml b/weixin-java-cp/src/test/resources/testng.xml index 4cddddcf..ba92bc0c 100644 --- a/weixin-java-cp/src/test/resources/testng.xml +++ b/weixin-java-cp/src/test/resources/testng.xml @@ -1,28 +1,29 @@ - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml index 2f757cfa..61d4972b 100644 --- a/weixin-java-mp/pom.xml +++ b/weixin-java-mp/pom.xml @@ -6,7 +6,7 @@ me.chanjar weixin-java-parent - 1.0.6 + 1.1.0-SNAPSHOT weixin-java-mp WeiXin Java Tools - MP diff --git a/weixin-java-mp/pom.xml.versionsBackup b/weixin-java-mp/pom.xml.versionsBackup deleted file mode 100644 index 2e10c3ed..00000000 --- a/weixin-java-mp/pom.xml.versionsBackup +++ /dev/null @@ -1,72 +0,0 @@ - - - 4.0.0 - - me.chanjar - weixin-java-parent - 1.0.5-SNAPSHOT - - weixin-java-mp - WeiXin Java Tools - MP - 微信公众号Java SDK - - - - me.chanjar - weixin-java-common - ${project.version} - - - junit - junit - test - - - org.testng - testng - 6.8.7 - test - - - org.mockito - mockito-all - 1.9.5 - test - - - com.google.inject - guice - 3.0 - test - - - org.eclipse.jetty - jetty-server - 9.3.0.M0 - test - - - org.eclipse.jetty - jetty-servlet - 9.3.0.M0 - test - - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - - src/test/resources/testng.xml - - - - - - - diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java index 3ecbf155..c54a119d 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpConfigStorage.java +++ b/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(); } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java index 82c3d621..ff272f77 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpInMemoryConfigStorage.java +++ b/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 + '\'' + '}'; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageHandler.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageHandler.java index 8e6deeb6..d7c30a31 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageHandler.java +++ b/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 context, WxMpService wxMpService); - + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, + Map context, + WxMpService wxMpService, + WxSessionManager sessionManager); + } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageInterceptor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageInterceptor.java index d940804f..2b68a6f2 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageInterceptor.java +++ b/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 context, WxMpService wxMpService); - + public boolean intercept(WxMpXmlMessage wxMessage, + Map context, + WxMpService wxMpService, + WxSessionManager sessionManager); + } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java index e904e41b..afd8d3fb 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java +++ b/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); * *
- * @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 rules = new ArrayList(); + private static final int DEFAULT_THREAD_POOL_SIZE = 100; - private final ExecutorService executorService; + private final List rules = new ArrayList(); 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); + /** + *
+   * 设置自定义的 {@link ExecutorService}
+   * 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100)
+   * 
+ * @param executorService + */ + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + /** + *
+   * 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker}
+   * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker}
+   * 
+ * @param messageDuplicateChecker + */ + public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) { + this.messageDuplicateChecker = messageDuplicateChecker; + } + + /** + *
+   * 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager}
+   * 如果不调用该方法,默认使用 {@linke SessionManagerImpl}
+   * 
+ * @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 matchRules = new ArrayList(); // 收集匹配的规则 for (final Rule rule : rules) { @@ -82,33 +129,90 @@ public class WxMpMessageRouter { } } } - + if (matchRules.size() == 0) { return null; } - + WxMpXmlOutMessage res = null; + final List futures = new ArrayList(); 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 handlers = new ArrayList(); private List interceptors = new ArrayList(); - - 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 context = new HashMap(); // 如果拦截器不通过 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; } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java index a6ac3c43..6bd501f3 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java +++ b/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; + /** *
    * 获取access_token,本方法线程安全
@@ -40,20 +50,56 @@ public interface WxMpService {
 
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
    * 
+ * @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; + + /** + *
+   * 获得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
+   * 
+ * @param forceRefresh 强制刷新 + * @return + * @throws WxErrorException + */ + public String getJsapiTicket(boolean forceRefresh) throws WxErrorException; + + /** + *
+   * 创建调用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
+   * 
+ * @param timestamp 时间戳 + * @param noncestr 用户自己生成的随机字符串 + * @param url url + * @return + */ + public String createJsapiSignature(long timestamp, String noncestr, String url) throws WxErrorException; + /** *
    * 上传多媒体文件
-   * 
+   *
    * 上传的多媒体文件有格式和大小限制,如下:
    *   图片(image): 1M,支持JPG格式
    *   语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
    *   视频(video):10MB,支持MP4格式
    *   缩略图(thumb):64KB,支持JPG格式
-   *    
+   *
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
    * 
* @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts} @@ -412,9 +458,28 @@ public interface WxMpService { */ public T execute(RequestExecutor executor, String uri, E data) throws WxErrorException; - /** - * 注入 {@link WxMpConfigStorage} 的实现 - * @param wxConfigProvider - */ + /** + * 注入 {@link WxMpConfigStorage} 的实现 + * @param wxConfigProvider + */ public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider); + + /** + *
+   * 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试
+   * 默认:1000ms
+   * 
+ * @param retrySleepMillis + */ + void setRetrySleepMillis(int retrySleepMillis); + + /** + *
+   * 设置当微信系统响应系统繁忙时,最大重试次数
+   * 默认:5次
+   * 
+ * @param maxRetryTimes + */ + void setMaxRetryTimes(int maxRetryTimes); + } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java index dbf2adc4..439c44df 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java +++ b/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 retryTimes = new ThreadLocal(); - 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 execute(RequestExecutor 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 executeInternal(RequestExecutor 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; + } + } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMpXmlOutImageMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMpXmlOutImageMessage.java deleted file mode 100644 index 1db47004..00000000 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMpXmlOutImageMessage.java +++ /dev/null @@ -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; - } - -} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpTemplateMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpTemplateMessage.java index cd6ac5f7..91441cd1 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpTemplateMessage.java +++ b/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; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlMessage.java index 2470aff4..1996e4f2 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlMessage.java +++ b/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("", "").replaceAll("", ""); - 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 picList = new ArrayList(); 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() { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessage.java new file mode 100644 index 00000000..6f6510c2 --- /dev/null +++ b/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; + } + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMessage.java index b9e4507d..a7534bde 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMessage.java +++ b/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); } /** diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMusicMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMusicMessage.java index 782b5dbb..b6fefdac 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutMusicMessage.java +++ b/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() { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutNewsMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutNewsMessage.java index 4730a99b..4b76b4f5 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutNewsMessage.java +++ b/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 articles = new ArrayList(); 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() { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutTextMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutTextMessage.java index 3542e07b..e2458a03 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutTextMessage.java +++ b/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() { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVideoMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVideoMessage.java index 4b192cf7..fb0520c1 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVideoMessage.java +++ b/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() { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVoiceMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVoiceMessage.java index 59f164ba..29c74e90 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutVoiceMessage.java +++ b/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; } - + } diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/ImageBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/ImageBuilder.java index f127406a..e263e57f 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/outxmlbuilder/ImageBuilder.java +++ b/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 { +public final class ImageBuilder extends BaseBuilder { private String mediaId; @@ -15,8 +15,8 @@ public final class ImageBuilder extends BaseBuilder { public JsonElement serialize(WxMpCustomMessage message, Type typeOfSrc, JsonSerializationContext context) { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGroupGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGroupGsonAdapter.java index 63f0624e..58e6f98d 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGroupGsonAdapter.java +++ b/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, JsonDeserializer { public JsonElement serialize(WxMpGroup group, Type typeOfSrc, JsonSerializationContext context) { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java index 40854843..73083d8c 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java +++ b/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 { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassGroupMessageGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassGroupMessageGsonAdapter.java index a0077469..79a9a0c6 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassGroupMessageGsonAdapter.java +++ b/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 { public JsonElement serialize(WxMpMassGroupMessage message, Type typeOfSrc, JsonSerializationContext context) { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java index 5ba78594..1719b75a 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassNewsGsonAdapter.java +++ b/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 { public JsonElement serialize(WxMpMassNews message, Type typeOfSrc, JsonSerializationContext context) { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassOpenIdsMessageGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassOpenIdsMessageGsonAdapter.java index cd03addf..84063da4 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpMassOpenIdsMessageGsonAdapter.java +++ b/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 { public JsonElement serialize(WxMpMassOpenIdsMessage message, Type typeOfSrc, JsonSerializationContext context) { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpOAuth2AccessTokenAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpOAuth2AccessTokenAdapter.java index 7ea7a35e..6d73131a 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpOAuth2AccessTokenAdapter.java +++ b/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 { public WxMpOAuth2AccessToken deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpSemanticQueryResultAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpSemanticQueryResultAdapter.java index c0d1e805..822357df 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpSemanticQueryResultAdapter.java +++ b/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; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpTemplateMessageGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpTemplateMessageGsonAdapter.java index ad603d3b..c34ac2c2 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpTemplateMessageGsonAdapter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpTemplateMessageGsonAdapter.java @@ -8,17 +8,15 @@ */ package me.chanjar.weixin.mp.util.json; -import com.google.gson.*; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.mp.bean.WxMpCustomMessage; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; import me.chanjar.weixin.mp.bean.WxMpTemplateData; import me.chanjar.weixin.mp.bean.WxMpTemplateMessage; import java.lang.reflect.Type; -/** - * @author qianjia - */ public class WxMpTemplateMessageGsonAdapter implements JsonSerializer { public JsonElement serialize(WxMpTemplateMessage message, Type typeOfSrc, JsonSerializationContext context) { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserGsonAdapter.java index cbbc3080..462994f6 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserGsonAdapter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserGsonAdapter.java @@ -14,11 +14,6 @@ import me.chanjar.weixin.mp.bean.result.WxMpUser; import java.lang.reflect.Type; -/** - * - * @author qianjia - * - */ public class WxUserGsonAdapter implements JsonDeserializer { public WxMpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserListGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserListGsonAdapter.java index ec65735e..5be3d160 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserListGsonAdapter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxUserListGsonAdapter.java @@ -14,11 +14,6 @@ import me.chanjar.weixin.mp.bean.result.WxMpUserList; import java.lang.reflect.Type; -/** - * - * @author qianjia - * - */ public class WxUserListGsonAdapter implements JsonDeserializer { public WxMpUserList deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java new file mode 100644 index 00000000..7b20c6f1 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java @@ -0,0 +1,114 @@ +package me.chanjar.weixin.mp.util.xml; + +import com.thoughtworks.xstream.XStream; +import me.chanjar.weixin.common.util.xml.XStreamInitializer; +import me.chanjar.weixin.mp.bean.*; + +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public class XStreamTransformer { + + protected static final Map CLASS_2_XSTREAM_INSTANCE = configXStreamInstance(); + + /** + * xml -> pojo + * + * @param clazz + * @param xml + * @return + */ + @SuppressWarnings("unchecked") + public static T fromXml(Class clazz, String xml) { + T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(xml); + return object; + } + + @SuppressWarnings("unchecked") + public static T fromXml(Class 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 String toXml(Class clazz, T object) { + return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object); + } + + private static Map configXStreamInstance() { + Map map = new HashMap(); + map.put(WxMpXmlMessage.class, config_WxMpXmlMessage()); + map.put(WxMpXmlOutMusicMessage.class, config_WxMpXmlOutMusicMessage()); + map.put(WxMpXmlOutNewsMessage.class, config_WxMpXmlOutNewsMessage()); + map.put(WxMpXmlOutTextMessage.class, config_WxMpXmlOutTextMessage()); + map.put(WxMpXmlOutImageMessage.class, config_WxMpXmlOutImageMessage()); + map.put(WxMpXmlOutVideoMessage.class, config_WxMpXmlOutVideoMessage()); + map.put(WxMpXmlOutVoiceMessage.class, config_WxMpXmlOutVoiceMessage()); + return map; + } + + private static XStream config_WxMpXmlMessage() { + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxMpXmlMessage.class); + xstream.processAnnotations(WxMpXmlMessage.ScanCodeInfo.class); + xstream.processAnnotations(WxMpXmlMessage.SendPicsInfo.class); + xstream.processAnnotations(WxMpXmlMessage.SendPicsInfo.Item.class); + xstream.processAnnotations(WxMpXmlMessage.SendLocationInfo.class); + + xstream.aliasField("MsgID", WxMpXmlMessage.class, "msgId"); + return xstream; + } + + private static XStream config_WxMpXmlOutImageMessage() { + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxMpXmlOutMessage.class); + xstream.processAnnotations(WxMpXmlOutImageMessage.class); + return xstream; + } + + private static XStream config_WxMpXmlOutNewsMessage() { + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxMpXmlOutMessage.class); + xstream.processAnnotations(WxMpXmlOutNewsMessage.class); + xstream.processAnnotations(WxMpXmlOutNewsMessage.Item.class); + return xstream; + } + + private static XStream config_WxMpXmlOutMusicMessage() { + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxMpXmlOutMessage.class); + xstream.processAnnotations(WxMpXmlOutMusicMessage.class); + xstream.processAnnotations(WxMpXmlOutMusicMessage.Music.class); + return xstream; + } + + private static XStream config_WxMpXmlOutTextMessage() { + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxMpXmlOutMessage.class); + xstream.processAnnotations(WxMpXmlOutTextMessage.class); + return xstream; + } + + private static XStream config_WxMpXmlOutVideoMessage() { + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxMpXmlOutMessage.class); + xstream.processAnnotations(WxMpXmlOutVideoMessage.class); + xstream.processAnnotations(WxMpXmlOutVideoMessage.Video.class); + return xstream; + } + + private static XStream config_WxMpXmlOutVoiceMessage() { + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxMpXmlOutMessage.class); + xstream.processAnnotations(WxMpXmlOutVoiceMessage.class); + return xstream; + } + +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XmlTransformer.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XmlTransformer.java deleted file mode 100644 index bff84eab..00000000 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XmlTransformer.java +++ /dev/null @@ -1,91 +0,0 @@ -package me.chanjar.weixin.mp.util.xml; - -import com.sun.xml.bind.marshaller.CharacterEscapeHandler; -import me.chanjar.weixin.mp.bean.*; -import org.xml.sax.InputSource; - -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; -import java.io.*; - -public class XmlTransformer { - - protected static final JAXBContext JAXB_CONTEXT = initJAXBContext(); - - /** - * xml -> pojo - * - * @param clazz - * @param xml - * @return - * @throws JAXBException - */ - @SuppressWarnings("unchecked") - public static T fromXml(Class 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 fromXml(Class 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 String toXml(Class clazz, T object) throws JAXBException { - StringWriter stringWriter = new StringWriter(); - toXml(clazz, object, stringWriter); - return stringWriter.getBuffer().toString(); - } - - public static void toXml(Class 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( - WxMpXmlOutMessage.class, - WxMpMpXmlOutImageMessage.class, - WxMpXmlOutNewsMessage.class, - WxMpXmlOutMusicMessage.class, - WxMpXmlOutTextMessage.class, - WxMpXmlOutVideoMessage.class, - WxMpXmlOutVoiceMessage.class, - WxMpXmlMessage.class); - } catch (JAXBException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java index ca4cbc13..e6df8e34 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/ApiTestModule.java @@ -1,46 +1,35 @@ package me.chanjar.weixin.mp.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"); - WxXmlMpInMemoryConfigStorage config = fromXml(WxXmlMpInMemoryConfigStorage.class, is1); - WxMpServiceImpl wxService = new WxMpServiceImpl(); - wxService.setWxMpConfigStorage(config); - - binder.bind(WxMpServiceImpl.class).toInstance(wxService); - binder.bind(WxMpConfigStorage.class).toInstance(config); - } catch (JAXBException e) { - throw new RuntimeException(e); - } + InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); + WxXmlMpInMemoryConfigStorage config = fromXml(WxXmlMpInMemoryConfigStorage.class, is1); + WxMpServiceImpl wxService = new WxMpServiceImpl(); + wxService.setWxMpConfigStorage(config); + + binder.bind(WxMpServiceImpl.class).toInstance(wxService); + binder.bind(WxMpConfigStorage.class).toInstance(config); } - public static T fromXml(Class 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 fromXml(Class 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) - public static class WxXmlMpInMemoryConfigStorage extends WxMpInMemoryConfigStorage { + @XStreamAlias("xml") + public static class WxXmlMpInMemoryConfigStorage extends WxMpInMemoryConfigStorage { protected String openId; @@ -53,7 +42,7 @@ public class ApiTestModule implements Module { @Override public String toString() { return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken - + ", expiresIn=" + expiresIn + ", token=" + token + ", openId=" + openId + "]"; + + ", expiresTime=" + expiresTime + ", token=" + token + ", openId=" + openId + "]"; } } diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxBaseAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java similarity index 92% rename from weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxBaseAPITest.java rename to weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java index f87d97a4..c4210b57 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxBaseAPITest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java @@ -1,14 +1,12 @@ package me.chanjar.weixin.mp.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 chanjarster @@ -16,7 +14,7 @@ import com.google.inject.Inject; */ @Test(groups = "baseAPI") @Guice(modules = ApiTestModule.class) -public class WxBaseAPITest { +public class WxMpBaseAPITest { @Inject protected WxMpServiceImpl wxService; @@ -24,10 +22,9 @@ public class WxBaseAPITest { public void testRefreshAccessToken() throws WxErrorException { WxMpConfigStorage configStorage = wxService.wxMpConfigStorage; String before = configStorage.getAccessToken(); - wxService.accessTokenRefresh(); + wxService.getAccessToken(false); String after = configStorage.getAccessToken(); - Assert.assertNotEquals(before, after); Assert.assertTrue(StringUtils.isNotBlank(after)); } diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBusyRetryTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBusyRetryTest.java new file mode 100644 index 00000000..2e16723a --- /dev/null +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBusyRetryTest.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.mp.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 WxMpBusyRetryTest { + + @DataProvider(name="getService") + public Object[][] getService() { + WxMpService service = new WxMpServiceImpl() { + + @Override + protected T executeInternal(RequestExecutor 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(WxMpService service) throws WxErrorException { + service.execute(null, null, null); + } + + @Test(dataProvider = "getService") + public void testRetryInThreadPool(final WxMpService 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(); + } + +} diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpCustomMessageAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpCustomMessageAPITest.java index 04e8400e..d0d9cde2 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpCustomMessageAPITest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpCustomMessageAPITest.java @@ -1,14 +1,12 @@ package me.chanjar.weixin.mp.api; +import com.google.inject.Inject; import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.exception.WxErrorException; import me.chanjar.weixin.mp.bean.WxMpCustomMessage; import org.testng.annotations.Guice; import org.testng.annotations.Test; -import me.chanjar.weixin.common.exception.WxErrorException; - -import com.google.inject.Inject; - /*** * 测试发送客服消息 * @author chanjarster diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpGroupAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpGroupAPITest.java index ca6f20ed..4397e057 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpGroupAPITest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpGroupAPITest.java @@ -1,15 +1,13 @@ package me.chanjar.weixin.mp.api; -import java.util.List; - +import com.google.inject.Inject; +import me.chanjar.weixin.common.exception.WxErrorException; import me.chanjar.weixin.mp.bean.WxMpGroup; 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; /** * 测试分组接口 diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpJsAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpJsAPITest.java new file mode 100644 index 00000000..16506caa --- /dev/null +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpJsAPITest.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.mp.api; + +import com.google.inject.Inject; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.util.crypto.SHA1; +import me.chanjar.weixin.mp.bean.WxMpGroup; +import org.testng.Assert; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.security.NoSuchAlgorithmException; +import java.util.List; + +/** + * 测试jsapi ticket接口 + * + * @author chanjarster + */ +@Test(groups = "jsAPI", dependsOnGroups = "baseAPI") +@Guice(modules = ApiTestModule.class) +public class WxMpJsAPITest { + + @Inject + protected WxMpServiceImpl wxService; + + + public void testJsapiTicket() throws WxErrorException { + String jsapiTicket = wxService.getJsapiTicket(false); + System.out.println(jsapiTicket); + Assert.assertNotNull(jsapiTicket); + } + + public void test() throws NoSuchAlgorithmException { + long timestamp = 1419835025l; + String url = "http://omstest.vmall.com:23568/thirdparty/wechat/vcode/gotoshare?quantity=1&batchName=MATE7"; + String noncestr = "82693e11-b9bc-448e-892f-f5289f46cd0f"; + String jsapiTicket = "bxLdikRXVbTPdHSM05e5u4RbEYQn7pNQMPrfzl8lJNb1foLDa3HIwI3BRMkQmSO_5F64VFa75uURcq6Uz7QHgA"; + String result = SHA1.genWithAmple( + "jsapi_ticket=" + jsapiTicket, + "noncestr=" + noncestr, + "timestamp=" + timestamp, + "url=" + url + ); + + Assert.assertEquals(result, "c6f04b64d6351d197b71bd23fb7dd2d44c0db486"); + } + +} diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMassMessageAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMassMessageAPITest.java index 78a48c2f..73ebd1e2 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMassMessageAPITest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMassMessageAPITest.java @@ -1,10 +1,9 @@ package me.chanjar.weixin.mp.api; -import java.io.IOException; -import java.io.InputStream; - +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 me.chanjar.weixin.mp.bean.WxMpMassGroupMessage; import me.chanjar.weixin.mp.bean.WxMpMassNews; import me.chanjar.weixin.mp.bean.WxMpMassOpenIdsMessage; @@ -16,9 +15,8 @@ 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; /** * 测试群发消息 diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMediaAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMediaAPITest.java index e012df40..0df7103d 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMediaAPITest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMediaAPITest.java @@ -1,20 +1,18 @@ package me.chanjar.weixin.mp.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; /** * 测试多媒体文件上传下载 diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMenuAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMenuAPITest.java index 335442b7..01acb179 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMenuAPITest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMenuAPITest.java @@ -1,19 +1,15 @@ package me.chanjar.weixin.mp.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; -import me.chanjar.weixin.common.bean.WxMenu.WxMenuButton; -import me.chanjar.weixin.common.exception.WxErrorException; - /** * 测试菜单 * @author chanjarster @@ -42,7 +38,7 @@ public class WxMpMenuAPITest { } @DataProvider(name="menu") - public Object[][] getMenu() throws JAXBException { + public Object[][] getMenu() { WxMenu menu = new WxMenu(); WxMenuButton button1 = new WxMenuButton(); button1.setType(WxConsts.BUTTON_CLICK); diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java index b3c2460b..0b636985 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java @@ -1,14 +1,16 @@ package me.chanjar.weixin.mp.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.mp.bean.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import java.util.Map; + /** * 测试消息路由器 * @author chanjarster @@ -67,7 +69,8 @@ public class WxMpMessageRouterTest { final WxMpMessageRouter router = new WxMpMessageRouter(null); router.rule().handler(new WxMpMessageHandler() { @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, WxMpService wxMpService) { + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { return null; } }).end(); @@ -149,11 +152,144 @@ public class WxMpMessageRouterTest { } @Override - public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, WxMpService wxMpService) { + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, WxMpService wxMpService, + 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 WxMpMessageRouter router = new WxMpMessageRouter(null); + router.setSessionManager(ism); + router + .rule().async(false).handler(new WxSessionMessageHandler()).next() + .rule().async(false).handler(new WxSessionMessageHandler()).end(); + + WxMpXmlMessage msg = new WxMpXmlMessage(); + 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 WxMpMessageRouter router = new WxMpMessageRouter(null); + router.setSessionManager(ism); + router + .rule().async(false).handler(new WxSessionMessageHandler()).next() + .rule().async(true).handler(new WxSessionMessageHandler()).end(); + + WxMpXmlMessage msg = new WxMpXmlMessage(); + msg.setFromUserName("abc"); + router.route(msg); + + Thread.sleep(2000l); + Assert.assertEquals(ism.getActiveSessions(), 0); + } + { + final WxMpMessageRouter router = new WxMpMessageRouter(null); + router.setSessionManager(ism); + router + .rule().async(true).handler(new WxSessionMessageHandler()).next() + .rule().async(false).handler(new WxSessionMessageHandler()).end(); + + WxMpXmlMessage msg = new WxMpXmlMessage(); + 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 WxMpMessageRouter router = new WxMpMessageRouter(null); + router.setSessionManager(ism); + router + .rule().async(true).handler(new WxSessionMessageHandler()).next() + .rule().async(true).handler(new WxSessionMessageHandler()).end(); + + WxMpXmlMessage msg = new WxMpXmlMessage(); + 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 WxMpMessageRouter router = new WxMpMessageRouter(null); + router.setSessionManager(ism); + router + .rule().async(false).handler(new WxSessionMessageHandler()).end(); + + WxMpXmlMessage msg = new WxMpXmlMessage(); + msg.setFromUserName("abc"); + router.route(msg); + + Thread.sleep(2000l); + Assert.assertEquals(ism.getActiveSessions(), 0); + } + + { + final WxMpMessageRouter router = new WxMpMessageRouter(null); + router.setSessionManager(ism); + router + .rule().async(true).handler(new WxSessionMessageHandler()).end(); + + WxMpXmlMessage msg = new WxMpXmlMessage(); + msg.setFromUserName("abc"); + router.route(msg); + + Thread.sleep(2000l); + Assert.assertEquals(ism.getActiveSessions(), 0); + } + } + + public static class WxSessionMessageHandler implements WxMpMessageHandler { + + @Override + public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, WxMpService wxMpService, + WxSessionManager sessionManager) { + sessionManager.getSession(wxMessage.getFromUserName()); + return null; + } + + } + } diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpQrCodeAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpQrCodeAPITest.java index 2a206d2e..1fa76e49 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpQrCodeAPITest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpQrCodeAPITest.java @@ -1,15 +1,13 @@ package me.chanjar.weixin.mp.api; -import java.io.File; - +import com.google.inject.Inject; +import me.chanjar.weixin.common.exception.WxErrorException; import me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket; 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.io.File; /** * 测试用户相关的接口 diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpShortUrlAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpShortUrlAPITest.java index e854ed08..fb97f2d4 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpShortUrlAPITest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpShortUrlAPITest.java @@ -1,13 +1,11 @@ package me.chanjar.weixin.mp.api; +import com.google.inject.Inject; +import me.chanjar.weixin.common.exception.WxErrorException; 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; - /** * 测试短连接 * diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpUserAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpUserAPITest.java index c29bda83..fa978c57 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpUserAPITest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpUserAPITest.java @@ -1,15 +1,13 @@ package me.chanjar.weixin.mp.api; +import com.google.inject.Inject; +import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.mp.bean.result.WxMpUser; import me.chanjar.weixin.mp.bean.result.WxMpUserList; import org.testng.Assert; import org.testng.annotations.Guice; import org.testng.annotations.Test; -import me.chanjar.weixin.mp.bean.result.WxMpUser; -import me.chanjar.weixin.common.exception.WxErrorException; - -import com.google.inject.Inject; - /** * 测试用户相关的接口 * @author chanjarster diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpCustomMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpCustomMessageTest.java index 5e3d5620..aa8de0df 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpCustomMessageTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpCustomMessageTest.java @@ -1,11 +1,10 @@ package me.chanjar.weixin.mp.bean; import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.mp.bean.WxMpCustomMessage.WxArticle; import org.testng.Assert; import org.testng.annotations.Test; -import me.chanjar.weixin.mp.bean.WxMpCustomMessage.WxArticle; - @Test public class WxMpCustomMessageTest { diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessageTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessageTest.java index f4b50e1d..9c92cab8 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessageTest.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessageTest.java @@ -7,7 +7,7 @@ import org.testng.annotations.Test; public class WxMpXmlOutImageMessageTest { public void test() { - WxMpMpXmlOutImageMessage m = new WxMpMpXmlOutImageMessage(); + WxMpXmlOutImageMessage m = new WxMpXmlOutImageMessage(); m.setMediaId("ddfefesfsdfef"); m.setCreateTime(1122l); m.setFromUserName("from"); @@ -25,7 +25,7 @@ public class WxMpXmlOutImageMessageTest { } public void testBuild() { - WxMpMpXmlOutImageMessage m = WxMpXmlOutMessage.IMAGE().mediaId("ddfefesfsdfef").fromUser("from").toUser("to").build(); + WxMpXmlOutImageMessage m = WxMpXmlOutMessage.IMAGE().mediaId("ddfefesfsdfef").fromUser("from").toUser("to").build(); String expected = "" + "" + "" diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java index 430d327d..becfbeb4 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoInMemoryConfigStorage.java @@ -1,35 +1,29 @@ package me.chanjar.weixin.mp.demo; +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import me.chanjar.weixin.common.util.xml.XStreamInitializer; import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage; -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 WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage { @Override public String toString() { return "SimpleWxConfigProvider [appId=" + appId + ", secret=" + secret + ", accessToken=" + accessToken - + ", expiresIn=" + expiresIn + ", token=" + token + ", aesKey=" + aesKey + "]"; + + ", expiresTime=" + expiresTime + ", token=" + token + ", aesKey=" + aesKey + "]"; } - public static WxMpDemoInMemoryConfigStorage fromXml(InputStream is) throws JAXBException { - Unmarshaller um = JAXBContext.newInstance(WxMpDemoInMemoryConfigStorage.class).createUnmarshaller(); - InputSource inputSource = new InputSource(is); - inputSource.setEncoding("utf-8"); - return (WxMpDemoInMemoryConfigStorage) um.unmarshal(inputSource); + public static WxMpDemoInMemoryConfigStorage fromXml(InputStream is) { + XStream xstream = XStreamInitializer.getInstance(); + xstream.processAnnotations(WxMpDemoInMemoryConfigStorage.class); + return (WxMpDemoInMemoryConfigStorage) xstream.fromXML(is); } } diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java index 8fc8008f..8e44d2db 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpDemoServer.java @@ -3,15 +3,16 @@ package me.chanjar.weixin.mp.demo; import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.exception.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.mp.api.*; -import me.chanjar.weixin.mp.bean.WxMpMpXmlOutImageMessage; import me.chanjar.weixin.mp.bean.WxMpXmlMessage; +import me.chanjar.weixin.mp.bean.WxMpXmlOutImageMessage; import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; import me.chanjar.weixin.mp.bean.WxMpXmlOutTextMessage; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.*; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; -import javax.xml.bind.JAXBException; import java.io.IOException; import java.io.InputStream; import java.util.Map; @@ -41,7 +42,6 @@ public class WxMpDemoServer { } private static void initWeixin() { - try { InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml"); WxMpDemoInMemoryConfigStorage config = WxMpDemoInMemoryConfigStorage.fromXml(is1); @@ -52,7 +52,7 @@ public class WxMpDemoServer { WxMpMessageHandler textHandler = new WxMpMessageHandler() { @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService wxMpService) { + WxMpService wxMpService, WxSessionManager sessionManager) { WxMpXmlOutTextMessage m = WxMpXmlOutMessage.TEXT().content("测试加密消息").fromUser(wxMessage.getToUserName()) .toUser(wxMessage.getFromUserName()).build(); @@ -63,11 +63,11 @@ public class WxMpDemoServer { WxMpMessageHandler imageHandler = new WxMpMessageHandler() { @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService wxMpService) { + WxMpService wxMpService, WxSessionManager sessionManager) { try { WxMediaUploadResult wxMediaUploadResult = wxMpService .mediaUpload(WxConsts.MEDIA_IMAGE, WxConsts.FILE_JPG, ClassLoader.getSystemResourceAsStream("mm.jpeg")); - WxMpMpXmlOutImageMessage m + WxMpXmlOutImageMessage m = WxMpXmlOutMessage .IMAGE() .mediaId(wxMediaUploadResult.getMediaId()) @@ -87,7 +87,7 @@ public class WxMpDemoServer { WxMpMessageHandler oauth2handler = new WxMpMessageHandler() { @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map context, - WxMpService wxMpService) { + WxMpService wxMpService, WxSessionManager sessionManager) { String href = "测试oauth2"; return WxMpXmlOutMessage @@ -117,8 +117,5 @@ public class WxMpDemoServer { .end() ; - } catch (JAXBException e) { - throw new RuntimeException(e); - } } } diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpEndpointServlet.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpEndpointServlet.java index 4a88ede9..36bc77ee 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpEndpointServlet.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpEndpointServlet.java @@ -1,23 +1,17 @@ package me.chanjar.weixin.mp.demo; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; -import me.chanjar.weixin.common.exception.WxErrorException; import me.chanjar.weixin.common.util.StringUtils; -import me.chanjar.weixin.mp.api.*; -import me.chanjar.weixin.mp.bean.WxMpMpXmlOutImageMessage; +import me.chanjar.weixin.mp.api.WxMpConfigStorage; +import me.chanjar.weixin.mp.api.WxMpMessageRouter; +import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.bean.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; -import me.chanjar.weixin.mp.bean.WxMpXmlOutTextMessage; 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 diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpOAuth2Servlet.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpOAuth2Servlet.java index 6ddf4ad3..e0291b10 100644 --- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpOAuth2Servlet.java +++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpOAuth2Servlet.java @@ -1,26 +1,15 @@ package me.chanjar.weixin.mp.demo; -import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; import me.chanjar.weixin.common.exception.WxErrorException; -import me.chanjar.weixin.common.util.StringUtils; -import me.chanjar.weixin.mp.api.*; -import me.chanjar.weixin.mp.bean.WxMpMpXmlOutImageMessage; -import me.chanjar.weixin.mp.bean.WxMpXmlMessage; -import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; -import me.chanjar.weixin.mp.bean.WxMpXmlOutTextMessage; +import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; import me.chanjar.weixin.mp.bean.result.WxMpUser; -import javax.servlet.ServletContext; 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; public class WxMpOAuth2Servlet extends HttpServlet { diff --git a/weixin-java-mp/src/test/resources/logback-test.xml b/weixin-java-mp/src/test/resources/logback-test.xml new file mode 100644 index 00000000..ed595101 --- /dev/null +++ b/weixin-java-mp/src/test/resources/logback-test.xml @@ -0,0 +1,16 @@ + + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + diff --git a/weixin-java-mp/src/test/resources/test-config.sample.xml b/weixin-java-mp/src/test/resources/test-config.sample.xml index 19535f3c..6ec4825a 100644 --- a/weixin-java-mp/src/test/resources/test-config.sample.xml +++ b/weixin-java-mp/src/test/resources/test-config.sample.xml @@ -4,7 +4,7 @@ 公众号Token 公众号EncodingAESKey 可以不填写 - 可以不填写 + 可以不填写 某个加你公众号的用户的openId 网页授权获取用户信息回调地址 diff --git a/weixin-java-mp/src/test/resources/testng.xml b/weixin-java-mp/src/test/resources/testng.xml index 8987f762..cf9c2d64 100644 --- a/weixin-java-mp/src/test/resources/testng.xml +++ b/weixin-java-mp/src/test/resources/testng.xml @@ -3,7 +3,8 @@ - + + @@ -13,6 +14,7 @@ +