From 72611b2654b324dc5795af961ab34b786f21962c Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Tue, 30 Dec 2014 09:45:20 +0800 Subject: [PATCH 01/33] upgrade to 1.0.7-SNAPSHOT --- pom.xml | 2 +- weixin-java-common/pom.xml | 2 +- weixin-java-cp/pom.xml | 2 +- weixin-java-mp/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 65107a6b..2c90f673 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 me.chanjar weixin-java-parent - 1.0.6 + 1.0.7-SNAPSHOT pom WeiXin Java Tools - Parent 微信公众号、企业号上级POM diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index 0e14fcae..dc2a75e3 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -6,7 +6,7 @@ me.chanjar weixin-java-parent - 1.0.6 + 1.0.7-SNAPSHOT weixin-java-common diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml index 2ec3e36a..01633152 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.0.7-SNAPSHOT weixin-java-cp diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml index 2f757cfa..b3d126f2 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.0.7-SNAPSHOT weixin-java-mp WeiXin Java Tools - MP From 9757dd16fc0e702b12fc55662c83fd4688ec3525 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Mon, 19 Jan 2015 11:10:45 +0800 Subject: [PATCH 02/33] =?UTF-8?q?issue=20#72=20WxCpUser=E7=BC=BA=E5=B0=91a?= =?UTF-8?q?vatar,status=E5=B1=9E=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../me/chanjar/weixin/cp/bean/WxCpUser.java | 18 ++++++++++++++++++ .../cp/util/json/WxCpUserGsonAdapter.java | 8 ++++++++ 2 files changed, 26 insertions(+) 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/util/json/WxCpUserGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java index bf04d50c..bfff55a7 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 @@ -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()) { From eaa77bc7b3d1666f782ff945b62c931a75fed51b Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Mon, 19 Jan 2015 11:22:18 +0800 Subject: [PATCH 03/33] remove backup file --- pom.xml.versionsBackup | 229 ---------------------- weixin-java-common/pom.xml.versionsBackup | 68 ------- weixin-java-cp/pom.xml.versionsBackup | 73 ------- weixin-java-mp/pom.xml.versionsBackup | 72 ------- 4 files changed, 442 deletions(-) delete mode 100644 pom.xml.versionsBackup delete mode 100644 weixin-java-common/pom.xml.versionsBackup delete mode 100644 weixin-java-cp/pom.xml.versionsBackup delete mode 100644 weixin-java-mp/pom.xml.versionsBackup 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.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-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-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 - - - - - - - From f3ec0b19655e0513e546e37e2ca71cf6667fb23f Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Mon, 19 Jan 2015 15:12:14 +0800 Subject: [PATCH 04/33] =?UTF-8?q?issue=20#71=20=E6=9B=BF=E6=8D=A2=E6=8E=89?= =?UTF-8?q?JAXB=EF=BC=8C=E9=81=BF=E5=85=8D=E5=9B=A0OpenJDK=E9=80=A0?= =?UTF-8?q?=E6=88=90=E6=97=A0=E6=B3=95deserialize=20xml=E7=9A=84=E9=94=99?= =?UTF-8?q?=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 10 - weixin-java-common/pom.xml | 127 ++++++------ .../weixin/common/util/xml/AdapterCDATA.java | 24 --- .../common/util/xml/MediaIdMarshaller.java | 21 -- .../util/xml/XStreamCDataConverter.java | 15 ++ .../common/util/xml/XStreamInitializer.java | 46 +++++ .../util/xml/XStreamMediaIdConverter.java | 11 ++ .../weixin/cp/api/WxCpMessageHandler.java | 4 +- .../weixin/cp/api/WxCpMessageInterceptor.java | 4 +- .../weixin/cp/api/WxCpMessageRouter.java | 6 +- .../me/chanjar/weixin/cp/api/WxCpService.java | 15 +- .../chanjar/weixin/cp/bean/WxCpMessage.java | 6 +- .../weixin/cp/bean/WxCpXmlMessage.java | 174 ++++++++--------- .../cp/bean/WxCpXmlOutImageMessage.java | 17 +- .../weixin/cp/bean/WxCpXmlOutMessage.java | 38 ++-- .../weixin/cp/bean/WxCpXmlOutNewsMessage.java | 33 ++-- .../weixin/cp/bean/WxCpXmlOutTextMessage.java | 17 +- .../cp/bean/WxCpXmlOutVideoMessage.java | 32 ++-- .../cp/bean/WxCpXmlOutVoiceMessage.java | 17 +- .../cp/bean/outxmlbuilder/NewsBuilder.java | 6 +- .../cp/util/json/WxCpDepartGsonAdapter.java | 11 +- .../weixin/cp/util/json/WxCpGsonBuilder.java | 7 +- .../cp/util/json/WxCpUserGsonAdapter.java | 4 +- .../cp/util/xml/XStreamTransformer.java | 106 +++++++++++ .../weixin/cp/util/xml/XmlTransformer.java | 93 --------- .../chanjar/weixin/cp/api/ApiTestModule.java | 33 ++-- .../weixin/cp/api/WxCpBaseAPITest.java | 6 +- .../weixin/cp/api/WxCpDepartAPITest.java | 8 +- .../weixin/cp/api/WxCpMediaAPITest.java | 14 +- .../weixin/cp/api/WxCpMessageAPITest.java | 6 +- .../weixin/cp/api/WxCpMessageRouterTest.java | 4 +- .../weixin/cp/api/WxCpUserAPITest.java | 2 +- .../chanjar/weixin/cp/api/WxMenuAPITest.java | 12 +- .../weixin/cp/bean/WxCpMessageTest.java | 3 +- .../demo/WxCpDemoInMemoryConfigStorage.java | 22 +-- .../weixin/cp/demo/WxCpDemoServer.java | 5 - .../weixin/cp/demo/WxCpEndpointServlet.java | 8 +- .../me/chanjar/weixin/mp/api/WxMpService.java | 2 +- .../mp/bean/WxMpMpXmlOutImageMessage.java | 32 ---- .../weixin/mp/bean/WxMpXmlMessage.java | 180 ++++++++---------- .../mp/bean/WxMpXmlOutImageMessage.java | 27 +++ .../weixin/mp/bean/WxMpXmlOutMessage.java | 38 ++-- .../mp/bean/WxMpXmlOutMusicMessage.java | 40 ++-- .../weixin/mp/bean/WxMpXmlOutNewsMessage.java | 33 ++-- .../weixin/mp/bean/WxMpXmlOutTextMessage.java | 17 +- .../mp/bean/WxMpXmlOutVideoMessage.java | 32 ++-- .../mp/bean/WxMpXmlOutVoiceMessage.java | 23 +-- .../mp/bean/outxmlbuilder/ImageBuilder.java | 8 +- .../mp/bean/result/WxMpOAuth2AccessToken.java | 1 - .../mp/util/http/QrCodeRequestExecutor.java | 2 +- .../weixin/mp/util/json/WxMpGsonBuilder.java | 1 - .../json/WxMpOAuth2AccessTokenAdapter.java | 1 - .../json/WxMpSemanticQueryResultAdapter.java | 2 - .../json/WxMpTemplateMessageGsonAdapter.java | 7 +- .../mp/util/xml/XStreamTransformer.java | 117 ++++++++++++ .../weixin/mp/util/xml/XmlTransformer.java | 91 --------- .../chanjar/weixin/mp/api/ApiTestModule.java | 49 ++--- .../chanjar/weixin/mp/api/WxBaseAPITest.java | 6 +- .../mp/api/WxMpCustomMessageAPITest.java | 6 +- .../weixin/mp/api/WxMpGroupAPITest.java | 8 +- .../weixin/mp/api/WxMpMassMessageAPITest.java | 10 +- .../weixin/mp/api/WxMpMediaAPITest.java | 14 +- .../weixin/mp/api/WxMpMenuAPITest.java | 14 +- .../weixin/mp/api/WxMpMessageRouterTest.java | 4 +- .../weixin/mp/api/WxMpQrCodeAPITest.java | 8 +- .../weixin/mp/api/WxMpShortUrlAPITest.java | 6 +- .../weixin/mp/api/WxMpUserAPITest.java | 8 +- .../weixin/mp/bean/WxMpCustomMessageTest.java | 3 +- .../mp/bean/WxMpXmlOutImageMessageTest.java | 4 +- .../demo/WxMpDemoInMemoryConfigStorage.java | 22 +-- .../weixin/mp/demo/WxMpDemoServer.java | 12 +- .../weixin/mp/demo/WxMpEndpointServlet.java | 12 +- .../weixin/mp/demo/WxMpOAuth2Servlet.java | 13 +- 73 files changed, 834 insertions(+), 986 deletions(-) delete mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/AdapterCDATA.java delete mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/MediaIdMarshaller.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataConverter.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamMediaIdConverter.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java delete mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XmlTransformer.java delete mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpMpXmlOutImageMessage.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/WxMpXmlOutImageMessage.java create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XStreamTransformer.java delete mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/xml/XmlTransformer.java diff --git a/pom.xml b/pom.xml index 2c90f673..83d777a2 100644 --- a/pom.xml +++ b/pom.xml @@ -55,16 +55,6 @@ 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 diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index dc2a75e3..34bd0c71 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.7-SNAPSHOT - + 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.0.7-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/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..69bad096 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamCDataConverter.java @@ -0,0 +1,15 @@ +package me.chanjar.weixin.common.util.xml; + +import com.thoughtworks.xstream.converters.basic.StringConverter; + +/** + * Created by qianjia on 15/1/19. + */ +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..c26b64a3 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java @@ -0,0 +1,46 @@ +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 java.io.Writer; + +/** + * Created by qianjia on 15/1/19. + */ +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.setMode(XStream.NO_REFERENCES); + 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..0e375ea5 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamMediaIdConverter.java @@ -0,0 +1,11 @@ +package me.chanjar.weixin.common.util.xml; + +/** + * Created by qianjia on 15/1/19. + */ +public class XStreamMediaIdConverter extends XStreamCDataConverter { + @Override + public String toString(Object obj) { + return "" + super.toString(obj) + ""; + } +} 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..e4e26eb2 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,10 +1,10 @@ package me.chanjar.weixin.cp.api; -import java.util.Map; - import me.chanjar.weixin.cp.bean.WxCpXmlMessage; import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; +import java.util.Map; + /** * 处理微信推送消息的处理器接口 * @author Daniel Qian 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..5cb478e3 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,9 +1,9 @@ package me.chanjar.weixin.cp.api; -import java.util.Map; - import me.chanjar.weixin.cp.bean.WxCpXmlMessage; +import java.util.Map; + /** * 微信消息拦截器,可以用来做验证 * @author Daniel Qian 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..5b4e1820 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,5 +1,8 @@ package me.chanjar.weixin.cp.api; +import me.chanjar.weixin.cp.bean.WxCpXmlMessage; +import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -8,9 +11,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.regex.Pattern; -import me.chanjar.weixin.cp.bean.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; - /** *
  * 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理
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..808eb6ce 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,18 @@
 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.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
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/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 bfff55a7..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
  */
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..13eeac17
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
@@ -0,0 +1,106 @@
+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;
+
+/**
+ * Created by qianjia on 15/1/19.
+ */
+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..6ed438a9 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
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..b3dc6c30 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,7 +1,5 @@
 package me.chanjar.weixin.cp.api;
 
-import java.util.Map;
-
 import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
 import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
@@ -9,6 +7,8 @@ import org.testng.Assert;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import java.util.Map;
+
 /**
  * 测试消息路由器
  * @author Daniel Qian
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..93e226a5 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;
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..1d9103a5 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,21 +1,16 @@
 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
@@ -25,11 +20,10 @@ class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage {
   }
 
 
-  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..81786100 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
@@ -8,7 +8,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 +36,6 @@ public class WxCpDemoServer {
   }
 
   private static void initWeixin() {
-    try {
       InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml");
       WxCpDemoInMemoryConfigStorage config = WxCpDemoInMemoryConfigStorage.fromXml(is1);
 
@@ -87,8 +85,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-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..c4dc3e54 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,10 @@ 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.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;
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/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 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..ad006acd 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;
 
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/WxBaseAPITest.java
index f87d97a4..71ca80c0 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/WxBaseAPITest.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
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/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..3f7e841e 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,7 +1,5 @@
 package me.chanjar.weixin.mp.api;
 
-import java.util.Map;
-
 import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.mp.bean.WxMpXmlMessage;
 import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage;
@@ -9,6 +7,8 @@ import org.testng.Assert;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
 
+import java.util.Map;
+
 /**
  * 测试消息路由器
  * @author chanjarster
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..22d87dd4 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,21 +1,16 @@
 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
@@ -25,11 +20,10 @@ class WxMpDemoInMemoryConfigStorage extends WxMpInMemoryConfigStorage {
   }
 
 
-  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..c58671cf 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
@@ -4,14 +4,14 @@ 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.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 +41,6 @@ public class WxMpDemoServer {
   }
 
   private static void initWeixin() {
-    try {
       InputStream is1 = ClassLoader.getSystemResourceAsStream("test-config.xml");
       WxMpDemoInMemoryConfigStorage config = WxMpDemoInMemoryConfigStorage.fromXml(is1);
 
@@ -67,7 +66,7 @@ public class WxMpDemoServer {
           try {
             WxMediaUploadResult wxMediaUploadResult = wxMpService
                 .mediaUpload(WxConsts.MEDIA_IMAGE, WxConsts.FILE_JPG, ClassLoader.getSystemResourceAsStream("mm.jpeg"));
-            WxMpMpXmlOutImageMessage m
+            WxMpXmlOutImageMessage m
                 = WxMpXmlOutMessage
                 .IMAGE()
                 .mediaId(wxMediaUploadResult.getMediaId())
@@ -117,8 +116,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 {
 

From 3ba128f6566600682fc9826fd756f2d8ff2bac75 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Mon, 19 Jan 2015 16:46:52 +0800
Subject: [PATCH 05/33] issue #71

---
 .../chanjar/weixin/common/util/xml/XStreamInitializer.java | 7 +++++++
 .../mp/api/{WxBaseAPITest.java => WxMpBaseAPITest.java}    | 2 +-
 weixin-java-mp/src/test/resources/testng.xml               | 2 +-
 3 files changed, 9 insertions(+), 2 deletions(-)
 rename weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/{WxBaseAPITest.java => WxMpBaseAPITest.java} (96%)

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
index c26b64a3..163c6558 100644
--- 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
@@ -8,6 +8,9 @@ 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;
 
@@ -18,6 +21,7 @@ public class XStreamInitializer {
 
   public static XStream getInstance() {
     XStream xstream = new XStream(new XppDriver() {
+
       @Override
       public HierarchicalStreamWriter createWriter(Writer out) {
         return new PrettyPrintWriter(out, getNameCoder()) {
@@ -39,7 +43,10 @@ public class XStreamInitializer {
         };
       }
     });
+    xstream.ignoreUnknownElements();
     xstream.setMode(XStream.NO_REFERENCES);
+    xstream.addPermission(NullPermission.NULL);
+    xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
     return xstream;
   }
 
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 96%
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 71ca80c0..ef9590e6 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
@@ -14,7 +14,7 @@ import org.testng.annotations.Test;
  */
 @Test(groups = "baseAPI")
 @Guice(modules = ApiTestModule.class)
-public class WxBaseAPITest {
+public class WxMpBaseAPITest {
 
   @Inject
   protected WxMpServiceImpl wxService;
diff --git a/weixin-java-mp/src/test/resources/testng.xml b/weixin-java-mp/src/test/resources/testng.xml
index 8987f762..17308c78 100644
--- a/weixin-java-mp/src/test/resources/testng.xml
+++ b/weixin-java-mp/src/test/resources/testng.xml
@@ -3,7 +3,7 @@
 
 	
 		
-			
+			
 			
 			
 			

From 85168a14aa9833fa4f9af85c378bbdb1a518f975 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Mon, 19 Jan 2015 17:25:41 +0800
Subject: [PATCH 06/33] =?UTF-8?q?issue=20#66=20=E6=B6=88=E6=81=AF=E5=8E=BB?=
 =?UTF-8?q?=E9=87=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../api/WxMpDuplicateMessageInterceptor.java  | 88 +++++++++++++++++++
 1 file changed, 88 insertions(+)
 create mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDuplicateMessageInterceptor.java

diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDuplicateMessageInterceptor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDuplicateMessageInterceptor.java
new file mode 100644
index 00000000..970bbba4
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDuplicateMessageInterceptor.java
@@ -0,0 +1,88 @@
+package me.chanjar.weixin.mp.api;
+
+import me.chanjar.weixin.mp.bean.WxMpXmlMessage;
+import sun.applet.Main;
+
+import java.net.SocketTimeoutException;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 
+ * 消息去重拦截器
+ * 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
+ * 使用方法:
+ * WxMpMessageRouter router = new WxMpMessageRouter();
+ * router
+ *   .rule()
+ *       .interceptor(new WxMpDuplicateMessageInterceptor())
+ *   .next()
+ *   .rule()
+ *       .msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT")
+ *       .interceptor(interceptor, ...).handler(handler, ...)
+ *   .end()
+ *   .rule()
+ *       // 另外一个匹配规则
+ *   .end()
+ * ;
+ * 
+ */ +public class WxMpDuplicateMessageInterceptor implements WxMpMessageInterceptor { + + private static final Long PERIOD = 15 * 1000l; + + private final ConcurrentHashMap msgId2timestamp; + + public WxMpDuplicateMessageInterceptor() { + this.msgId2timestamp = new ConcurrentHashMap(); + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + while (true) { + Thread.sleep(PERIOD); + Long now = System.currentTimeMillis(); + for (Map.Entry entry : msgId2timestamp.entrySet()) { + if (now - entry.getValue() > PERIOD) { + msgId2timestamp.entrySet().remove(entry); + } + } + msgId2timestamp.clear(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + t.setDaemon(true); + t.start(); + } + + @Override + public boolean intercept(WxMpXmlMessage wxMessage, Map context, WxMpService wxMpService) { + Long now = System.currentTimeMillis(); + Long timestamp = msgId2timestamp.putIfAbsent(wxMessage.getMsgId(), now); + if (timestamp == null) { + return true; + } + if (timestamp.equals(now)) { + // 第一次接收到这个消息 + return true; + } + return false; + } + + public static void main(String[] args) { + WxMpDuplicateMessageInterceptor d = new WxMpDuplicateMessageInterceptor(); + Long endTime = System.currentTimeMillis() + 30 * 1000; + Random r = new Random(); + + while(System.currentTimeMillis() < endTime) { + WxMpXmlMessage m = new WxMpXmlMessage(); + m.setMsgId(r.nextLong() % 100); + d.intercept(m, null, null); + } + + } +} From 9fe73319d1457770d793ca48b5928819a2255dfe Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Tue, 20 Jan 2015 09:49:39 +0800 Subject: [PATCH 07/33] issue #66 --- .../common/util/WxMsgIdDuplicateChecker.java | 18 ++++ .../util/WxMsgIdInMemoryDuplicateChecker.java | 84 ++++++++++++++++++ .../WxMsgIdInMemoryDuplicateCheckerTest.java | 38 ++++++++ .../src/test/resources/testng.xml | 1 + .../weixin/cp/api/WxCpMessageRouter.java | 31 ++++++- .../api/WxMpDuplicateMessageInterceptor.java | 88 ------------------- .../weixin/mp/api/WxMpMessageRouter.java | 31 ++++++- 7 files changed, 199 insertions(+), 92 deletions(-) create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdDuplicateChecker.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java create mode 100644 weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateCheckerTest.java delete mode 100644 weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDuplicateMessageInterceptor.java diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdDuplicateChecker.java new file mode 100644 index 00000000..b3d320b1 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdDuplicateChecker.java @@ -0,0 +1,18 @@ +package me.chanjar.weixin.common.util; + +/** + *
+ * 消息重复检查器
+ * 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
+ * 
+ */ +public interface WxMsgIdDuplicateChecker { + + /** + * 检查消息ID是否重复 + * @param wxMsgId + * @return 如果是重复消息,返回true,否则返回false + */ + public boolean isDuplicate(Long wxMsgId); + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java new file mode 100644 index 00000000..a7bb70c6 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java @@ -0,0 +1,84 @@ +package me.chanjar.weixin.common.util; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + *
+ * 默认消息重复检查器
+ * 将每个消息id保存在内存里,每隔5秒清理已经过期的消息id,每个消息id的过期时间是15秒
+ * 
+ */ +public class WxMsgIdInMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { + + /** + * 一个消息ID在内存的过期时间:15秒 + */ + private final Long TIME_TO_LIVE; + + /** + * 每隔多少周期检查消息ID是否过期:5秒 + */ + private final Long CLEAR_PERIOD; + + private final ConcurrentHashMap msgId2Timestamp = new ConcurrentHashMap(); + + /** + * WxMsgIdInMemoryDuplicateChecker构造函数 + *
+   * 一个消息ID在内存的过期时间:15秒
+   * 每隔多少周期检查消息ID是否过期:5秒
+   * 
+ */ + public WxMsgIdInMemoryDuplicateChecker() { + this.TIME_TO_LIVE = 15 * 1000l; + this.CLEAR_PERIOD = 5 * 1000l; + this.start(); + } + + /** + * WxMsgIdInMemoryDuplicateChecker构造函数 + * @param timeToLive 一个消息ID在内存的过期时间:毫秒 + * @param clearPeriod 每隔多少周期检查消息ID是否过期:毫秒 + */ + public WxMsgIdInMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) { + this.TIME_TO_LIVE = timeToLive; + this.CLEAR_PERIOD = clearPeriod; + this.start(); + } + + private void start() { + Thread t = new Thread(new Runnable() { + @Override + public void run() { + try { + while (true) { + Thread.sleep(CLEAR_PERIOD); + Long now = System.currentTimeMillis(); + for (Map.Entry entry : msgId2Timestamp.entrySet()) { + if (now - entry.getValue() > TIME_TO_LIVE) { + msgId2Timestamp.entrySet().remove(entry); + } + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + t.setDaemon(true); + t.start(); + } + + @Override + public boolean isDuplicate(Long wxMsgId) { + Long timestamp = msgId2Timestamp.putIfAbsent(wxMsgId, System.currentTimeMillis()); + if (timestamp == null) { + // 第一次接收到这个消息 + return false; + } + return true; + } + + +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateCheckerTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateCheckerTest.java new file mode 100644 index 00000000..a35eba5c --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateCheckerTest.java @@ -0,0 +1,38 @@ +package me.chanjar.weixin.common.util; + +import org.testng.Assert; +import org.testng.annotations.Test; + +/** + * Created by qianjia on 15/1/20. + */ +@Test +public class WxMsgIdInMemoryDuplicateCheckerTest { + + public void test() throws InterruptedException { + Long[] msgIds = new Long[] { 1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l }; + WxMsgIdInMemoryDuplicateChecker checker = new WxMsgIdInMemoryDuplicateChecker(2000l, 1000l); + + // 第一次检查 + for (Long msgId : msgIds) { + boolean result = checker.isDuplicate(msgId); + Assert.assertFalse(result); + } + + // 过1秒再检查 + Thread.sleep(1000l); + for (Long msgId : msgIds) { + boolean result = checker.isDuplicate(msgId); + Assert.assertTrue(result); + } + + // 过1.5秒再检查 + Thread.sleep(1500l); + for (Long msgId : msgIds) { + boolean result = checker.isDuplicate(msgId); + Assert.assertFalse(result); + } + + } + +} diff --git a/weixin-java-common/src/test/resources/testng.xml b/weixin-java-common/src/test/resources/testng.xml index f0058b53..49d65248 100644 --- a/weixin-java-common/src/test/resources/testng.xml +++ b/weixin-java-common/src/test/resources/testng.xml @@ -7,6 +7,7 @@ +
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 5b4e1820..c88bc8a3 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,5 +1,7 @@ package me.chanjar.weixin.cp.api; +import me.chanjar.weixin.common.util.WxMsgIdDuplicateChecker; +import me.chanjar.weixin.common.util.WxMsgIdInMemoryDuplicateChecker; import me.chanjar.weixin.cp.bean.WxCpXmlMessage; import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; @@ -45,18 +47,38 @@ public class WxCpMessageRouter { private final List rules = new ArrayList(); - private final ExecutorService executorService; - private final WxCpService wxCpService; + private ExecutorService executorService; + + private WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker; + public WxCpMessageRouter(WxCpService wxCpService) { this.wxCpService = wxCpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); + this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); } public WxCpMessageRouter(WxCpService wxMpService, int threadPoolSize) { this.wxCpService = wxMpService; this.executorService = Executors.newFixedThreadPool(threadPoolSize); + this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); + } + + /** + * 设置自定义的ExecutorService + * @param executorService + */ + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + /** + * 设置自定义的WxMsgIdDuplicateChecker + * @param wxMsgIdDuplicateChecker + */ + public void setWxMsgIdDuplicateChecker(WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker) { + this.wxMsgIdDuplicateChecker = wxMsgIdDuplicateChecker; } /** @@ -72,6 +94,11 @@ public class WxCpMessageRouter { * @param wxMessage */ public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { + if (wxMsgIdDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { + // 如果是重复消息,那么就不做处理 + return null; + } + final List matchRules = new ArrayList(); // 收集匹配的规则 for (final Rule rule : rules) { diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDuplicateMessageInterceptor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDuplicateMessageInterceptor.java deleted file mode 100644 index 970bbba4..00000000 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpDuplicateMessageInterceptor.java +++ /dev/null @@ -1,88 +0,0 @@ -package me.chanjar.weixin.mp.api; - -import me.chanjar.weixin.mp.bean.WxMpXmlMessage; -import sun.applet.Main; - -import java.net.SocketTimeoutException; -import java.util.Map; -import java.util.Random; -import java.util.concurrent.ConcurrentHashMap; - -/** - *
- * 消息去重拦截器
- * 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次
- * 使用方法:
- * WxMpMessageRouter router = new WxMpMessageRouter();
- * router
- *   .rule()
- *       .interceptor(new WxMpDuplicateMessageInterceptor())
- *   .next()
- *   .rule()
- *       .msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT")
- *       .interceptor(interceptor, ...).handler(handler, ...)
- *   .end()
- *   .rule()
- *       // 另外一个匹配规则
- *   .end()
- * ;
- * 
- */ -public class WxMpDuplicateMessageInterceptor implements WxMpMessageInterceptor { - - private static final Long PERIOD = 15 * 1000l; - - private final ConcurrentHashMap msgId2timestamp; - - public WxMpDuplicateMessageInterceptor() { - this.msgId2timestamp = new ConcurrentHashMap(); - Thread t = new Thread(new Runnable() { - @Override - public void run() { - try { - while (true) { - Thread.sleep(PERIOD); - Long now = System.currentTimeMillis(); - for (Map.Entry entry : msgId2timestamp.entrySet()) { - if (now - entry.getValue() > PERIOD) { - msgId2timestamp.entrySet().remove(entry); - } - } - msgId2timestamp.clear(); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }); - t.setDaemon(true); - t.start(); - } - - @Override - public boolean intercept(WxMpXmlMessage wxMessage, Map context, WxMpService wxMpService) { - Long now = System.currentTimeMillis(); - Long timestamp = msgId2timestamp.putIfAbsent(wxMessage.getMsgId(), now); - if (timestamp == null) { - return true; - } - if (timestamp.equals(now)) { - // 第一次接收到这个消息 - return true; - } - return false; - } - - public static void main(String[] args) { - WxMpDuplicateMessageInterceptor d = new WxMpDuplicateMessageInterceptor(); - Long endTime = System.currentTimeMillis() + 30 * 1000; - Random r = new Random(); - - while(System.currentTimeMillis() < endTime) { - WxMpXmlMessage m = new WxMpXmlMessage(); - m.setMsgId(r.nextLong() % 100); - d.intercept(m, null, null); - } - - } -} 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..7fcc3c96 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,5 +1,7 @@ package me.chanjar.weixin.mp.api; +import me.chanjar.weixin.common.util.WxMsgIdDuplicateChecker; +import me.chanjar.weixin.common.util.WxMsgIdInMemoryDuplicateChecker; import me.chanjar.weixin.mp.bean.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; @@ -45,18 +47,38 @@ public class WxMpMessageRouter { private final List rules = new ArrayList(); - private final ExecutorService executorService; - private final WxMpService wxMpService; + private ExecutorService executorService; + + private WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker; + public WxMpMessageRouter(WxMpService wxMpService) { this.wxMpService = wxMpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); + this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); } public WxMpMessageRouter(WxMpService wxMpService, int threadPoolSize) { this.wxMpService = wxMpService; this.executorService = Executors.newFixedThreadPool(threadPoolSize); + this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); + } + + /** + * 设置自定义的ExecutorService + * @param executorService + */ + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + /** + * 设置自定义的WxMsgIdDuplicateChecker + * @param wxMsgIdDuplicateChecker + */ + public void setWxMsgIdDuplicateChecker(WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker) { + this.wxMsgIdDuplicateChecker = wxMsgIdDuplicateChecker; } /** @@ -72,6 +94,11 @@ public class WxMpMessageRouter { * @param wxMessage */ public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) { + if (wxMsgIdDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { + // 如果是重复消息,那么就不做处理 + return null; + } + final List matchRules = new ArrayList(); // 收集匹配的规则 for (final Rule rule : rules) { From f00c5496c8a83109f3da7a32b90141d2f6c0e6e4 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Tue, 20 Jan 2015 09:53:49 +0800 Subject: [PATCH 08/33] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E5=8E=BB=E6=8E=89=E9=9A=90=E7=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../weixin/cp/api/WxCpUserAPITest.java | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) 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 93e226a5..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 @@ -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"); } } From cef8ac01e89307bd37784bae46836785bfc43435 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Tue, 20 Jan 2015 12:01:33 +0800 Subject: [PATCH 09/33] issue #74; git commit -m issue --- .../weixin/common/util/crypto/SHA1.java | 34 +++++++++++++++---- .../weixin/mp/api/WxMpConfigStorage.java | 24 +++++++++++-- .../mp/api/WxMpInMemoryConfigStorage.java | 24 +++++++++++-- .../me/chanjar/weixin/mp/api/WxMpService.java | 31 +++++++++++++++-- .../weixin/mp/api/WxMpServiceImpl.java | 34 ++++++++++++++++++- 5 files changed, 133 insertions(+), 14 deletions(-) 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-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..68d42785 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,10 +9,19 @@ import me.chanjar.weixin.common.bean.WxAccessToken; */ public interface WxMpConfigStorage { + /** + * 应该是线程安全的 + * @param accessToken + */ public void updateAccessToken(WxAccessToken accessToken); - + + /** + * 应该是线程安全的 + * @param accessToken + * @param expiresIn + */ public void updateAccessToken(String accessToken, int expiresIn); - + public String getAccessToken(); public String getAppId(); @@ -35,4 +44,15 @@ public interface WxMpConfigStorage { public String getHttp_proxy_password(); + + public String getJsapiTicket(); + + public boolean isJsapiTokenExpired(); + + /** + * 应该是线程安全的 + * @param jsapiTicket + */ + public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); + } 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..d278d1b7 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,6 +9,8 @@ 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; @@ -23,11 +25,14 @@ 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 synchronized void updateAccessToken(WxAccessToken accessToken) { updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); } - public void updateAccessToken(String accessToken, int expiresIn) { + public synchronized void updateAccessToken(String accessToken, int expiresIn) { this.accessToken = accessToken; this.expiresIn = expiresIn; } @@ -121,6 +126,21 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { this.http_proxy_password = http_proxy_password; } + + public String getJsapiTicket() { + return jsapiTicket; + } + + public boolean isJsapiTokenExpired() { + return System.currentTimeMillis() > this.jsapiTicketExpiresTime; + } + + public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { + this.jsapiTicket = jsapiTicket; + // 预留200秒的时间 + this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; + } + @Override public String toString() { return "WxMpInMemoryConfigStorage{" + 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 c4dc3e54..e8a56787 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 @@ -43,17 +43,42 @@ public interface WxMpService { * @throws me.chanjar.weixin.common.exception.WxErrorException */ public void accessTokenRefresh() 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
+   * 
+ * @return + * @throws WxErrorException + */ + public String getJsapiTicket() 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(String 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} 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..636e8f62 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 @@ -36,6 +36,7 @@ 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; @@ -106,7 +107,38 @@ public class WxMpServiceImpl implements WxMpService { // 刷新完毕了,就没他什么事儿了 } } - + + public String getJsapiTicket() throws WxErrorException { + if (wxMpConfigStorage.isJsapiTokenExpired()) { + synchronized (wxMpConfigStorage) { + if (wxMpConfigStorage.isJsapiTokenExpired()) { + 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(String timestamp, String noncestr, String url) throws WxErrorException { + String jsapiTicket = getJsapiTicket(); + try { + return SHA1.genWithAmple( + "jsapi_ticket=" + jsapiTicket, + "timestamp=" + timestamp, + "noncestr=" + noncestr, + "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()); From 1bca854b52e416be66916d4e85dec998bd734298 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Tue, 20 Jan 2015 13:52:40 +0800 Subject: [PATCH 10/33] =?UTF-8?q?refactor:=20mp=20=E8=B0=83=E6=95=B4access?= =?UTF-8?q?=20token=E5=88=B7=E6=96=B0=E7=AD=96=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/test/resources/test-config.sample.xml | 2 +- .../weixin/mp/api/WxMpConfigStorage.java | 44 +++++---- .../mp/api/WxMpInMemoryConfigStorage.java | 60 +++++++----- .../me/chanjar/weixin/mp/api/WxMpService.java | 3 +- .../weixin/mp/api/WxMpServiceImpl.java | 92 +++++++++---------- .../mp/bean/result/WxMpOAuth2AccessToken.java | 2 +- .../chanjar/weixin/mp/api/ApiTestModule.java | 2 +- .../weixin/mp/api/WxMpBaseAPITest.java | 2 +- .../demo/WxMpDemoInMemoryConfigStorage.java | 2 +- .../src/test/resources/test-config.sample.xml | 2 +- 10 files changed, 115 insertions(+), 96 deletions(-) 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-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 68d42785..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,6 +9,15 @@ import me.chanjar.weixin.common.bean.WxAccessToken; */ public interface WxMpConfigStorage { + public String getAccessToken(); + + public boolean isAccessTokenExpired(); + + /** + * 强制将access token过期掉 + */ + public void expireAccessToken(); + /** * 应该是线程安全的 * @param accessToken @@ -22,17 +31,30 @@ public interface WxMpConfigStorage { */ 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(); @@ -42,17 +64,7 @@ public interface WxMpConfigStorage { public String getHttp_proxy_username(); - public String getHttp_proxy_password(); - - - public String getJsapiTicket(); - - public boolean isJsapiTokenExpired(); - /** - * 应该是线程安全的 - * @param jsapiTicket - */ - public void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); + 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 d278d1b7..c694dde8 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 @@ -16,7 +16,7 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { protected String token; protected String accessToken; protected String aesKey; - protected int expiresIn; + protected long expiresTime; protected String oauth2redirectUri; @@ -28,17 +28,43 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { 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 synchronized 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() { @@ -53,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) { @@ -81,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 @@ -127,20 +153,6 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { } - public String getJsapiTicket() { - return jsapiTicket; - } - - public boolean isJsapiTokenExpired() { - return System.currentTimeMillis() > this.jsapiTicketExpiresTime; - } - - public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { - this.jsapiTicket = jsapiTicket; - // 预留200秒的时间 - this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; - } - @Override public String toString() { return "WxMpInMemoryConfigStorage{" + @@ -149,7 +161,7 @@ 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 + '\'' + 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 e8a56787..cef912ee 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 @@ -40,9 +40,10 @@ public interface WxMpService { * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token *
+ * @return * @throws me.chanjar.weixin.common.exception.WxErrorException */ - public void accessTokenRefresh() throws WxErrorException; + public String getAccessToken() throws WxErrorException; /** *
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 636e8f62..0bb1c550 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
@@ -44,12 +44,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
 public class WxMpServiceImpl implements WxMpService {
 
   /**
-   * 全局的是否正在刷新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();
+
+  /**
+   * 全局的是否正在刷新jsapi_ticket的锁
+   */
+  protected static final Object GLOBAL_JSAPI_TICKET_REFRESH_LOCK = new Object();
+
   protected WxMpConfigStorage wxMpConfigStorage;
   
   protected final ThreadLocal retryTimes = new ThreadLocal();
@@ -66,52 +69,45 @@ public class WxMpServiceImpl implements WxMpService {
     }
   }
   
-  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 {
+    if (wxMpConfigStorage.isAccessTokenExpired()) {
+      synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) {
+        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 {
-    if (wxMpConfigStorage.isJsapiTokenExpired()) {
-      synchronized (wxMpConfigStorage) {
-        if (wxMpConfigStorage.isJsapiTokenExpired()) {
+    if (wxMpConfigStorage.isJsapiTicketExpired()) {
+      synchronized (GLOBAL_JSAPI_TICKET_REFRESH_LOCK) {
+        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)));
@@ -440,10 +436,7 @@ 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();
+    String accessToken = getAccessToken();
     
     String uriWithAccessToken = uri;
     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
@@ -458,7 +451,8 @@ 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);
       }
       /**
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java
index c2a28546..d2a6ef8e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java
@@ -65,7 +65,7 @@ public class WxMpOAuth2AccessToken {
   public String toString() {
     return "WxMpOAuth2AccessToken{" +
         "accessToken='" + accessToken + '\'' +
-        ", expiresIn=" + expiresIn +
+        ", expiresTime=" + expiresIn +
         ", refreshToken='" + refreshToken + '\'' +
         ", openId='" + openId + '\'' +
         ", scope='" + scope + '\'' +
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 ad006acd..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
@@ -42,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/WxMpBaseAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
index ef9590e6..e773f117 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
@@ -22,7 +22,7 @@ public class WxMpBaseAPITest {
   public void testRefreshAccessToken() throws WxErrorException {
     WxMpConfigStorage configStorage = wxService.wxMpConfigStorage;
     String before = configStorage.getAccessToken();
-    wxService.accessTokenRefresh();
+    wxService.getAccessToken();
 
     String after = configStorage.getAccessToken();
 
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 22d87dd4..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
@@ -16,7 +16,7 @@ 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 + "]";
   }
 
 
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
     网页授权获取用户信息回调地址
 

From 1daf3a63a1051d4d5d81bc792e57a01a1ae08478 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:13:11 +0800
Subject: [PATCH 11/33] =?UTF-8?q?refactor:=20CP=20=E8=B0=83=E6=95=B4access?=
 =?UTF-8?q?=20token=E5=88=B7=E6=96=B0=E7=AD=96=E7=95=A5?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../weixin/cp/api/WxCpConfigStorage.java      | 18 +++--
 .../cp/api/WxCpInMemoryConfigStorage.java     | 32 +++++---
 .../me/chanjar/weixin/cp/api/WxCpService.java |  3 +-
 .../weixin/cp/api/WxCpServiceImpl.java        | 76 ++++++++-----------
 .../weixin/cp/api/WxCpBaseAPITest.java        |  2 +-
 .../demo/WxCpDemoInMemoryConfigStorage.java   |  2 +-
 6 files changed, 69 insertions(+), 64 deletions(-)

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..dd4882e2 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 String getAccessToken() {
+    return this.accessToken;
+  }
+
+  public boolean isAccessTokenExpired() {
+    return System.currentTimeMillis() > this.expiresTime;
+  }
+
+  public void expireAccessToken() {
+    this.expiresTime = 0;
+  }
+
   public void updateAccessToken(WxAccessToken accessToken) {
     updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
   }
   
-  public void updateAccessToken(String accessToken, int expiresIn) {
+  public 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/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
index 808eb6ce..c81bf715 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
@@ -52,9 +52,10 @@ public interface WxCpService {
    * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=获取access_token
    * 
* + * @return * @throws me.chanjar.weixin.common.exception.WxErrorException */ - public void accessTokenRefresh() throws WxErrorException; + public String getAccessToken() throws WxErrorException; /** *
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..c38ea65f 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
@@ -46,11 +46,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
 public class WxCpServiceImpl implements WxCpService {
 
   /**
-   * 全局的是否正在刷新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;
 
@@ -73,45 +71,37 @@ 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 {
+    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 +359,7 @@ 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();
+    String accessToken = getAccessToken();
 
     String uriWithAccessToken = uri;
     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
@@ -387,7 +374,8 @@ public class WxCpServiceImpl implements WxCpService {
        * 42001 access_token超时
        */
       if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
-        accessTokenRefresh();
+        // 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
+        wxCpConfigStorage.expireAccessToken();
         return execute(executor, uri, data);
       }
       /**
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 6ed438a9..606cca9b 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
@@ -22,7 +22,7 @@ public class WxCpBaseAPITest {
   public void testRefreshAccessToken() throws WxErrorException {
     WxCpConfigStorage configStorage = wxService.wxCpConfigStorage;
     String before = configStorage.getAccessToken();
-    wxService.accessTokenRefresh();
+    wxService.getAccessToken();
 
     String after = configStorage.getAccessToken();
 
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 1d9103a5..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
@@ -16,7 +16,7 @@ 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 + "]";
   }
 
 

From ae99bcdb639ee73c9816d66059f4971236c8b376 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:15:24 +0800
Subject: [PATCH 12/33] issue #74

---
 .../src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 0bb1c550..18d3bcdd 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
@@ -126,8 +126,8 @@ public class WxMpServiceImpl implements WxMpService {
     try {
       return SHA1.genWithAmple(
           "jsapi_ticket=" + jsapiTicket,
-          "timestamp=" + timestamp,
           "noncestr=" + noncestr,
+          "timestamp=" + timestamp,
           "url=" + url
       );
     } catch (NoSuchAlgorithmException e) {

From 5c7448f212d556e677454f26c42dad99d546a16c Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:30:44 +0800
Subject: [PATCH 13/33] =?UTF-8?q?=E9=92=88=E5=AF=B9AccessToken=EF=BC=8Cjsa?=
 =?UTF-8?q?pi=20ticket=E6=B7=BB=E5=8A=A0=E5=BC=BA=E5=88=B6=E5=88=B7?=
 =?UTF-8?q?=E6=96=B0=E7=9A=84=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../java/me/chanjar/weixin/cp/api/WxCpService.java |  4 ++--
 .../me/chanjar/weixin/cp/api/WxCpServiceImpl.java  |  7 +++++--
 .../java/me/chanjar/weixin/mp/api/WxMpService.java |  6 ++++--
 .../me/chanjar/weixin/mp/api/WxMpServiceImpl.java  | 14 ++++++++++----
 4 files changed, 21 insertions(+), 10 deletions(-)

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 c81bf715..36c78f0c 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
@@ -51,11 +51,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 String getAccessToken() throws WxErrorException; + public String getAccessToken(boolean forceRefresh) throws WxErrorException; /** *
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 c38ea65f..fb7255c6 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
@@ -71,7 +71,10 @@ public class WxCpServiceImpl implements WxCpService {
     execute(new SimpleGetRequestExecutor(), url, null);
   }
 
-  public String getAccessToken() throws WxErrorException {
+  public String getAccessToken(boolean forceRefresh) throws WxErrorException {
+    if (forceRefresh) {
+      wxCpConfigStorage.expireAccessToken();
+    }
     if (wxCpConfigStorage.isAccessTokenExpired()) {
       synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) {
         if (wxCpConfigStorage.isAccessTokenExpired()) {
@@ -359,7 +362,7 @@ public class WxCpServiceImpl implements WxCpService {
    * @throws WxErrorException
    */
   public  T execute(RequestExecutor executor, String uri, E data) throws WxErrorException {
-    String accessToken = getAccessToken();
+    String accessToken = getAccessToken(false);
 
     String uriWithAccessToken = uri;
     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;
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 cef912ee..5500376c 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
@@ -40,10 +40,11 @@ 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 String getAccessToken() throws WxErrorException; + public String getAccessToken(boolean forceRefresh) throws WxErrorException; /** *
@@ -52,10 +53,11 @@ public interface WxMpService {
    *
    * 详情请见: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() throws WxErrorException; + public String getJsapiTicket(boolean forceRefresh) throws WxErrorException; /** *
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 18d3bcdd..f1260f64 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
@@ -69,7 +69,10 @@ public class WxMpServiceImpl implements WxMpService {
     }
   }
   
-  public String getAccessToken() throws WxErrorException {
+  public String getAccessToken(boolean forceRefresh) throws WxErrorException {
+    if (forceRefresh) {
+      wxMpConfigStorage.expireAccessToken();
+    }
     if (wxMpConfigStorage.isAccessTokenExpired()) {
       synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) {
         if (wxMpConfigStorage.isAccessTokenExpired()) {
@@ -104,7 +107,10 @@ public class WxMpServiceImpl implements WxMpService {
   }
 
 
-  public String getJsapiTicket() throws WxErrorException {
+  public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
+    if (forceRefresh) {
+      wxMpConfigStorage.expireJsapiTicket();
+    }
     if (wxMpConfigStorage.isJsapiTicketExpired()) {
       synchronized (GLOBAL_JSAPI_TICKET_REFRESH_LOCK) {
         if (wxMpConfigStorage.isJsapiTicketExpired()) {
@@ -122,7 +128,7 @@ public class WxMpServiceImpl implements WxMpService {
   }
 
   public String createJsapiSignature(String timestamp, String noncestr, String url) throws WxErrorException {
-    String jsapiTicket = getJsapiTicket();
+    String jsapiTicket = getJsapiTicket(false);
     try {
       return SHA1.genWithAmple(
           "jsapi_ticket=" + jsapiTicket,
@@ -436,7 +442,7 @@ public class WxMpServiceImpl implements WxMpService {
    * @throws WxErrorException
    */
   public  T execute(RequestExecutor executor, String uri, E data) throws WxErrorException {
-    String accessToken = getAccessToken();
+    String accessToken = getAccessToken(false);
     
     String uriWithAccessToken = uri;
     uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;

From d0825ca3969a91182713b3368f483ac5744069c7 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:31:52 +0800
Subject: [PATCH 14/33] update

---
 .../src/test/java/me/chanjar/weixin/cp/api/WxCpBaseAPITest.java | 2 +-
 .../src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

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 606cca9b..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
@@ -22,7 +22,7 @@ public class WxCpBaseAPITest {
   public void testRefreshAccessToken() throws WxErrorException {
     WxCpConfigStorage configStorage = wxService.wxCpConfigStorage;
     String before = configStorage.getAccessToken();
-    wxService.getAccessToken();
+    wxService.getAccessToken(false);
 
     String after = configStorage.getAccessToken();
 
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
index e773f117..cf01bfbc 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
@@ -22,7 +22,7 @@ public class WxMpBaseAPITest {
   public void testRefreshAccessToken() throws WxErrorException {
     WxMpConfigStorage configStorage = wxService.wxMpConfigStorage;
     String before = configStorage.getAccessToken();
-    wxService.getAccessToken();
+    wxService.getAccessToken(false);
 
     String after = configStorage.getAccessToken();
 

From 4f292da60cdd8114b360b81707f3360e58e09ac9 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:52:52 +0800
Subject: [PATCH 15/33] =?UTF-8?q?issue=20#74=20=E6=B7=BB=E5=8A=A0=E6=B5=8B?=
 =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../me/chanjar/weixin/mp/api/WxMpService.java |  2 +-
 .../weixin/mp/api/WxMpServiceImpl.java        |  2 +-
 .../weixin/mp/api/WxMpBaseAPITest.java        |  1 -
 .../chanjar/weixin/mp/api/WxMpJsAPITest.java  | 48 +++++++++++++++++++
 weixin-java-mp/src/test/resources/testng.xml  |  1 +
 5 files changed, 51 insertions(+), 3 deletions(-)
 create mode 100644 weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpJsAPITest.java

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 5500376c..2f0e6844 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
@@ -70,7 +70,7 @@ public interface WxMpService {
    * @param url       url
    * @return
    */
-  public String createJsapiSignature(String timestamp, String noncestr, String url) throws WxErrorException;
+  public String createJsapiSignature(long timestamp, String noncestr, String url) throws WxErrorException;
 
   /**
    * 
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 f1260f64..7b256304 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
@@ -127,7 +127,7 @@ public class WxMpServiceImpl implements WxMpService {
     return wxMpConfigStorage.getJsapiTicket();
   }
 
-  public String createJsapiSignature(String timestamp, String noncestr, String url) throws WxErrorException {
+  public String createJsapiSignature(long timestamp, String noncestr, String url) throws WxErrorException {
     String jsapiTicket = getJsapiTicket(false);
     try {
       return SHA1.genWithAmple(
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
index cf01bfbc..c4210b57 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBaseAPITest.java
@@ -25,7 +25,6 @@ public class WxMpBaseAPITest {
     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/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/resources/testng.xml b/weixin-java-mp/src/test/resources/testng.xml
index 17308c78..333400e4 100644
--- a/weixin-java-mp/src/test/resources/testng.xml
+++ b/weixin-java-mp/src/test/resources/testng.xml
@@ -13,6 +13,7 @@
 			
 			
 			
+      
 		
 	
 

From 87a2720bafcfc034919d52364cf6c4920dc75253 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 14:58:50 +0800
Subject: [PATCH 16/33] modify README.md

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 138e4670..5e3f871a 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,7 +29,7 @@ weixin-java-tools
 
   me.chanjar
   weixin-java-cp
-  1.0.6
+  1.0.7
 
 ```
 

From b613cf547949b6cc8655be0a78f139d3fca61eb7 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 15:02:33 +0800
Subject: [PATCH 17/33] modify README.md

---
 README.md | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/README.md b/README.md
index 5e3f871a..abc1a16e 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,20 @@ weixin-java-tools
 
 ```
 
+## 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``。需要做的是:

From 01cee5a3d9441115f2160f4490def8773b47a550 Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Tue, 20 Jan 2015 15:14:14 +0800
Subject: [PATCH 18/33] refactor: modify method

---
 .../me/chanjar/weixin/cp/api/WxCpService.java  |  8 ++++++++
 .../chanjar/weixin/cp/api/WxCpServiceImpl.java |  4 ++++
 .../me/chanjar/weixin/mp/api/WxMpService.java  | 18 +++++++++++++++++-
 .../chanjar/weixin/mp/api/WxMpServiceImpl.java |  9 ++++++++-
 4 files changed, 37 insertions(+), 2 deletions(-)

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 36c78f0c..2c25614a 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
@@ -43,6 +43,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,本方法线程安全
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 fb7255c6..ac13edaa 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
@@ -71,6 +71,10 @@ public class WxCpServiceImpl implements WxCpService {
     execute(new SimpleGetRequestExecutor(), url, null);
   }
 
+  public String getAccessToken() throws WxErrorException {
+    return getAccessToken(false);
+  }
+
   public String getAccessToken(boolean forceRefresh) throws WxErrorException {
     if (forceRefresh) {
       wxCpConfigStorage.expireAccessToken();
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 2f0e6844..7b194ccd 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
@@ -28,7 +28,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,本方法线程安全
@@ -46,6 +54,14 @@ public interface WxMpService {
    */
   public String getAccessToken(boolean forceRefresh) throws WxErrorException;
 
+  /**
+   * 获得jsapi_ticket,不强制刷新jsapi_ticket
+   * @see #getJsapiTicket(boolean)
+   * @return
+   * @throws WxErrorException
+   */
+  public String getJsapiTicket() throws WxErrorException;
+
   /**
    * 
    * 获得jsapi_ticket
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 7b256304..ac007444 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
@@ -68,7 +68,11 @@ public class WxMpServiceImpl implements WxMpService {
       return false;
     }
   }
-  
+
+  public String getAccessToken() throws WxErrorException {
+    return getAccessToken(false);
+  }
+
   public String getAccessToken(boolean forceRefresh) throws WxErrorException {
     if (forceRefresh) {
       wxMpConfigStorage.expireAccessToken();
@@ -106,6 +110,9 @@ public class WxMpServiceImpl implements WxMpService {
     return wxMpConfigStorage.getAccessToken();
   }
 
+  public String getJsapiTicket() throws WxErrorException {
+    return getJsapiTicket(false);
+  }
 
   public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
     if (forceRefresh) {

From 8c80ca25b65218feff2cf07720c101233bef44ed Mon Sep 17 00:00:00 2001
From: Daniel Qian 
Date: Wed, 21 Jan 2015 09:28:25 +0800
Subject: [PATCH 19/33] modify

---
 .../util/WxMsgIdInMemoryDuplicateChecker.java    | 16 ++++++++--------
 .../chanjar/weixin/mp/api/WxMpServiceImpl.java   |  9 ++++-----
 2 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java
index a7bb70c6..0c8eee91 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java
@@ -14,12 +14,12 @@ public class WxMsgIdInMemoryDuplicateChecker implements WxMsgIdDuplicateChecker
   /**
    * 一个消息ID在内存的过期时间:15秒
    */
-  private final Long TIME_TO_LIVE;
+  private final Long timeToLive;
 
   /**
    * 每隔多少周期检查消息ID是否过期:5秒
    */
-  private final Long CLEAR_PERIOD;
+  private final Long clearPeriod;
 
   private final ConcurrentHashMap msgId2Timestamp = new ConcurrentHashMap();
 
@@ -31,8 +31,8 @@ public class WxMsgIdInMemoryDuplicateChecker implements WxMsgIdDuplicateChecker
    * 
*/ public WxMsgIdInMemoryDuplicateChecker() { - this.TIME_TO_LIVE = 15 * 1000l; - this.CLEAR_PERIOD = 5 * 1000l; + this.timeToLive = 15 * 1000l; + this.clearPeriod = 5 * 1000l; this.start(); } @@ -42,8 +42,8 @@ public class WxMsgIdInMemoryDuplicateChecker implements WxMsgIdDuplicateChecker * @param clearPeriod 每隔多少周期检查消息ID是否过期:毫秒 */ public WxMsgIdInMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) { - this.TIME_TO_LIVE = timeToLive; - this.CLEAR_PERIOD = clearPeriod; + this.timeToLive = timeToLive; + this.clearPeriod = clearPeriod; this.start(); } @@ -53,10 +53,10 @@ public class WxMsgIdInMemoryDuplicateChecker implements WxMsgIdDuplicateChecker public void run() { try { while (true) { - Thread.sleep(CLEAR_PERIOD); + Thread.sleep(clearPeriod); Long now = System.currentTimeMillis(); for (Map.Entry entry : msgId2Timestamp.entrySet()) { - if (now - entry.getValue() > TIME_TO_LIVE) { + if (now - entry.getValue() > timeToLive) { msgId2Timestamp.entrySet().remove(entry); } } 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 ac007444..ba256865 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 @@ -39,19 +39,18 @@ 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 { /** * 全局的是否正在刷新access token的锁 */ - protected static final Object GLOBAL_ACCESS_TOKEN_REFRESH_LOCK = new Object(); + protected final Object globalAccessTokenRefreshLock = new Object(); /** * 全局的是否正在刷新jsapi_ticket的锁 */ - protected static final Object GLOBAL_JSAPI_TICKET_REFRESH_LOCK = new Object(); + protected final Object globalJsapiTicketRefreshLock = new Object(); protected WxMpConfigStorage wxMpConfigStorage; @@ -78,7 +77,7 @@ public class WxMpServiceImpl implements WxMpService { wxMpConfigStorage.expireAccessToken(); } if (wxMpConfigStorage.isAccessTokenExpired()) { - synchronized (GLOBAL_ACCESS_TOKEN_REFRESH_LOCK) { + synchronized (globalAccessTokenRefreshLock) { if (wxMpConfigStorage.isAccessTokenExpired()) { String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" + "&appid=" + wxMpConfigStorage.getAppId() @@ -119,7 +118,7 @@ public class WxMpServiceImpl implements WxMpService { wxMpConfigStorage.expireJsapiTicket(); } if (wxMpConfigStorage.isJsapiTicketExpired()) { - synchronized (GLOBAL_JSAPI_TICKET_REFRESH_LOCK) { + 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); From 1635024146d320db0e476032c7449be98770f501 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Wed, 21 Jan 2015 12:02:52 +0800 Subject: [PATCH 20/33] issue #69 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 抄了apache tomcat的源代码里关于Session, StandardSession, Manager, StandardManager中关于Session管理部分的代码。 --- .../weixin/common/session/Constants.java | 31 ++ .../common/session/InternalSession.java | 73 +++++ .../common/session/InternalSessionFacade.java | 43 +++ .../session/InternalSessionManager.java | 66 ++++ .../common/session/LocalStrings.properties | 80 +++++ .../weixin/common/session/SessionImpl.java | 294 ++++++++++++++++++ .../common/session/SessionManagerImpl.java | 252 +++++++++++++++ .../TooManyActiveSessionsException.java | 57 ++++ .../weixin/common/session/WxSession.java | 20 ++ .../common/session/WxSessionManager.java | 11 + .../weixin/common/util/res/StringManager.java | 251 +++++++++++++++ 11 files changed, 1178 insertions(+) create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/session/Constants.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/session/LocalStrings.properties create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java create mode 100644 weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java 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..b13f5c86 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSession.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.common.session; + +/** + * Created by qianjia on 15/1/21. + */ +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(); + + /** + * Set the isNew flag for this session. + * + * @param isNew The new value for the isNew flag + */ + void setNew(boolean isNew); + + /** + * 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/InternalSessionFacade.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java new file mode 100644 index 00000000..242b4c6f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.common.session; + +import java.util.Enumeration; + +/** + * Created by qianjia on 15/1/21. + */ +public class InternalSessionFacade implements WxSession { + + /** + * Wrapped session object. + */ + private WxSession session = null; + + public InternalSessionFacade(WxSession session) { + session = 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/InternalSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java new file mode 100644 index 00000000..e29fbd1d --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionManager.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.common.session; + +/** + * Created by qianjia on 15/1/21. + */ +public interface InternalSessionManager { + + /** + * 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(); + +} 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/SessionImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java new file mode 100644 index 00000000..eabe399e --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java @@ -0,0 +1,294 @@ +package me.chanjar.weixin.common.session; + +import me.chanjar.weixin.common.util.res.StringManager; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by qianjia on 15/1/21. + */ +public class SessionImpl 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; + + /** + * Flag indicating whether this session is new or not. + */ + protected boolean isNew = 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 last accessed time for this Session. + */ + protected volatile long lastAccessedTime = 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 InternalSessionFacade facade = null; + + + public SessionImpl(InternalSessionManager manager) { + this.manager = manager; + } + + + @Override + public WxSession getSession() { + + if (facade == null){ + facade = new InternalSessionFacade(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() { + return 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; + + // 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(); + + } + + + @Override + public void setNew(boolean isNew) { + + this.isNew = isNew; + + } + + + @Override + public void setCreationTime(long time) { + + this.creationTime = time; + this.lastAccessedTime = time; + this.thisAccessedTime = time; + + } + + @Override + public void setMaxInactiveInterval(int interval) { + int oldMaxInactiveInterval = this.maxInactiveInterval; + 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); + + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java new file mode 100644 index 00000000..464296cc --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java @@ -0,0 +1,252 @@ +package me.chanjar.weixin.common.session; + +import me.chanjar.weixin.common.util.res.StringManager; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class SessionManagerImpl implements WxSessionManager, InternalSessionManager { + + 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; + + @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()); + } + } + + + /** + * 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 + */ + protected 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.setNew(true); + 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 SessionImpl(this); + } + + + @Override + public void add(InternalSession session) { + + 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 " + getName() + " at " + timeNow + " sessioncount " + 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 " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere); + processingTime += ( timeEnd - timeNow ); + + } + + + /** + * Return the descriptive short name of this Manager implementation. + */ + public String getName() { + + return (name); + + } + +} 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..70d81aff --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSession.java @@ -0,0 +1,20 @@ +package me.chanjar.weixin.common.session; + +import java.util.Enumeration; + +/** + * Created by qianjia on 15/1/21. + */ +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..e2b1dd79 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/WxSessionManager.java @@ -0,0 +1,11 @@ +package me.chanjar.weixin.common.session; + +public interface WxSessionManager { + + + public WxSession getSession(String sessionId); + + public WxSession getSession(String sessionId, boolean create); + + +} 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); + } +} From e53c921d176f017fcc9ea846327dfed46d9f2692 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Wed, 21 Jan 2015 14:08:34 +0800 Subject: [PATCH 21/33] =?UTF-8?q?fix:=20WxMpServiceImpl=E4=B8=AD=E7=94=A8T?= =?UTF-8?q?hreadLocal=E8=AE=B0=E5=BD=95=E9=87=8D=E8=AF=95=E6=AC=A1?= =?UTF-8?q?=E6=95=B0=EF=BC=8C=E8=BF=99=E4=B8=AA=E6=98=AF=E6=9C=89=E9=97=AE?= =?UTF-8?q?=E9=A2=98=E7=9A=84=EF=BC=8C=E5=9B=A0=E4=B8=BA=E5=9C=A8=E5=A4=9A?= =?UTF-8?q?=E7=BA=BF=E7=A8=8B=EF=BC=88=E7=BA=BF=E7=A8=8B=E6=B1=A0=EF=BC=89?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=E4=B8=8BThreadLocal=E6=98=AF=E4=B8=8D?= =?UTF-8?q?=E4=BC=9A=E8=A2=AB=E6=B8=850=E7=9A=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../me/chanjar/weixin/cp/api/WxCpService.java | 18 +++++ .../weixin/cp/api/WxCpServiceImpl.java | 68 ++++++++++++------- .../weixin/cp/api/WxCpBusyRetryTest.java | 66 ++++++++++++++++++ weixin-java-cp/src/test/resources/testng.xml | 47 ++++++------- .../me/chanjar/weixin/mp/api/WxMpService.java | 18 +++++ .../weixin/mp/api/WxMpServiceImpl.java | 68 ++++++++++++------- .../weixin/mp/api/WxMpBusyRetryTest.java | 66 ++++++++++++++++++ weixin-java-mp/src/test/resources/testng.xml | 1 + 8 files changed, 280 insertions(+), 72 deletions(-) create mode 100644 weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java create mode 100644 weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBusyRetryTest.java 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 2c25614a..c3b8b439 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 @@ -353,4 +353,22 @@ 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); } 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 ac13edaa..70ac819a 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 @@ -52,12 +52,14 @@ public class WxCpServiceImpl implements WxCpService { protected WxCpConfigStorage wxCpConfigStorage; - protected final ThreadLocal retryTimes = new ThreadLocal(); - protected CloseableHttpClient httpClient; protected HttpHost httpProxy; + private int retrySleepMillis = 1000; + + private int maxRetryTimes = 5; + public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { try { return SHA1.gen(wxCpConfigStorage.getToken(), timestamp, nonce, data).equals(msgSignature); @@ -366,6 +368,33 @@ public class WxCpServiceImpl implements WxCpService { * @throws WxErrorException */ public T execute(RequestExecutor executor, String uri, E data) throws WxErrorException { + 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 { + System.out.println("微信系统繁忙," + sleepMillis + "ms后重试(第" + (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; @@ -381,31 +410,10 @@ public class WxCpServiceImpl implements WxCpService { * 42001 access_token超时 */ if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) { - // 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token + // 强制设置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); } @@ -416,7 +424,6 @@ public class WxCpServiceImpl implements WxCpService { throw new RuntimeException(e); } } - protected CloseableHttpClient getHttpclient() { return httpClient; } @@ -451,4 +458,15 @@ public class WxCpServiceImpl implements WxCpService { } } + @Override + public void setRetrySleepMillis(int retrySleepMillis) { + this.retrySleepMillis = retrySleepMillis; + } + + + @Override + public void setMaxRetryTimes(int maxRetryTimes) { + this.maxRetryTimes = maxRetryTimes; + } + } 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/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/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 7b194ccd..3ad10bdc 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 @@ -461,4 +461,22 @@ public interface WxMpService { * @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 ba256865..744f8bf2 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 @@ -54,12 +54,14 @@ public class WxMpServiceImpl implements WxMpService { protected WxMpConfigStorage wxMpConfigStorage; - protected final ThreadLocal retryTimes = new ThreadLocal(); - protected CloseableHttpClient httpClient; protected HttpHost httpProxy; + private int retrySleepMillis = 1000; + + private int maxRetryTimes = 5; + public boolean checkSignature(String timestamp, String nonce, String signature) { try { return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature); @@ -439,7 +441,7 @@ public class WxMpServiceImpl implements WxMpService { return execute(new SimplePostRequestExecutor(), url, postData); } - /** + /** * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 * @param executor * @param uri @@ -448,6 +450,33 @@ public class WxMpServiceImpl implements WxMpService { * @throws WxErrorException */ public T execute(RequestExecutor executor, String uri, E data) throws WxErrorException { + 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 { + System.out.println("微信系统繁忙," + sleepMillis + "ms后重试(第" + (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; @@ -467,27 +496,6 @@ public class WxMpServiceImpl implements WxMpService { 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); } @@ -533,4 +541,16 @@ 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/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/resources/testng.xml b/weixin-java-mp/src/test/resources/testng.xml index 333400e4..cf9c2d64 100644 --- a/weixin-java-mp/src/test/resources/testng.xml +++ b/weixin-java-mp/src/test/resources/testng.xml @@ -3,6 +3,7 @@ + From 5ff2ab6436aea1f8bdcb082550037db80a56df07 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Wed, 21 Jan 2015 14:43:34 +0800 Subject: [PATCH 22/33] =?UTF-8?q?issue=20#23=20=E6=B7=BB=E5=8A=A0=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E8=BE=93=E5=87=BA=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E4=BA=86slf4j?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 416 +++++++++--------- .../common/session/SessionManagerImpl.java | 8 +- .../weixin/cp/api/WxCpServiceImpl.java | 6 +- .../weixin/mp/api/WxMpServiceImpl.java | 6 +- 4 files changed, 230 insertions(+), 206 deletions(-) diff --git a/pom.xml b/pom.xml index 83d777a2..8ce73cd4 100644 --- a/pom.xml +++ b/pom.xml @@ -1,219 +1,231 @@ - 4.0.0 - me.chanjar - weixin-java-parent - 1.0.7-SNAPSHOT - 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.0.7-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 + - - UTF-8 - true - true - 4.3.5 - + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.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} - - - 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/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java index 464296cc..0188c672 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java @@ -1,12 +1,16 @@ 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; public class SessionManagerImpl implements WxSessionManager, InternalSessionManager { + protected final Logger log = LoggerFactory.getLogger(SessionManagerImpl.class); + protected static final StringManager sm = StringManager.getManager(Constants.Package); @@ -226,7 +230,7 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana int expireHere = 0 ; if(log.isDebugEnabled()) - log.debug("Start expire sessions " + getName() + " at " + timeNow + " sessioncount " + sessions.length); + 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++; @@ -234,7 +238,7 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana } long timeEnd = System.currentTimeMillis(); if(log.isDebugEnabled()) - log.debug("End expire sessions " + getName() + " processingTime " + (timeEnd - timeNow) + " expired sessions: " + expireHere); + log.debug("End expire sessions {} processingTime {} expired sessions: {}", getName(), timeEnd - timeNow, expireHere); processingTime += ( timeEnd - timeNow ); } 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 70ac819a..c49a90a7 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 @@ -34,6 +34,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; @@ -45,6 +47,8 @@ import java.util.concurrent.atomic.AtomicBoolean; public class WxCpServiceImpl implements WxCpService { + protected final Logger log = LoggerFactory.getLogger(WxCpServiceImpl.class); + /** * 全局的是否正在刷新access token的锁 */ @@ -380,7 +384,7 @@ public class WxCpServiceImpl implements WxCpService { if (error.getErrorCode() == -1) { int sleepMillis = retrySleepMillis * (1 << retryTimes); try { - System.out.println("微信系统繁忙," + sleepMillis + "ms后重试(第" + (retryTimes + 1) + "次)"); + log.debug("微信系统繁忙,{}ms 后重试(第{}次)", sleepMillis, retryTimes + 1); Thread.sleep(sleepMillis); } catch (InterruptedException e1) { throw new RuntimeException(e1); 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 744f8bf2..21a02648 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 @@ -31,6 +31,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; @@ -42,6 +44,8 @@ import java.util.UUID; public class WxMpServiceImpl implements WxMpService { + protected final Logger log = LoggerFactory.getLogger(WxMpServiceImpl.class); + /** * 全局的是否正在刷新access token的锁 */ @@ -462,7 +466,7 @@ public class WxMpServiceImpl implements WxMpService { if (error.getErrorCode() == -1) { int sleepMillis = retrySleepMillis * (1 << retryTimes); try { - System.out.println("微信系统繁忙," + sleepMillis + "ms后重试(第" + (retryTimes + 1) + "次)"); + log.debug("微信系统繁忙,{}ms 后重试(第{}次)", sleepMillis, retryTimes + 1); Thread.sleep(sleepMillis); } catch (InterruptedException e1) { throw new RuntimeException(e1); From d3c9679eecb93655cd9e0b98e222c291d303cab5 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Wed, 21 Jan 2015 15:15:47 +0800 Subject: [PATCH 23/33] =?UTF-8?q?issue=20#66=20=E5=BE=AE=E4=BF=A1=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E5=8E=BB=E9=87=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...java => WxMsgIdMemoryDuplicateChecker.java} | 6 +++--- ... => WxMsgIdMemoryDuplicateCheckerTest.java} | 7 ++----- .../src/test/resources/testng.xml | 18 +++++++++--------- 3 files changed, 14 insertions(+), 17 deletions(-) rename weixin-java-common/src/main/java/me/chanjar/weixin/common/util/{WxMsgIdInMemoryDuplicateChecker.java => WxMsgIdMemoryDuplicateChecker.java} (90%) rename weixin-java-common/src/test/java/me/chanjar/weixin/common/util/{WxMsgIdInMemoryDuplicateCheckerTest.java => WxMsgIdMemoryDuplicateCheckerTest.java} (79%) diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateChecker.java similarity index 90% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateChecker.java index 0c8eee91..5343ff3c 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateChecker.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateChecker.java @@ -9,7 +9,7 @@ import java.util.concurrent.ConcurrentHashMap; * 将每个消息id保存在内存里,每隔5秒清理已经过期的消息id,每个消息id的过期时间是15秒 *
*/ -public class WxMsgIdInMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { +public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { /** * 一个消息ID在内存的过期时间:15秒 @@ -30,7 +30,7 @@ public class WxMsgIdInMemoryDuplicateChecker implements WxMsgIdDuplicateChecker * 每隔多少周期检查消息ID是否过期:5秒 *
*/ - public WxMsgIdInMemoryDuplicateChecker() { + public WxMsgIdMemoryDuplicateChecker() { this.timeToLive = 15 * 1000l; this.clearPeriod = 5 * 1000l; this.start(); @@ -41,7 +41,7 @@ public class WxMsgIdInMemoryDuplicateChecker implements WxMsgIdDuplicateChecker * @param timeToLive 一个消息ID在内存的过期时间:毫秒 * @param clearPeriod 每隔多少周期检查消息ID是否过期:毫秒 */ - public WxMsgIdInMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) { + public WxMsgIdMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) { this.timeToLive = timeToLive; this.clearPeriod = clearPeriod; this.start(); diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateCheckerTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateCheckerTest.java similarity index 79% rename from weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateCheckerTest.java rename to weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateCheckerTest.java index a35eba5c..b75d94d5 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdInMemoryDuplicateCheckerTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateCheckerTest.java @@ -3,15 +3,12 @@ package me.chanjar.weixin.common.util; import org.testng.Assert; import org.testng.annotations.Test; -/** - * Created by qianjia on 15/1/20. - */ @Test -public class WxMsgIdInMemoryDuplicateCheckerTest { +public class WxMsgIdMemoryDuplicateCheckerTest { public void test() throws InterruptedException { Long[] msgIds = new Long[] { 1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l }; - WxMsgIdInMemoryDuplicateChecker checker = new WxMsgIdInMemoryDuplicateChecker(2000l, 1000l); + WxMsgIdMemoryDuplicateChecker checker = new WxMsgIdMemoryDuplicateChecker(2000l, 1000l); // 第一次检查 for (Long msgId : msgIds) { diff --git a/weixin-java-common/src/test/resources/testng.xml b/weixin-java-common/src/test/resources/testng.xml index 49d65248..47d0d0d6 100644 --- a/weixin-java-common/src/test/resources/testng.xml +++ b/weixin-java-common/src/test/resources/testng.xml @@ -1,13 +1,13 @@ - - - - - - - - - + + + + + + + + + From 8e1d58d565065b0058371b497e6c37847e48b699 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Wed, 21 Jan 2015 15:15:57 +0800 Subject: [PATCH 24/33] remove author name --- .../me/chanjar/weixin/common/session/InternalSession.java | 3 --- .../weixin/common/session/InternalSessionFacade.java | 3 --- .../weixin/common/session/InternalSessionManager.java | 3 --- .../me/chanjar/weixin/common/session/SessionImpl.java | 3 --- .../java/me/chanjar/weixin/common/session/WxSession.java | 3 --- .../java/me/chanjar/weixin/cp/api/WxCpMessageRouter.java | 6 +++--- .../me/chanjar/weixin/cp/util/xml/XStreamTransformer.java | 3 --- .../java/me/chanjar/weixin/cp/demo/WxCpOAuth2Servlet.java | 3 --- .../me/chanjar/weixin/mp/api/WxMpMessageInterceptor.java | 2 -- .../java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java | 8 +++----- .../me/chanjar/weixin/mp/bean/WxMpTemplateMessage.java | 3 --- .../weixin/mp/bean/result/WxMpOAuth2AccessToken.java | 3 --- .../weixin/mp/util/json/WxMpCustomMessageGsonAdapter.java | 5 ----- .../chanjar/weixin/mp/util/json/WxMpGroupGsonAdapter.java | 5 ----- .../mp/util/json/WxMpMassGroupMessageGsonAdapter.java | 5 ----- .../weixin/mp/util/json/WxMpMassNewsGsonAdapter.java | 5 ----- .../mp/util/json/WxMpMassOpenIdsMessageGsonAdapter.java | 5 ----- .../weixin/mp/util/json/WxMpOAuth2AccessTokenAdapter.java | 3 --- .../mp/util/json/WxMpTemplateMessageGsonAdapter.java | 3 --- .../me/chanjar/weixin/mp/util/json/WxUserGsonAdapter.java | 5 ----- .../weixin/mp/util/json/WxUserListGsonAdapter.java | 5 ----- .../me/chanjar/weixin/mp/util/xml/XStreamTransformer.java | 3 --- 22 files changed, 6 insertions(+), 81 deletions(-) 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 index b13f5c86..e81a8717 100644 --- 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 @@ -1,8 +1,5 @@ package me.chanjar.weixin.common.session; -/** - * Created by qianjia on 15/1/21. - */ public interface InternalSession { /** diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java index 242b4c6f..458b40c1 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java @@ -2,9 +2,6 @@ package me.chanjar.weixin.common.session; import java.util.Enumeration; -/** - * Created by qianjia on 15/1/21. - */ public class InternalSessionFacade implements WxSession { /** 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 index e29fbd1d..7699717a 100644 --- 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 @@ -1,8 +1,5 @@ package me.chanjar.weixin.common.session; -/** - * Created by qianjia on 15/1/21. - */ public interface InternalSessionManager { /** diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java index eabe399e..8ad039ec 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java @@ -5,9 +5,6 @@ import me.chanjar.weixin.common.util.res.StringManager; import java.util.*; import java.util.concurrent.ConcurrentHashMap; -/** - * Created by qianjia on 15/1/21. - */ public class SessionImpl implements WxSession, InternalSession { /** 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 index 70d81aff..816b172b 100644 --- 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 @@ -2,9 +2,6 @@ package me.chanjar.weixin.common.session; import java.util.Enumeration; -/** - * Created by qianjia on 15/1/21. - */ public interface WxSession { public Object getAttribute(String name); 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 c88bc8a3..9e67d5bd 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,7 +1,7 @@ package me.chanjar.weixin.cp.api; import me.chanjar.weixin.common.util.WxMsgIdDuplicateChecker; -import me.chanjar.weixin.common.util.WxMsgIdInMemoryDuplicateChecker; +import me.chanjar.weixin.common.util.WxMsgIdMemoryDuplicateChecker; import me.chanjar.weixin.cp.bean.WxCpXmlMessage; import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; @@ -56,13 +56,13 @@ public class WxCpMessageRouter { public WxCpMessageRouter(WxCpService wxCpService) { this.wxCpService = wxCpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); - this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); + this.wxMsgIdDuplicateChecker = new WxMsgIdMemoryDuplicateChecker(); } public WxCpMessageRouter(WxCpService wxMpService, int threadPoolSize) { this.wxCpService = wxMpService; this.executorService = Executors.newFixedThreadPool(threadPoolSize); - this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); + this.wxMsgIdDuplicateChecker = new WxMsgIdMemoryDuplicateChecker(); } /** 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 index 13eeac17..c6c5a5f1 100644 --- 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 @@ -8,9 +8,6 @@ import java.io.InputStream; import java.util.HashMap; import java.util.Map; -/** - * Created by qianjia on 15/1/19. - */ public class XStreamTransformer { protected static final Map CLASS_2_XSTREAM_INSTANCE = configXStreamInstance(); 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-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..2e382515 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 @@ -6,8 +6,6 @@ import java.util.Map; /** * 微信消息拦截器,可以用来做验证 - * @author qianjia - * */ public interface WxMpMessageInterceptor { 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 7fcc3c96..00b0f80f 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,7 +1,7 @@ package me.chanjar.weixin.mp.api; import me.chanjar.weixin.common.util.WxMsgIdDuplicateChecker; -import me.chanjar.weixin.common.util.WxMsgIdInMemoryDuplicateChecker; +import me.chanjar.weixin.common.util.WxMsgIdMemoryDuplicateChecker; import me.chanjar.weixin.mp.bean.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; @@ -38,8 +38,6 @@ import java.util.regex.Pattern; * router.route(message); * *
- * @author qianjia - * */ public class WxMpMessageRouter { @@ -56,13 +54,13 @@ public class WxMpMessageRouter { public WxMpMessageRouter(WxMpService wxMpService) { this.wxMpService = wxMpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); - this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); + this.wxMsgIdDuplicateChecker = new WxMsgIdMemoryDuplicateChecker(); } public WxMpMessageRouter(WxMpService wxMpService, int threadPoolSize) { this.wxMpService = wxMpService; this.executorService = Executors.newFixedThreadPool(threadPoolSize); - this.wxMsgIdDuplicateChecker = new WxMsgIdInMemoryDuplicateChecker(); + this.wxMsgIdDuplicateChecker = new WxMsgIdMemoryDuplicateChecker(); } /** 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/result/WxMpOAuth2AccessToken.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java index d2a6ef8e..e2d0d1a0 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java @@ -2,9 +2,6 @@ package me.chanjar.weixin.mp.bean.result; import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; -/** - * Created by qianjia on 14/11/26. - */ public class WxMpOAuth2AccessToken { private String accessToken; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpCustomMessageGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpCustomMessageGsonAdapter.java index 488dd8df..3dc0515d 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpCustomMessageGsonAdapter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpCustomMessageGsonAdapter.java @@ -14,11 +14,6 @@ import me.chanjar.weixin.mp.bean.WxMpCustomMessage; import java.lang.reflect.Type; -/** - * - * @author qianjia - * - */ public class WxMpCustomMessageGsonAdapter implements JsonSerializer { 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/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 0c3d4acd..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 @@ -6,9 +6,6 @@ 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/WxMpTemplateMessageGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpTemplateMessageGsonAdapter.java index 21195b4d..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 @@ -17,9 +17,6 @@ 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 index 4699b01d..7b20c6f1 100644 --- 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 @@ -8,9 +8,6 @@ import java.io.InputStream; import java.util.HashMap; import java.util.Map; -/** - * Created by qianjia on 15/1/19. - */ public class XStreamTransformer { protected static final Map CLASS_2_XSTREAM_INSTANCE = configXStreamInstance(); From 7ea6e3ec038f62f73462c62e65b2cffa9bb249e7 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Wed, 21 Jan 2015 15:17:30 +0800 Subject: [PATCH 25/33] remove author --- .../chanjar/weixin/common/util/xml/XStreamCDataConverter.java | 3 --- .../me/chanjar/weixin/common/util/xml/XStreamInitializer.java | 3 --- .../weixin/common/util/xml/XStreamMediaIdConverter.java | 3 --- 3 files changed, 9 deletions(-) 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 index 69bad096..aa948b34 100644 --- 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 @@ -2,9 +2,6 @@ package me.chanjar.weixin.common.util.xml; import com.thoughtworks.xstream.converters.basic.StringConverter; -/** - * Created by qianjia on 15/1/19. - */ public class XStreamCDataConverter extends StringConverter { @Override 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 index 163c6558..d545a796 100644 --- 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 @@ -14,9 +14,6 @@ import com.thoughtworks.xstream.security.PrimitiveTypePermission; import java.io.Writer; -/** - * Created by qianjia on 15/1/19. - */ public class XStreamInitializer { public static XStream getInstance() { 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 index 0e375ea5..f67a3d9d 100644 --- 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 @@ -1,8 +1,5 @@ package me.chanjar.weixin.common.util.xml; -/** - * Created by qianjia on 15/1/19. - */ public class XStreamMediaIdConverter extends XStreamCDataConverter { @Override public String toString(Object obj) { From 4accbe6ec2d6a35f0f616630d5a6d9c744314872 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Wed, 21 Jan 2015 16:01:33 +0800 Subject: [PATCH 26/33] =?UTF-8?q?issue=20#69=20=E6=B7=BB=E5=8A=A0Session?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/session/InternalSession.java | 6 +- .../weixin/common/session/SessionImpl.java | 50 +++++++++++----- .../common/session/SessionManagerImpl.java | 27 ++++++++- .../common/session/WxSessionManager.java | 12 +++- .../util/WxMsgIdMemoryDuplicateChecker.java | 17 +++++- .../me/chanjar/weixin/cp/api/WxCpService.java | 26 +++++++++ .../weixin/cp/api/WxCpServiceImpl.java | 27 +++++++++ .../weixin/mp/api/WxMpMessageRouter.java | 58 +++++++++++++++++-- .../me/chanjar/weixin/mp/api/WxMpService.java | 11 ++-- .../weixin/mp/api/WxMpServiceImpl.java | 6 +- 10 files changed, 205 insertions(+), 35 deletions(-) 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 index e81a8717..79a7f4db 100644 --- 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 @@ -39,11 +39,9 @@ public interface InternalSession { void access(); /** - * Set the isNew flag for this session. - * - * @param isNew The new value for the isNew flag + * End the access. */ - void setNew(boolean isNew); + void endAccess(); /** * Set the creation time for this session. This method is called by the diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java index 8ad039ec..a899190d 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java @@ -4,6 +4,7 @@ import me.chanjar.weixin.common.util.res.StringManager; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; public class SessionImpl implements WxSession, InternalSession { @@ -90,11 +91,6 @@ public class SessionImpl implements WxSession, InternalSession { */ protected volatile boolean isValid = false; - /** - * Flag indicating whether this session is new or not. - */ - protected boolean isNew = false; - /** * We are currently processing a session expiration, so bypass * certain IllegalStateException tests. NOTE: This value is not @@ -123,11 +119,6 @@ public class SessionImpl implements WxSession, InternalSession { */ protected volatile long thisAccessedTime = creationTime; - /** - * The last accessed time for this Session. - */ - protected volatile long lastAccessedTime = creationTime; - /** * The default maximum inactive interval for Sessions created by * this Manager. @@ -140,9 +131,15 @@ public class SessionImpl implements WxSession, InternalSession { */ protected transient InternalSessionFacade facade = null; + /** + * The access count for this session. + */ + protected transient AtomicInteger accessCount = null; + public SessionImpl(InternalSessionManager manager) { this.manager = manager; + this.accessCount = new AtomicInteger(); } @@ -176,7 +173,28 @@ public class SessionImpl implements WxSession, InternalSession { @Override public boolean isValid() { - return 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 @@ -215,6 +233,8 @@ public class SessionImpl implements WxSession, InternalSession { // Mark this session as "being expired" expiring = true; + accessCount.set(0); + // Remove this session from our manager's active sessions manager.remove(this, true); @@ -238,23 +258,23 @@ public class SessionImpl implements WxSession, InternalSession { public void access() { this.thisAccessedTime = System.currentTimeMillis(); + accessCount.incrementAndGet(); } @Override - public void setNew(boolean isNew) { + public void endAccess() { - this.isNew = isNew; + this.thisAccessedTime = System.currentTimeMillis(); + accessCount.decrementAndGet(); } - @Override public void setCreationTime(long time) { this.creationTime = time; - this.lastAccessedTime = time; this.thisAccessedTime = time; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java index 0188c672..e6de713d 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java @@ -6,6 +6,7 @@ import org.slf4j.LoggerFactory; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; public class SessionManagerImpl implements WxSessionManager, InternalSessionManager { @@ -104,6 +105,11 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana */ protected int processExpiresFrequency = 6; + /** + * 后台清理线程是否已经开启 + */ + private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false); + @Override public void remove(InternalSession session) { remove(session, false); @@ -155,7 +161,6 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana InternalSession session = createEmptySession(); // Initialize the properties of the new session and return it - session.setNew(true); session.setValid(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(this.maxInactiveInterval); @@ -191,6 +196,26 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana @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(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 ) { 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 index e2b1dd79..262d9fe4 100644 --- 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 @@ -2,9 +2,19 @@ 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/WxMsgIdMemoryDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateChecker.java index 5343ff3c..461a245a 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateChecker.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateChecker.java @@ -2,6 +2,7 @@ package me.chanjar.weixin.common.util; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; /** *
@@ -21,8 +22,16 @@ public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker {
    */
   private final Long clearPeriod;
 
+  /**
+   * 消息id->消息时间戳的map
+   */
   private final ConcurrentHashMap msgId2Timestamp = new ConcurrentHashMap();
 
+  /**
+   * 后台清理线程是否已经开启
+   */
+  private final AtomicBoolean backgroundProcessStarted = new AtomicBoolean(false);
+
   /**
    * WxMsgIdInMemoryDuplicateChecker构造函数
    * 
@@ -33,7 +42,6 @@ public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker {
   public WxMsgIdMemoryDuplicateChecker() {
     this.timeToLive = 15 * 1000l;
     this.clearPeriod = 5 * 1000l;
-    this.start();
   }
 
   /**
@@ -44,10 +52,12 @@ public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker {
   public WxMsgIdMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) {
     this.timeToLive = timeToLive;
     this.clearPeriod = clearPeriod;
-    this.start();
   }
 
-  private void start() {
+  protected void checkBackgroundProcessStarted() {
+    if (backgroundProcessStarted.getAndSet(true)) {
+      return;
+    }
     Thread t = new Thread(new Runnable() {
       @Override
       public void run() {
@@ -72,6 +82,7 @@ public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker {
 
   @Override
   public boolean isDuplicate(Long wxMsgId) {
+    checkBackgroundProcessStarted();
     Long timestamp = msgId2Timestamp.putIfAbsent(wxMsgId, System.currentTimeMillis());
     if (timestamp == null) {
       // 第一次接收到这个消息
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 c3b8b439..29ec8177 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
@@ -3,6 +3,8 @@ package me.chanjar.weixin.cp.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.cp.bean.WxCpDepart;
 import me.chanjar.weixin.cp.bean.WxCpMessage;
@@ -371,4 +373,28 @@ public interface WxCpService {
    * @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.SessionManagerImpl}
+   * 
+ * @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 c49a90a7..473050e6 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.SessionManagerImpl; +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; @@ -64,6 +67,8 @@ public class WxCpServiceImpl implements WxCpService { private int maxRetryTimes = 5; + protected WxSessionManager sessionManager = new SessionManagerImpl(); + public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { try { return SHA1.gen(wxCpConfigStorage.getToken(), timestamp, nonce, data).equals(msgSignature); @@ -473,4 +478,26 @@ public class WxCpServiceImpl implements WxCpService { 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-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 00b0f80f..f069fc8d 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,16 +1,25 @@ package me.chanjar.weixin.mp.api; +import me.chanjar.weixin.common.session.InternalSession; +import me.chanjar.weixin.common.session.SessionManagerImpl; +import me.chanjar.weixin.common.session.WxSession; +import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.WxMsgIdDuplicateChecker; import me.chanjar.weixin.common.util.WxMsgIdMemoryDuplicateChecker; import me.chanjar.weixin.mp.bean.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.WxMpXmlOutMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import javax.swing.text.StyledEditorKit; 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; /** @@ -41,6 +50,8 @@ import java.util.regex.Pattern; */ public class WxMpMessageRouter { + protected final Logger log = LoggerFactory.getLogger(WxMpMessageRouter.class); + private static final int DEFAULT_THREAD_POOL_SIZE = 20; private final List rules = new ArrayList(); @@ -51,6 +62,8 @@ public class WxMpMessageRouter { private WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker; + protected WxSessionManager sessionManager = new SessionManagerImpl(); + public WxMpMessageRouter(WxMpService wxMpService) { this.wxMpService = wxMpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); @@ -113,21 +126,54 @@ public class WxMpMessageRouter { } 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,它已经用不着了 + if (futures.size() > 0) { + executorService.submit(new Runnable() { + @Override + public void run() { + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException e) { + log.error("Error happened when wait task finish", e); + } catch (ExecutionException e) { + log.error("Error happened when wait task finish", e); + } + } + // 在这里session再也不会被使用了 + sessionEndAccess(wxMessage); + } + }); + } else { + // 在这里session再也不会被使用了 + sessionEndAccess(wxMessage); + } return res; } - + + protected void sessionEndAccess(WxMpXmlMessage wxMessage) { + WxSession session = sessionManager.getSession(wxMessage.getFromUserName(), false); + if (session != null) { + ((InternalSession) session).endAccess(); + } + } + public static class Rule { private final WxMpMessageRouter routerBuilder; 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 3ad10bdc..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 @@ -3,6 +3,8 @@ 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.*; @@ -456,10 +458,10 @@ 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); /** @@ -479,4 +481,5 @@ public interface WxMpService { * @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 21a02648..f3c96f5b 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,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.SessionManagerImpl; +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; @@ -66,6 +69,8 @@ public class WxMpServiceImpl implements WxMpService { private int maxRetryTimes = 5; + protected WxSessionManager sessionManager = new SessionManagerImpl(); + public boolean checkSignature(String timestamp, String nonce, String signature) { try { return SHA1.gen(wxMpConfigStorage.getToken(), timestamp, nonce).equals(signature); @@ -545,7 +550,6 @@ public class WxMpServiceImpl implements WxMpService { } } - @Override public void setRetrySleepMillis(int retrySleepMillis) { this.retrySleepMillis = retrySleepMillis; From 7184711301806832bbc2d66b3e4045b3aab04673 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Wed, 21 Jan 2015 19:02:47 +0800 Subject: [PATCH 27/33] refactor --- ...er.java => WxMessageDuplicateChecker.java} | 2 +- ...=> WxMessageInMemoryDuplicateChecker.java} | 6 +-- ...xMessageInMemoryDuplicateCheckerTest.java} | 4 +- .../src/test/resources/testng.xml | 2 +- .../cp/api/WxCpInMemoryConfigStorage.java | 4 +- .../weixin/cp/api/WxCpMessageRouter.java | 24 ++++----- .../mp/api/WxMpInMemoryConfigStorage.java | 3 +- .../weixin/mp/api/WxMpMessageRouter.java | 53 ++++++++++++------- 8 files changed, 54 insertions(+), 44 deletions(-) rename weixin-java-common/src/main/java/me/chanjar/weixin/common/util/{WxMsgIdDuplicateChecker.java => WxMessageDuplicateChecker.java} (89%) rename weixin-java-common/src/main/java/me/chanjar/weixin/common/util/{WxMsgIdMemoryDuplicateChecker.java => WxMessageInMemoryDuplicateChecker.java} (91%) rename weixin-java-common/src/test/java/me/chanjar/weixin/common/util/{WxMsgIdMemoryDuplicateCheckerTest.java => WxMessageInMemoryDuplicateCheckerTest.java} (82%) diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageDuplicateChecker.java similarity index 89% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdDuplicateChecker.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageDuplicateChecker.java index b3d320b1..21f21c5d 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdDuplicateChecker.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageDuplicateChecker.java @@ -6,7 +6,7 @@ package me.chanjar.weixin.common.util; * 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次 *
*/ -public interface WxMsgIdDuplicateChecker { +public interface WxMessageDuplicateChecker { /** * 检查消息ID是否重复 diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateChecker.java similarity index 91% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateChecker.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateChecker.java index 461a245a..2dbfa611 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateChecker.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateChecker.java @@ -10,7 +10,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * 将每个消息id保存在内存里,每隔5秒清理已经过期的消息id,每个消息id的过期时间是15秒 *
*/ -public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { +public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChecker { /** * 一个消息ID在内存的过期时间:15秒 @@ -39,7 +39,7 @@ public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { * 每隔多少周期检查消息ID是否过期:5秒 *
*/ - public WxMsgIdMemoryDuplicateChecker() { + public WxMessageInMemoryDuplicateChecker() { this.timeToLive = 15 * 1000l; this.clearPeriod = 5 * 1000l; } @@ -49,7 +49,7 @@ public class WxMsgIdMemoryDuplicateChecker implements WxMsgIdDuplicateChecker { * @param timeToLive 一个消息ID在内存的过期时间:毫秒 * @param clearPeriod 每隔多少周期检查消息ID是否过期:毫秒 */ - public WxMsgIdMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) { + public WxMessageInMemoryDuplicateChecker(Long timeToLive, Long clearPeriod) { this.timeToLive = timeToLive; this.clearPeriod = clearPeriod; } diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateCheckerTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateCheckerTest.java similarity index 82% rename from weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateCheckerTest.java rename to weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateCheckerTest.java index b75d94d5..55e99070 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMsgIdMemoryDuplicateCheckerTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateCheckerTest.java @@ -4,11 +4,11 @@ import org.testng.Assert; import org.testng.annotations.Test; @Test -public class WxMsgIdMemoryDuplicateCheckerTest { +public class WxMessageInMemoryDuplicateCheckerTest { public void test() throws InterruptedException { Long[] msgIds = new Long[] { 1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l }; - WxMsgIdMemoryDuplicateChecker checker = new WxMsgIdMemoryDuplicateChecker(2000l, 1000l); + WxMessageInMemoryDuplicateChecker checker = new WxMessageInMemoryDuplicateChecker(2000l, 1000l); // 第一次检查 for (Long msgId : msgIds) { diff --git a/weixin-java-common/src/test/resources/testng.xml b/weixin-java-common/src/test/resources/testng.xml index 47d0d0d6..984f0ae8 100644 --- a/weixin-java-common/src/test/resources/testng.xml +++ b/weixin-java-common/src/test/resources/testng.xml @@ -7,7 +7,7 @@ - + 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 dd4882e2..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 @@ -37,11 +37,11 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { this.expiresTime = 0; } - public void updateAccessToken(WxAccessToken accessToken) { + public synchronized void updateAccessToken(WxAccessToken accessToken) { updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); } - public void updateAccessToken(String accessToken, int expiresInSeconds) { + public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { this.accessToken = accessToken; this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000l; } 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 9e67d5bd..5b9cd574 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,7 +1,7 @@ package me.chanjar.weixin.cp.api; -import me.chanjar.weixin.common.util.WxMsgIdDuplicateChecker; -import me.chanjar.weixin.common.util.WxMsgIdMemoryDuplicateChecker; +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; @@ -43,7 +43,7 @@ import java.util.regex.Pattern; */ public class WxCpMessageRouter { - private static final int DEFAULT_THREAD_POOL_SIZE = 20; + private static final int DEFAULT_THREAD_POOL_SIZE = 100; private final List rules = new ArrayList(); @@ -51,18 +51,12 @@ public class WxCpMessageRouter { private ExecutorService executorService; - private WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker; + private WxMessageDuplicateChecker wxMessageDuplicateChecker; public WxCpMessageRouter(WxCpService wxCpService) { this.wxCpService = wxCpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); - this.wxMsgIdDuplicateChecker = new WxMsgIdMemoryDuplicateChecker(); - } - - public WxCpMessageRouter(WxCpService wxMpService, int threadPoolSize) { - this.wxCpService = wxMpService; - this.executorService = Executors.newFixedThreadPool(threadPoolSize); - this.wxMsgIdDuplicateChecker = new WxMsgIdMemoryDuplicateChecker(); + this.wxMessageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); } /** @@ -75,10 +69,10 @@ public class WxCpMessageRouter { /** * 设置自定义的WxMsgIdDuplicateChecker - * @param wxMsgIdDuplicateChecker + * @param wxMessageDuplicateChecker */ - public void setWxMsgIdDuplicateChecker(WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker) { - this.wxMsgIdDuplicateChecker = wxMsgIdDuplicateChecker; + public void setWxMessageDuplicateChecker(WxMessageDuplicateChecker wxMessageDuplicateChecker) { + this.wxMessageDuplicateChecker = wxMessageDuplicateChecker; } /** @@ -94,7 +88,7 @@ public class WxCpMessageRouter { * @param wxMessage */ public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { - if (wxMsgIdDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { + if (wxMessageDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { // 如果是重复消息,那么就不做处理 return null; } 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 c694dde8..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 @@ -152,7 +152,6 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { this.http_proxy_password = http_proxy_password; } - @Override public String toString() { return "WxMpInMemoryConfigStorage{" + @@ -166,6 +165,8 @@ public class WxMpInMemoryConfigStorage implements WxMpConfigStorage { ", 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/WxMpMessageRouter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java index f069fc8d..a46d5716 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 @@ -4,14 +4,13 @@ import me.chanjar.weixin.common.session.InternalSession; import me.chanjar.weixin.common.session.SessionManagerImpl; import me.chanjar.weixin.common.session.WxSession; import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.common.util.WxMsgIdDuplicateChecker; -import me.chanjar.weixin.common.util.WxMsgIdMemoryDuplicateChecker; +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 javax.swing.text.StyledEditorKit; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -52,7 +51,7 @@ public class WxMpMessageRouter { protected final Logger log = LoggerFactory.getLogger(WxMpMessageRouter.class); - private static final int DEFAULT_THREAD_POOL_SIZE = 20; + private static final int DEFAULT_THREAD_POOL_SIZE = 100; private final List rules = new ArrayList(); @@ -60,24 +59,22 @@ public class WxMpMessageRouter { private ExecutorService executorService; - private WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker; + private WxMessageDuplicateChecker wxMessageDuplicateChecker; - protected WxSessionManager sessionManager = new SessionManagerImpl(); + private WxSessionManager sessionManager; public WxMpMessageRouter(WxMpService wxMpService) { this.wxMpService = wxMpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); - this.wxMsgIdDuplicateChecker = new WxMsgIdMemoryDuplicateChecker(); - } - - public WxMpMessageRouter(WxMpService wxMpService, int threadPoolSize) { - this.wxMpService = wxMpService; - this.executorService = Executors.newFixedThreadPool(threadPoolSize); - this.wxMsgIdDuplicateChecker = new WxMsgIdMemoryDuplicateChecker(); + this.wxMessageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); + this.sessionManager = new SessionManagerImpl(); } /** - * 设置自定义的ExecutorService + *
+   * 设置自定义的 {@link ExecutorService}
+   * 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100)
+   * 
* @param executorService */ public void setExecutorService(ExecutorService executorService) { @@ -85,11 +82,25 @@ public class WxMpMessageRouter { } /** - * 设置自定义的WxMsgIdDuplicateChecker - * @param wxMsgIdDuplicateChecker + *
+   * 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker}
+   * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker}
+   * 
+ * @param wxMessageDuplicateChecker + */ + public void setWxMessageDuplicateChecker(WxMessageDuplicateChecker wxMessageDuplicateChecker) { + this.wxMessageDuplicateChecker = wxMessageDuplicateChecker; + } + + /** + *
+   * 设置自定义的{@link me.chanjar.weixin.common.session.WxSessionManager}
+   * 如果不调用该方法,默认使用 {@linke SessionManagerImpl}
+   * 
+ * @param sessionManager */ - public void setWxMsgIdDuplicateChecker(WxMsgIdDuplicateChecker wxMsgIdDuplicateChecker) { - this.wxMsgIdDuplicateChecker = wxMsgIdDuplicateChecker; + public void setSessionManager(WxSessionManager sessionManager) { + this.sessionManager = sessionManager; } /** @@ -105,7 +116,7 @@ public class WxMpMessageRouter { * @param wxMessage */ public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) { - if (wxMsgIdDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { + if (wxMessageDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { // 如果是重复消息,那么就不做处理 return null; } @@ -167,6 +178,10 @@ public class WxMpMessageRouter { return res; } + /** + * 对session的访问结束 + * @param wxMessage + */ protected void sessionEndAccess(WxMpXmlMessage wxMessage) { WxSession session = sessionManager.getSession(wxMessage.getFromUserName(), false); if (session != null) { From d18b66c38d331b9cab9d25db846b708c0be1d1a0 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Wed, 21 Jan 2015 19:58:40 +0800 Subject: [PATCH 28/33] =?UTF-8?q?issue=20#69=20=E6=B7=BB=E5=8A=A0Session?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...rImpl.java => InMemorySessionManager.java} | 40 ++++++- .../common/session/InternalSessionFacade.java | 2 +- .../session/InternalSessionManager.java | 11 ++ .../weixin/common/session/SessionImpl.java | 36 ++++++ .../weixin/common/session/TestSession.java | 68 ++++++++++++ .../src/test/resources/testng.xml | 1 + .../weixin/cp/api/WxCpMessageHandler.java | 14 ++- .../weixin/cp/api/WxCpMessageInterceptor.java | 16 ++- .../weixin/cp/api/WxCpMessageRouter.java | 104 +++++++++++++++--- .../me/chanjar/weixin/cp/api/WxCpService.java | 2 +- .../weixin/cp/api/WxCpServiceImpl.java | 5 +- .../weixin/cp/api/WxCpMessageRouterTest.java | 7 +- .../weixin/cp/demo/WxCpDemoServer.java | 5 +- .../weixin/mp/api/WxMpMessageHandler.java | 14 ++- .../weixin/mp/api/WxMpMessageInterceptor.java | 16 ++- .../weixin/mp/api/WxMpMessageRouter.java | 29 ++--- .../weixin/mp/api/WxMpServiceImpl.java | 5 +- .../weixin/mp/api/WxMpMessageRouterTest.java | 7 +- .../weixin/mp/demo/WxMpDemoServer.java | 7 +- 19 files changed, 320 insertions(+), 69 deletions(-) rename weixin-java-common/src/main/java/me/chanjar/weixin/common/session/{SessionManagerImpl.java => InMemorySessionManager.java} (88%) create mode 100644 weixin-java-common/src/test/java/me/chanjar/weixin/common/session/TestSession.java diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InMemorySessionManager.java similarity index 88% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InMemorySessionManager.java index e6de713d..4d4d4c00 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionManagerImpl.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InMemorySessionManager.java @@ -8,9 +8,9 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; -public class SessionManagerImpl implements WxSessionManager, InternalSessionManager { +public class InMemorySessionManager implements WxSessionManager, InternalSessionManager { - protected final Logger log = LoggerFactory.getLogger(SessionManagerImpl.class); + protected final Logger log = LoggerFactory.getLogger(InMemorySessionManager.class); protected static final StringManager sm = StringManager.getManager(Constants.Package); @@ -105,6 +105,11 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana */ protected int processExpiresFrequency = 6; + /** + * background processor delay in seconds + */ + protected int backgroundProcessorDelay = 10; + /** * 后台清理线程是否已经开启 */ @@ -204,7 +209,7 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana while (true) { try { // 每秒清理一次 - Thread.sleep(1000l); + Thread.sleep(backgroundProcessorDelay * 1000l); backgroundProcess(); } catch (InterruptedException e) { log.error("SessionManagerImpl.backgroundProcess error", e); @@ -225,6 +230,7 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana } } } + } /** @@ -269,6 +275,34 @@ public class SessionManagerImpl implements WxSessionManager, InternalSessionMana } + @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. */ diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java index 458b40c1..d7c0bd62 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java @@ -10,7 +10,7 @@ public class InternalSessionFacade implements WxSession { private WxSession session = null; public InternalSessionFacade(WxSession session) { - session = session; + this.session = session; } @Override 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 index 7699717a..9dea5b71 100644 --- 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 @@ -60,4 +60,15 @@ public interface InternalSessionManager { */ 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); + + void setProcessExpiresFrequency(int processExpiresFrequency); + + void setBackgroundProcessorDelay(int backgroundProcessorDelay); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java index a899190d..96cc470b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java @@ -308,4 +308,40 @@ public class SessionImpl implements WxSession, InternalSession { } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SessionImpl)) return false; + + SessionImpl session = (SessionImpl) 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/test/java/me/chanjar/weixin/common/session/TestSession.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/session/TestSession.java new file mode 100644 index 00000000..20e2dd88 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/session/TestSession.java @@ -0,0 +1,68 @@ +package me.chanjar.weixin.common.session; + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +@Test +public class TestSession { + + @DataProvider + public Object[][] getSessionManager() { + return new Object[][] { + new Object[] { new InMemorySessionManager() } + }; + } + + + @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.assertTrue(session1.equals(session2)); + + WxSession abc1 = sessionManager.getSession("abc1"); + Assert.assertFalse(session1.equals(abc1)); + + WxSession abc1b = sessionManager.getSession("abc1", false); + Assert.assertTrue(abc1.equals(abc1b)); + + WxSession def = sessionManager.getSession("def", false); + Assert.assertNull(def); + } + + @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); + + } + +} diff --git a/weixin-java-common/src/test/resources/testng.xml b/weixin-java-common/src/test/resources/testng.xml index 984f0ae8..ed4bf6ad 100644 --- a/weixin-java-common/src/test/resources/testng.xml +++ b/weixin-java-common/src/test/resources/testng.xml @@ -8,6 +8,7 @@ + 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 e4e26eb2..3b838781 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,5 +1,6 @@ package me.chanjar.weixin.cp.api; +import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.cp.bean.WxCpXmlMessage; import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; @@ -7,18 +8,21 @@ 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 5cb478e3..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 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 5b9cd574..cf831cf0 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,24 @@ package me.chanjar.weixin.cp.api; +import me.chanjar.weixin.common.session.InternalSession; +import me.chanjar.weixin.common.session.WxSession; +import me.chanjar.weixin.common.session.InMemorySessionManager; +import me.chanjar.weixin.common.session.WxSessionManager; 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; /** @@ -43,6 +51,8 @@ import java.util.regex.Pattern; */ public class WxCpMessageRouter { + protected final Logger log = LoggerFactory.getLogger(WxCpMessageRouter.class); + private static final int DEFAULT_THREAD_POOL_SIZE = 100; private final List rules = new ArrayList(); @@ -51,16 +61,22 @@ public class WxCpMessageRouter { private ExecutorService executorService; - private WxMessageDuplicateChecker wxMessageDuplicateChecker; + private WxMessageDuplicateChecker messageDuplicateChecker; + + private WxSessionManager sessionManager; public WxCpMessageRouter(WxCpService wxCpService) { this.wxCpService = wxCpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); - this.wxMessageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); + this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); + this.sessionManager = new InMemorySessionManager(); } /** - * 设置自定义的ExecutorService + *
+   * 设置自定义的 {@link ExecutorService}
+   * 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100)
+   * 
* @param executorService */ public void setExecutorService(ExecutorService executorService) { @@ -68,11 +84,25 @@ public class WxCpMessageRouter { } /** - * 设置自定义的WxMsgIdDuplicateChecker - * @param wxMessageDuplicateChecker + *
+   * 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker}
+   * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker}
+   * 
+ * @param messageDuplicateChecker */ - public void setWxMessageDuplicateChecker(WxMessageDuplicateChecker wxMessageDuplicateChecker) { - this.wxMessageDuplicateChecker = wxMessageDuplicateChecker; + 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; } /** @@ -80,7 +110,7 @@ public class WxCpMessageRouter { * @return */ public Rule rule() { - return new Rule(this, wxCpService); + return new Rule(this, wxCpService, sessionManager); } /** @@ -88,7 +118,7 @@ public class WxCpMessageRouter { * @param wxMessage */ public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { - if (wxMessageDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { + if (messageDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { // 如果是重复消息,那么就不做处理 return null; } @@ -109,27 +139,66 @@ 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,它已经用不着了 + if (futures.size() > 0) { + executorService.submit(new Runnable() { + @Override + public void run() { + for (Future future : futures) { + try { + future.get(); + } catch (InterruptedException e) { + log.error("Error happened when wait task finish", e); + } catch (ExecutionException e) { + log.error("Error happened when wait task finish", e); + } + } + // 在这里session再也不会被使用了 + sessionEndAccess(wxMessage); + } + }); + } else { + // 在这里session再也不会被使用了 + sessionEndAccess(wxMessage); + } return res; } + /** + * 对session的访问结束 + * @param wxMessage + */ + protected void sessionEndAccess(WxCpXmlMessage wxMessage) { + WxSession session = sessionManager.getSession(wxMessage.getFromUserName(), false); + if (session != null) { + ((InternalSession) 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; @@ -152,9 +221,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; } /** @@ -332,7 +402,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; } } @@ -341,7 +411,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 29ec8177..e0b624bb 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 @@ -392,7 +392,7 @@ public interface WxCpService { /** *
    * 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法,
-   * WxCpService默认使用的是{@link me.chanjar.weixin.common.session.SessionManagerImpl}
+   * WxCpService默认使用的是{@link me.chanjar.weixin.common.session.InMemorySessionManager}
    * 
* @param 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 473050e6..1afcd7b3 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,7 +12,7 @@ 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.SessionManagerImpl; +import me.chanjar.weixin.common.session.InMemorySessionManager; import me.chanjar.weixin.common.session.WxSession; import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.StringUtils; @@ -46,7 +46,6 @@ 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 { @@ -67,7 +66,7 @@ public class WxCpServiceImpl implements WxCpService { private int maxRetryTimes = 5; - protected WxSessionManager sessionManager = new SessionManagerImpl(); + protected WxSessionManager sessionManager = new InMemorySessionManager(); public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { try { 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 b3dc6c30..0053a86b 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,6 +1,7 @@ package me.chanjar.weixin.cp.api; import me.chanjar.weixin.common.api.WxConsts; +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; @@ -67,7 +68,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,7 +151,8 @@ 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; } 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 81786100..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; @@ -46,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("测试加密消息") @@ -60,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 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 2e382515..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,21 +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 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 a46d5716..c3537380 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,7 +1,7 @@ package me.chanjar.weixin.mp.api; import me.chanjar.weixin.common.session.InternalSession; -import me.chanjar.weixin.common.session.SessionManagerImpl; +import me.chanjar.weixin.common.session.InMemorySessionManager; import me.chanjar.weixin.common.session.WxSession; import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.WxMessageDuplicateChecker; @@ -59,15 +59,15 @@ public class WxMpMessageRouter { private ExecutorService executorService; - private WxMessageDuplicateChecker wxMessageDuplicateChecker; + private WxMessageDuplicateChecker messageDuplicateChecker; private WxSessionManager sessionManager; public WxMpMessageRouter(WxMpService wxMpService) { this.wxMpService = wxMpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); - this.wxMessageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); - this.sessionManager = new SessionManagerImpl(); + this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); + this.sessionManager = new InMemorySessionManager(); } /** @@ -86,10 +86,10 @@ public class WxMpMessageRouter { * 设置自定义的 {@link me.chanjar.weixin.common.util.WxMessageDuplicateChecker} * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateChecker} * - * @param wxMessageDuplicateChecker + * @param messageDuplicateChecker */ - public void setWxMessageDuplicateChecker(WxMessageDuplicateChecker wxMessageDuplicateChecker) { - this.wxMessageDuplicateChecker = wxMessageDuplicateChecker; + public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) { + this.messageDuplicateChecker = messageDuplicateChecker; } /** @@ -108,7 +108,7 @@ public class WxMpMessageRouter { * @return */ public Rule rule() { - return new Rule(this, wxMpService); + return new Rule(this, wxMpService, sessionManager); } /** @@ -116,7 +116,7 @@ public class WxMpMessageRouter { * @param wxMessage */ public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) { - if (wxMessageDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { + if (messageDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { // 如果是重复消息,那么就不做处理 return null; } @@ -195,6 +195,8 @@ public class WxMpMessageRouter { private final WxMpService wxMpService; + private final WxSessionManager sessionManager; + private boolean async = true; private String fromUser; @@ -214,10 +216,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; } /** @@ -383,7 +386,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; } } @@ -392,7 +395,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/WxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpServiceImpl.java index f3c96f5b..21a8b94c 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,8 +10,7 @@ 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.SessionManagerImpl; -import me.chanjar.weixin.common.session.WxSession; +import me.chanjar.weixin.common.session.InMemorySessionManager; import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.StringUtils; import me.chanjar.weixin.common.util.crypto.SHA1; @@ -69,7 +68,7 @@ public class WxMpServiceImpl implements WxMpService { private int maxRetryTimes = 5; - protected WxSessionManager sessionManager = new SessionManagerImpl(); + protected WxSessionManager sessionManager = new InMemorySessionManager(); public boolean checkSignature(String timestamp, String nonce, String signature) { try { 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 3f7e841e..15506c85 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,6 +1,7 @@ package me.chanjar.weixin.mp.api; import me.chanjar.weixin.common.api.WxConsts; +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; @@ -67,7 +68,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,7 +151,8 @@ 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; } 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 c58671cf..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,6 +3,7 @@ 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.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.WxMpXmlOutImageMessage; @@ -51,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(); @@ -62,7 +63,7 @@ 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")); @@ -86,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 From 6368ca6816ead05bab61d85b4d9cfc0656015462 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Thu, 22 Jan 2015 12:36:51 +0800 Subject: [PATCH 29/33] =?UTF-8?q?issue=20#69=20=E6=B7=BB=E5=8A=A0Session?= =?UTF-8?q?=E7=9A=84=E6=94=AF=E6=8C=81=EF=BC=8C=E6=B7=BB=E5=8A=A0=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../session/InternalSessionManager.java | 39 +++++ ...{SessionImpl.java => StandardSession.java} | 13 +- ...Facade.java => StandardSessionFacade.java} | 9 +- ...nager.java => StandardSessionManager.java} | 38 ++--- .../WxMessageInMemoryDuplicateChecker.java | 3 + .../weixin/common/session/SessionTest.java | 132 +++++++++++++++++ .../weixin/common/session/TestSession.java | 68 --------- .../src/test/resources/testng.xml | 2 +- .../weixin/cp/api/WxCpMessageHandler.java | 1 + .../weixin/cp/api/WxCpMessageRouter.java | 25 ++-- .../me/chanjar/weixin/cp/api/WxCpService.java | 2 +- .../weixin/cp/api/WxCpServiceImpl.java | 4 +- .../weixin/cp/api/WxCpMessageRouterTest.java | 134 ++++++++++++++++++ .../weixin/mp/api/WxMpMessageRouter.java | 39 +++-- .../weixin/mp/api/WxMpServiceImpl.java | 4 +- .../weixin/mp/api/WxMpMessageRouterTest.java | 133 +++++++++++++++++ 16 files changed, 514 insertions(+), 132 deletions(-) rename weixin-java-common/src/main/java/me/chanjar/weixin/common/session/{SessionImpl.java => StandardSession.java} (95%) rename weixin-java-common/src/main/java/me/chanjar/weixin/common/session/{InternalSessionFacade.java => StandardSessionFacade.java} (76%) rename weixin-java-common/src/main/java/me/chanjar/weixin/common/session/{InMemorySessionManager.java => StandardSessionManager.java} (92%) create mode 100644 weixin-java-common/src/test/java/me/chanjar/weixin/common/session/SessionTest.java delete mode 100644 weixin-java-common/src/test/java/me/chanjar/weixin/common/session/TestSession.java 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 index 9dea5b71..556d8ab8 100644 --- 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 @@ -2,6 +2,19 @@ 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 @@ -68,7 +81,33 @@ public interface InternalSessionManager { */ 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/SessionImpl.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSession.java similarity index 95% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSession.java index 96cc470b..d9827308 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/SessionImpl.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSession.java @@ -6,7 +6,7 @@ import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; -public class SessionImpl implements WxSession, InternalSession { +public class StandardSession implements WxSession, InternalSession { /** * The string manager for this package. @@ -129,7 +129,7 @@ public class SessionImpl implements WxSession, InternalSession { * The facade associated with this session. NOTE: This value is not * included in the serialized version of this object. */ - protected transient InternalSessionFacade facade = null; + protected transient StandardSessionFacade facade = null; /** * The access count for this session. @@ -137,7 +137,7 @@ public class SessionImpl implements WxSession, InternalSession { protected transient AtomicInteger accessCount = null; - public SessionImpl(InternalSessionManager manager) { + public StandardSession(InternalSessionManager manager) { this.manager = manager; this.accessCount = new AtomicInteger(); } @@ -147,7 +147,7 @@ public class SessionImpl implements WxSession, InternalSession { public WxSession getSession() { if (facade == null){ - facade = new InternalSessionFacade(this); + facade = new StandardSessionFacade(this); } return (facade); @@ -281,7 +281,6 @@ public class SessionImpl implements WxSession, InternalSession { @Override public void setMaxInactiveInterval(int interval) { - int oldMaxInactiveInterval = this.maxInactiveInterval; this.maxInactiveInterval = interval; } @@ -311,9 +310,9 @@ public class SessionImpl implements WxSession, InternalSession { @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof SessionImpl)) return false; + if (!(o instanceof StandardSession)) return false; - SessionImpl session = (SessionImpl) o; + StandardSession session = (StandardSession) o; if (creationTime != session.creationTime) return false; if (expiring != session.expiring) return false; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionFacade.java similarity index 76% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionFacade.java index d7c0bd62..a9631de6 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InternalSessionFacade.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionFacade.java @@ -2,17 +2,21 @@ package me.chanjar.weixin.common.session; import java.util.Enumeration; -public class InternalSessionFacade implements WxSession { +public class StandardSessionFacade implements WxSession { /** * Wrapped session object. */ private WxSession session = null; - public InternalSessionFacade(WxSession session) { + public StandardSessionFacade(StandardSession session) { this.session = session; } + public InternalSession getInternalSession() { + return (InternalSession) session; + } + @Override public Object getAttribute(String name) { return session.getAttribute(name); @@ -37,4 +41,5 @@ public class InternalSessionFacade implements WxSession { public void invalidate() { session.invalidate(); } + } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InMemorySessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java similarity index 92% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InMemorySessionManager.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java index 4d4d4c00..f3a02695 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/InMemorySessionManager.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java @@ -8,9 +8,12 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; -public class InMemorySessionManager implements WxSessionManager, InternalSessionManager { +/** + * 基于内存的session manager + */ +public class StandardSessionManager implements WxSessionManager, InternalSessionManager { - protected final Logger log = LoggerFactory.getLogger(InMemorySessionManager.class); + protected final Logger log = LoggerFactory.getLogger(StandardSessionManager.class); protected static final StringManager sm = StringManager.getManager(Constants.Package); @@ -128,18 +131,9 @@ public class InMemorySessionManager implements WxSessionManager, InternalSession } - /** - * 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 - */ - protected InternalSession findSession(String id) { + + @Override + public InternalSession findSession(String id) { if (id == null) return (null); @@ -189,12 +183,11 @@ public class InMemorySessionManager implements WxSessionManager, InternalSession return (getNewSession()); } - /** * Get new session class to be used in the doLoad() method. */ protected InternalSession getNewSession() { - return new SessionImpl(this); + return new StandardSession(this); } @@ -312,4 +305,17 @@ public class InMemorySessionManager implements WxSessionManager, InternalSession } + /** + * 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/util/WxMessageInMemoryDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateChecker.java index 2dbfa611..f132ca54 100644 --- 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 @@ -82,6 +82,9 @@ public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChec @Override public boolean isDuplicate(Long wxMsgId) { + if (wxMsgId == null) { + return false; + } checkBackgroundProcessStarted(); Long timestamp = msgId2Timestamp.putIfAbsent(wxMsgId, System.currentTimeMillis()); if (timestamp == null) { 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/session/TestSession.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/session/TestSession.java deleted file mode 100644 index 20e2dd88..00000000 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/session/TestSession.java +++ /dev/null @@ -1,68 +0,0 @@ -package me.chanjar.weixin.common.session; - -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -@Test -public class TestSession { - - @DataProvider - public Object[][] getSessionManager() { - return new Object[][] { - new Object[] { new InMemorySessionManager() } - }; - } - - - @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.assertTrue(session1.equals(session2)); - - WxSession abc1 = sessionManager.getSession("abc1"); - Assert.assertFalse(session1.equals(abc1)); - - WxSession abc1b = sessionManager.getSession("abc1", false); - Assert.assertTrue(abc1.equals(abc1b)); - - WxSession def = sessionManager.getSession("def", false); - Assert.assertNull(def); - } - - @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); - - } - -} diff --git a/weixin-java-common/src/test/resources/testng.xml b/weixin-java-common/src/test/resources/testng.xml index ed4bf6ad..f2222275 100644 --- a/weixin-java-common/src/test/resources/testng.xml +++ b/weixin-java-common/src/test/resources/testng.xml @@ -8,7 +8,7 @@ - + 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 3b838781..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,5 +1,6 @@ package me.chanjar.weixin.cp.api; +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; 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 cf831cf0..366effc8 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,9 +1,6 @@ package me.chanjar.weixin.cp.api; -import me.chanjar.weixin.common.session.InternalSession; -import me.chanjar.weixin.common.session.WxSession; -import me.chanjar.weixin.common.session.InMemorySessionManager; -import me.chanjar.weixin.common.session.WxSessionManager; +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; @@ -69,7 +66,7 @@ public class WxCpMessageRouter { this.wxCpService = wxCpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); - this.sessionManager = new InMemorySessionManager(); + this.sessionManager = new StandardSessionManager(); } /** @@ -152,10 +149,12 @@ public class WxCpMessageRouter { ); } else { res = rule.service(wxMessage); + // 在同步操作结束,session访问结束 + log.trace("End session access after sync operation finish {}", wxMessage.getFromUserName()); + sessionEndAccess(wxMessage); } } - // 告诉session,它已经用不着了 if (futures.size() > 0) { executorService.submit(new Runnable() { @Override @@ -163,19 +162,17 @@ public class WxCpMessageRouter { for (Future future : futures) { try { future.get(); + log.trace("End session access after async operation finish {}", 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); } } - // 在这里session再也不会被使用了 - sessionEndAccess(wxMessage); } }); - } else { - // 在这里session再也不会被使用了 - sessionEndAccess(wxMessage); } return res; } @@ -185,10 +182,12 @@ public class WxCpMessageRouter { * @param wxMessage */ protected void sessionEndAccess(WxCpXmlMessage wxMessage) { - WxSession session = sessionManager.getSession(wxMessage.getFromUserName(), false); + + InternalSession session = ((InternalSessionManager)sessionManager).findSession(wxMessage.getFromUserName()); if (session != null) { - ((InternalSession) session).endAccess(); + session.endAccess(); } + } public static class Rule { 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 e0b624bb..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 @@ -392,7 +392,7 @@ public interface WxCpService { /** *
    * 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法,
-   * WxCpService默认使用的是{@link me.chanjar.weixin.common.session.InMemorySessionManager}
+   * WxCpService默认使用的是{@link me.chanjar.weixin.common.session.StandardSessionManager}
    * 
* @param 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 1afcd7b3..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,7 +12,7 @@ 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.InMemorySessionManager; +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; @@ -66,7 +66,7 @@ public class WxCpServiceImpl implements WxCpService { private int maxRetryTimes = 5; - protected WxSessionManager sessionManager = new InMemorySessionManager(); + protected WxSessionManager sessionManager = new StandardSessionManager(); public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) { try { 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 0053a86b..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,6 +1,7 @@ package me.chanjar.weixin.cp.api; 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; @@ -159,4 +160,137 @@ public class WxCpMessageRouterTest { } + @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-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 c3537380..ccf9f19e 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,9 +1,6 @@ package me.chanjar.weixin.mp.api; -import me.chanjar.weixin.common.session.InternalSession; -import me.chanjar.weixin.common.session.InMemorySessionManager; -import me.chanjar.weixin.common.session.WxSession; -import me.chanjar.weixin.common.session.WxSessionManager; +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; @@ -67,7 +64,7 @@ public class WxMpMessageRouter { this.wxMpService = wxMpService; this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); - this.sessionManager = new InMemorySessionManager(); + this.sessionManager = new StandardSessionManager(); } /** @@ -131,29 +128,31 @@ 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) { futures.add( - executorService.submit(new Runnable() { - public void run() { - rule.service(wxMessage); - } - }) + executorService.submit(new Runnable() { + public void run() { + rule.service(wxMessage); + } + }) ); } else { res = rule.service(wxMessage); + // 在同步操作结束,session访问结束 + log.trace("End session access after sync operation finish {}", wxMessage.getFromUserName()); + sessionEndAccess(wxMessage); } } - // 告诉session,它已经用不着了 if (futures.size() > 0) { executorService.submit(new Runnable() { @Override @@ -161,19 +160,17 @@ public class WxMpMessageRouter { for (Future future : futures) { try { future.get(); + log.trace("End session access after async operation finish {}", 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); } } - // 在这里session再也不会被使用了 - sessionEndAccess(wxMessage); } }); - } else { - // 在这里session再也不会被使用了 - sessionEndAccess(wxMessage); } return res; } @@ -183,10 +180,12 @@ public class WxMpMessageRouter { * @param wxMessage */ protected void sessionEndAccess(WxMpXmlMessage wxMessage) { - WxSession session = sessionManager.getSession(wxMessage.getFromUserName(), false); + + InternalSession session = ((InternalSessionManager)sessionManager).findSession(wxMessage.getFromUserName()); if (session != null) { - ((InternalSession) session).endAccess(); + session.endAccess(); } + } public static class Rule { 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 21a8b94c..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,7 +10,7 @@ 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.InMemorySessionManager; +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; @@ -68,7 +68,7 @@ public class WxMpServiceImpl implements WxMpService { private int maxRetryTimes = 5; - protected WxSessionManager sessionManager = new InMemorySessionManager(); + protected WxSessionManager sessionManager = new StandardSessionManager(); public boolean checkSignature(String timestamp, String nonce, String signature) { try { 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 15506c85..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,6 +1,7 @@ package me.chanjar.weixin.mp.api; 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; @@ -159,4 +160,136 @@ public class WxMpMessageRouterTest { } + @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; + } + + } + } From 3ff9d260f2c664e4a434d21c696927d0d1efd5d5 Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Thu, 22 Jan 2015 13:27:47 +0800 Subject: [PATCH 30/33] =?UTF-8?q?issue=20#23=20=E4=BD=BF=E7=94=A8logback?= =?UTF-8?q?=E4=BD=9C=E4=B8=BA=E5=8D=95=E5=85=83=E6=B5=8B=E8=AF=95=E7=9A=84?= =?UTF-8?q?log=E5=B7=A5=E5=85=B7=EF=BC=8C=E5=B9=B6=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E9=BB=98=E8=AE=A4=E7=9A=84log?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 7 ++++--- .../src/test/resources/logback-test.xml | 16 ++++++++++++++++ .../chanjar/weixin/cp/api/WxCpMessageRouter.java | 4 ++-- .../src/test/resources/logback-test.xml | 16 ++++++++++++++++ .../chanjar/weixin/mp/api/WxMpMessageRouter.java | 4 ++-- .../src/test/resources/logback-test.xml | 16 ++++++++++++++++ 6 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 weixin-java-common/src/test/resources/logback-test.xml create mode 100644 weixin-java-cp/src/test/resources/logback-test.xml create mode 100644 weixin-java-mp/src/test/resources/logback-test.xml diff --git a/pom.xml b/pom.xml index 8ce73cd4..01cadc11 100644 --- a/pom.xml +++ b/pom.xml @@ -43,6 +43,7 @@ true 4.3.5 1.7.10 + 1.1.2 @@ -52,9 +53,9 @@ ${slf4j.version}
- org.slf4j - slf4j-simple - ${slf4j.version} + ch.qos.logback + logback-classic + ${logback.version} test 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-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 366effc8..7ae9d444 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 @@ -150,7 +150,7 @@ public class WxCpMessageRouter { } else { res = rule.service(wxMessage); // 在同步操作结束,session访问结束 - log.trace("End session access after sync operation finish {}", wxMessage.getFromUserName()); + log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUserName()); sessionEndAccess(wxMessage); } } @@ -162,7 +162,7 @@ public class WxCpMessageRouter { for (Future future : futures) { try { future.get(); - log.trace("End session access after async operation finish {}", wxMessage.getFromUserName()); + log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUserName()); // 异步操作结束,session访问结束 sessionEndAccess(wxMessage); } catch (InterruptedException e) { 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-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 ccf9f19e..5c09b48f 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 @@ -148,7 +148,7 @@ public class WxMpMessageRouter { } else { res = rule.service(wxMessage); // 在同步操作结束,session访问结束 - log.trace("End session access after sync operation finish {}", wxMessage.getFromUserName()); + log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUserName()); sessionEndAccess(wxMessage); } } @@ -160,7 +160,7 @@ public class WxMpMessageRouter { for (Future future : futures) { try { future.get(); - log.trace("End session access after async operation finish {}", wxMessage.getFromUserName()); + log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUserName()); // 异步操作结束,session访问结束 sessionEndAccess(wxMessage); } catch (InterruptedException e) { 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 + + + + + + + + + From 3216cb54ddbb2e01e90b2ab8db3249ffb4b72ffb Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Thu, 22 Jan 2015 14:06:15 +0800 Subject: [PATCH 31/33] =?UTF-8?q?issue=20#66=20=E5=AF=B9=E5=8E=BB=E9=87=8D?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=81=9A=E4=BA=86=E4=B8=80=E4=BA=9B=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../util/WxMessageDuplicateChecker.java | 15 ++++++++++++--- .../WxMessageInMemoryDuplicateChecker.java | 10 +++++----- ...WxMessageInMemoryDuplicateCheckerTest.java | 6 +++--- .../weixin/cp/api/WxCpMessageRouter.java | 18 +++++++++++++++++- .../weixin/mp/api/WxMpMessageRouter.java | 19 ++++++++++++++++++- 5 files changed, 55 insertions(+), 13 deletions(-) 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 index 21f21c5d..1abe588b 100644 --- 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 @@ -9,10 +9,19 @@ package me.chanjar.weixin.common.util; public interface WxMessageDuplicateChecker { /** - * 检查消息ID是否重复 - * @param wxMsgId + *

公众号的排重方式

+ * + *

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

+ *

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

+ * + *

企业号的排重方式

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

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

+ * @param messageId messageId需要根据上面讲的方式构造 * @return 如果是重复消息,返回true,否则返回false */ - public boolean isDuplicate(Long wxMsgId); + 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 index f132ca54..ae0e0623 100644 --- 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 @@ -25,7 +25,7 @@ public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChec /** * 消息id->消息时间戳的map */ - private final ConcurrentHashMap msgId2Timestamp = new ConcurrentHashMap(); + private final ConcurrentHashMap msgId2Timestamp = new ConcurrentHashMap(); /** * 后台清理线程是否已经开启 @@ -65,7 +65,7 @@ public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChec while (true) { Thread.sleep(clearPeriod); Long now = System.currentTimeMillis(); - for (Map.Entry entry : msgId2Timestamp.entrySet()) { + for (Map.Entry entry : msgId2Timestamp.entrySet()) { if (now - entry.getValue() > timeToLive) { msgId2Timestamp.entrySet().remove(entry); } @@ -81,12 +81,12 @@ public class WxMessageInMemoryDuplicateChecker implements WxMessageDuplicateChec } @Override - public boolean isDuplicate(Long wxMsgId) { - if (wxMsgId == null) { + public boolean isDuplicate(String messageId) { + if (messageId == null) { return false; } checkBackgroundProcessStarted(); - Long timestamp = msgId2Timestamp.putIfAbsent(wxMsgId, System.currentTimeMillis()); + Long timestamp = msgId2Timestamp.putIfAbsent(messageId, System.currentTimeMillis()); if (timestamp == null) { // 第一次接收到这个消息 return false; 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 index 55e99070..28642c44 100644 --- 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 @@ -12,21 +12,21 @@ public class WxMessageInMemoryDuplicateCheckerTest { // 第一次检查 for (Long msgId : msgIds) { - boolean result = checker.isDuplicate(msgId); + boolean result = checker.isDuplicate(String.valueOf(msgId)); Assert.assertFalse(result); } // 过1秒再检查 Thread.sleep(1000l); for (Long msgId : msgIds) { - boolean result = checker.isDuplicate(msgId); + boolean result = checker.isDuplicate(String.valueOf(msgId)); Assert.assertTrue(result); } // 过1.5秒再检查 Thread.sleep(1500l); for (Long msgId : msgIds) { - boolean result = checker.isDuplicate(msgId); + boolean result = checker.isDuplicate(String.valueOf(msgId)); Assert.assertFalse(result); } 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 7ae9d444..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 @@ -115,7 +115,7 @@ public class WxCpMessageRouter { * @param wxMessage */ public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) { - if (messageDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { + if (isDuplicateMessage(wxMessage)) { // 如果是重复消息,那么就不做处理 return null; } @@ -177,6 +177,22 @@ public class WxCpMessageRouter { 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 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 5c09b48f..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,5 +1,6 @@ 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; @@ -113,7 +114,7 @@ public class WxMpMessageRouter { * @param wxMessage */ public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) { - if (messageDuplicateChecker.isDuplicate(wxMessage.getMsgId())) { + if (isDuplicateMessage(wxMessage)) { // 如果是重复消息,那么就不做处理 return null; } @@ -175,6 +176,22 @@ public class WxMpMessageRouter { 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 From 2427a1c395785cf808b169993ef2f5c362ecf9bb Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Thu, 22 Jan 2015 14:15:27 +0800 Subject: [PATCH 32/33] upgrade to 1.1.0-SNAPSHOT --- pom.xml | 2 +- weixin-java-common/pom.xml | 2 +- weixin-java-cp/pom.xml | 2 +- weixin-java-mp/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index 01cadc11..056d4828 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ 4.0.0 me.chanjar weixin-java-parent - 1.0.7-SNAPSHOT + 1.1.0-SNAPSHOT pom WeiXin Java Tools - Parent 微信公众号、企业号上级POM diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index 34bd0c71..647886fe 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -6,7 +6,7 @@ me.chanjar weixin-java-parent - 1.0.7-SNAPSHOT + 1.1.0-SNAPSHOT weixin-java-common diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml index 01633152..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.7-SNAPSHOT + 1.1.0-SNAPSHOT weixin-java-cp diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml index b3d126f2..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.7-SNAPSHOT + 1.1.0-SNAPSHOT weixin-java-mp WeiXin Java Tools - MP From 7d4e8dc716fc923bd9934163c5d10bf8b9bcef4f Mon Sep 17 00:00:00 2001 From: Daniel Qian Date: Thu, 22 Jan 2015 14:19:38 +0800 Subject: [PATCH 33/33] add LICENSE --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE 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.