| @@ -1,381 +1,387 @@ | |||||
| <?xml version="1.0"?> | |||||
| <project | |||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| 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"> | |||||
| <modelVersion>4.0.0</modelVersion> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>weixin-java-parent</artifactId> | |||||
| <version>2.8.6.BETA</version> | |||||
| <packaging>pom</packaging> | |||||
| <name>WeiXin Java Tools - Parent</name> | |||||
| <description>微信公众号、企业号上级POM</description> | |||||
| <url>https://github.com/wechat-group/weixin-java-tools</url> | |||||
| <licenses> | |||||
| <license> | |||||
| <name>The Apache License, Version 2.0</name> | |||||
| <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> | |||||
| </license> | |||||
| </licenses> | |||||
| <developers> | |||||
| <developer> | |||||
| <name>Daniel Qian</name> | |||||
| <email>chanjarster@gmail.com</email> | |||||
| <url>https://github.com/chanjarster</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>Binary Wang</name> | |||||
| <email>binarywang@gmail.com</email> | |||||
| <url>https://github.com/binarywang</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>gaigeshen</name> | |||||
| <email>gaigeshen@qq.com</email> | |||||
| <url>https://github.com/gaigeshen</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>Liu Mingbo</name> | |||||
| <email>liumingbo2008@gmail.com</email> | |||||
| <url>https://github.com/FirenzesEagle</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>kakotor</name> | |||||
| <email>kakotor@gmail.com</email> | |||||
| <url>https://github.com/kakotor</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>xiong</name> | |||||
| <email>zhaoxiong.tan@gmail.com</email> | |||||
| <url>https://github.com/ZhaoxiongTan</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>LiuJunGuang</name> | |||||
| <email>aimilin@yeah.net</email> | |||||
| <url>https://github.com/aimilin6688</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>Eric.Tsai</name> | |||||
| <email>xiaodong.cai.ks@gmail.com</email> | |||||
| <url>https://github.com/iwareserictsai</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>withinthefog</name> | |||||
| <email>withinthefog@gmail.com</email> | |||||
| <url>https://github.com/withinthefog</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>Keung</name> | |||||
| <email>dongfuqiang1988@163.com</email> | |||||
| <url>https://github.com/johnnytung</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>Jonk</name> | |||||
| <email>aimilin@yeah.net</email> | |||||
| <url>https://github.com/aimilin6688</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>ecoolper</name> | |||||
| <email>crskyp@gmail.com</email> | |||||
| <url>https://github.com/crskyp</url> | |||||
| </developer> | |||||
| </developers> | |||||
| <scm> | |||||
| <connection>scm:git:https://github.com/wechat-group/weixin-java-tools.git</connection> | |||||
| <developerConnection>scm:git:git@github.com:wechat-group/weixin-java-tools.git</developerConnection> | |||||
| <url>https://github.com/wechat-group/weixin-java-tools</url> | |||||
| </scm> | |||||
| <modules> | |||||
| <module>weixin-java-common</module> | |||||
| <module>weixin-java-cp</module> | |||||
| <module>weixin-java-mp</module> | |||||
| <module>weixin-java-pay</module> | |||||
| <module>weixin-java-miniapp</module> | |||||
| <!--module>weixin-java-osgi</module--> | |||||
| </modules> | |||||
| <properties> | |||||
| <maven.compiler.source>1.7</maven.compiler.source> | |||||
| <maven.compiler.target>1.7</maven.compiler.target> | |||||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||||
| <downloadJavadocs>true</downloadJavadocs> | |||||
| <downloadSources>true</downloadSources> | |||||
| <httpclient.version>4.5</httpclient.version> | |||||
| <jetty.version>9.3.0.RC0</jetty.version> | |||||
| </properties> | |||||
| <dependencyManagement> | |||||
| <dependencies> | |||||
| <dependency> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>qrcode-utils</artifactId> | |||||
| <version>1.1</version> | |||||
| </dependency> | |||||
| <!-- 由于jodd-http较新的3.8版本需要jdk8,故而此处采用较低版本 --> | |||||
| <dependency> | |||||
| <groupId>org.jodd</groupId> | |||||
| <artifactId>jodd-http</artifactId> | |||||
| <version>3.7.1</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.squareup.okhttp3</groupId> | |||||
| <artifactId>okhttp</artifactId> | |||||
| <version>3.7.0</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.httpcomponents</groupId> | |||||
| <artifactId>httpclient</artifactId> | |||||
| <version>${httpclient.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.httpcomponents</groupId> | |||||
| <artifactId>httpmime</artifactId> | |||||
| <version>${httpclient.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-codec</groupId> | |||||
| <artifactId>commons-codec</artifactId> | |||||
| <version>1.10</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-io</groupId> | |||||
| <artifactId>commons-io</artifactId> | |||||
| <version>2.5</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.commons</groupId> | |||||
| <artifactId>commons-lang3</artifactId> | |||||
| <version>3.5</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.slf4j</groupId> | |||||
| <artifactId>slf4j-api</artifactId> | |||||
| <version>1.7.24</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.thoughtworks.xstream</groupId> | |||||
| <artifactId>xstream</artifactId> | |||||
| <version>1.4.9</version> | |||||
| </dependency> | |||||
| <!-- 由于guava较新的21.0版本需要jdk8,故而此处采用较低版本 --> | |||||
| <dependency> | |||||
| <groupId>com.google.guava</groupId> | |||||
| <artifactId>guava</artifactId> | |||||
| <version>20.0</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.google.code.gson</groupId> | |||||
| <artifactId>gson</artifactId> | |||||
| <version>2.8.0</version> | |||||
| </dependency> | |||||
| <!-- 测试所用依赖 --> | |||||
| <dependency> | |||||
| <groupId>joda-time</groupId> | |||||
| <artifactId>joda-time</artifactId> | |||||
| <version>2.9.7</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>ch.qos.logback</groupId> | |||||
| <artifactId>logback-classic</artifactId> | |||||
| <version>1.1.11</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.google.inject</groupId> | |||||
| <artifactId>guice</artifactId> | |||||
| <version>3.0</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.testng</groupId> | |||||
| <artifactId>testng</artifactId> | |||||
| <version>6.10</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.mockito</groupId> | |||||
| <artifactId>mockito-all</artifactId> | |||||
| <version>1.9.5</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.eclipse.jetty</groupId> | |||||
| <artifactId>jetty-server</artifactId> | |||||
| <version>${jetty.version}</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.eclipse.jetty</groupId> | |||||
| <artifactId>jetty-servlet</artifactId> | |||||
| <version>${jetty.version}</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>redis.clients</groupId> | |||||
| <artifactId>jedis</artifactId> | |||||
| <version>2.9.0</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.projectlombok</groupId> | |||||
| <artifactId>lombok</artifactId> | |||||
| <version>1.16.18</version> | |||||
| <scope>compile</scope> | |||||
| </dependency> | |||||
| </dependencies> | |||||
| </dependencyManagement> | |||||
| <distributionManagement> | |||||
| <snapshotRepository> | |||||
| <id>ossrh</id> | |||||
| <url>https://oss.sonatype.org/content/repositories/snapshots</url> | |||||
| </snapshotRepository> | |||||
| <repository> | |||||
| <id>ossrh</id> | |||||
| <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> | |||||
| </repository> | |||||
| </distributionManagement> | |||||
| <profiles> | |||||
| <profile> | |||||
| <id>doclint-java8-disable</id> | |||||
| <activation> | |||||
| <jdk>[1.8,)</jdk> | |||||
| </activation> | |||||
| <properties> | |||||
| <javadoc.opts>-Xdoclint:none</javadoc.opts> | |||||
| </properties> | |||||
| </profile> | |||||
| <profile> | |||||
| <id>release</id> | |||||
| <build> | |||||
| <plugins> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-source-plugin</artifactId> | |||||
| <version>2.2.1</version> | |||||
| <executions> | |||||
| <execution> | |||||
| <id>attach-sources</id> | |||||
| <goals> | |||||
| <goal>jar-no-fork</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-javadoc-plugin</artifactId> | |||||
| <version>2.9.1</version> | |||||
| <executions> | |||||
| <execution> | |||||
| <id>attach-javadocs</id> | |||||
| <goals> | |||||
| <goal>jar</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| <configuration> | |||||
| <additionalparam>${javadoc.opts}</additionalparam> | |||||
| <charset>UTF-8</charset> | |||||
| <locale>zh_CN</locale> | |||||
| </configuration> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-gpg-plugin</artifactId> | |||||
| <version>1.6</version> | |||||
| <executions> | |||||
| <execution> | |||||
| <id>sign-artifacts</id> | |||||
| <phase>verify</phase> | |||||
| <goals> | |||||
| <goal>sign</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| </plugin> | |||||
| </plugins> | |||||
| </build> | |||||
| </profile> | |||||
| </profiles> | |||||
| <build> | |||||
| <pluginManagement> | |||||
| <plugins> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-surefire-plugin</artifactId> | |||||
| <version>2.17</version> | |||||
| <configuration> | |||||
| <skip>true</skip> | |||||
| </configuration> | |||||
| </plugin> | |||||
| </plugins> | |||||
| </pluginManagement> | |||||
| <plugins> | |||||
| <plugin> | |||||
| <groupId>org.sonatype.plugins</groupId> | |||||
| <artifactId>nexus-staging-maven-plugin</artifactId> | |||||
| <version>1.6.3</version> | |||||
| <extensions>true</extensions> | |||||
| <configuration> | |||||
| <serverId>ossrh</serverId> | |||||
| <nexusUrl>https://oss.sonatype.org/</nexusUrl> | |||||
| <autoReleaseAfterClose>true</autoReleaseAfterClose> | |||||
| </configuration> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-release-plugin</artifactId> | |||||
| <version>2.5.1</version> | |||||
| <configuration> | |||||
| <autoVersionSubmodules>true</autoVersionSubmodules> | |||||
| <useReleaseProfile>false</useReleaseProfile> | |||||
| <releaseProfiles>release</releaseProfiles> | |||||
| <goals>deploy</goals> | |||||
| </configuration> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-compiler-plugin</artifactId> | |||||
| <version>3.6.0</version> | |||||
| <configuration> | |||||
| <encoding>UTF-8</encoding> | |||||
| </configuration> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-checkstyle-plugin</artifactId> | |||||
| <version>2.17</version> | |||||
| <configuration> | |||||
| <configLocation>quality-checks/google_checks.xml</configLocation> | |||||
| <includeTestSourceDirectory>true</includeTestSourceDirectory> | |||||
| <consoleOutput>true</consoleOutput> | |||||
| <failsOnError>true</failsOnError> | |||||
| </configuration> | |||||
| <executions> | |||||
| <execution> | |||||
| <phase>verify</phase> | |||||
| <goals> | |||||
| <goal>check</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| </plugin> | |||||
| </plugins> | |||||
| </build> | |||||
| </project> | |||||
| <?xml version="1.0"?> | |||||
| <project | |||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| 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"> | |||||
| <modelVersion>4.0.0</modelVersion> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>weixin-java-parent</artifactId> | |||||
| <version>2.8.6.BETA</version> | |||||
| <packaging>pom</packaging> | |||||
| <name>WeiXin Java Tools - Parent</name> | |||||
| <description>微信公众号、企业号上级POM</description> | |||||
| <url>https://github.com/wechat-group/weixin-java-tools</url> | |||||
| <licenses> | |||||
| <license> | |||||
| <name>The Apache License, Version 2.0</name> | |||||
| <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url> | |||||
| </license> | |||||
| </licenses> | |||||
| <developers> | |||||
| <developer> | |||||
| <name>Daniel Qian</name> | |||||
| <email>chanjarster@gmail.com</email> | |||||
| <url>https://github.com/chanjarster</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>Binary Wang</name> | |||||
| <email>binarywang@gmail.com</email> | |||||
| <url>https://github.com/binarywang</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>gaigeshen</name> | |||||
| <email>gaigeshen@qq.com</email> | |||||
| <url>https://github.com/gaigeshen</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>Liu Mingbo</name> | |||||
| <email>liumingbo2008@gmail.com</email> | |||||
| <url>https://github.com/FirenzesEagle</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>kakotor</name> | |||||
| <email>kakotor@gmail.com</email> | |||||
| <url>https://github.com/kakotor</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>xiong</name> | |||||
| <email>zhaoxiong.tan@gmail.com</email> | |||||
| <url>https://github.com/ZhaoxiongTan</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>LiuJunGuang</name> | |||||
| <email>aimilin@yeah.net</email> | |||||
| <url>https://github.com/aimilin6688</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>Eric.Tsai</name> | |||||
| <email>xiaodong.cai.ks@gmail.com</email> | |||||
| <url>https://github.com/iwareserictsai</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>withinthefog</name> | |||||
| <email>withinthefog@gmail.com</email> | |||||
| <url>https://github.com/withinthefog</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>Keung</name> | |||||
| <email>dongfuqiang1988@163.com</email> | |||||
| <url>https://github.com/johnnytung</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>Jonk</name> | |||||
| <email>aimilin@yeah.net</email> | |||||
| <url>https://github.com/aimilin6688</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>ecoolper</name> | |||||
| <email>crskyp@gmail.com</email> | |||||
| <url>https://github.com/crskyp</url> | |||||
| </developer> | |||||
| <developer> | |||||
| <name>007</name> | |||||
| <email>007gzs@gmail.com</email> | |||||
| <url>https://github.com/007gzs</url> | |||||
| </developer> | |||||
| </developers> | |||||
| <scm> | |||||
| <connection>scm:git:https://github.com/wechat-group/weixin-java-tools.git</connection> | |||||
| <developerConnection>scm:git:git@github.com:wechat-group/weixin-java-tools.git</developerConnection> | |||||
| <url>https://github.com/wechat-group/weixin-java-tools</url> | |||||
| </scm> | |||||
| <modules> | |||||
| <module>weixin-java-common</module> | |||||
| <module>weixin-java-cp</module> | |||||
| <module>weixin-java-mp</module> | |||||
| <module>weixin-java-pay</module> | |||||
| <module>weixin-java-miniapp</module> | |||||
| <module>weixin-java-open</module> | |||||
| <!--module>weixin-java-osgi</module--> | |||||
| </modules> | |||||
| <properties> | |||||
| <maven.compiler.source>1.7</maven.compiler.source> | |||||
| <maven.compiler.target>1.7</maven.compiler.target> | |||||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||||
| <downloadJavadocs>true</downloadJavadocs> | |||||
| <downloadSources>true</downloadSources> | |||||
| <httpclient.version>4.5</httpclient.version> | |||||
| <jetty.version>9.3.0.RC0</jetty.version> | |||||
| </properties> | |||||
| <dependencyManagement> | |||||
| <dependencies> | |||||
| <dependency> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>qrcode-utils</artifactId> | |||||
| <version>1.1</version> | |||||
| </dependency> | |||||
| <!-- 由于jodd-http较新的3.8版本需要jdk8,故而此处采用较低版本 --> | |||||
| <dependency> | |||||
| <groupId>org.jodd</groupId> | |||||
| <artifactId>jodd-http</artifactId> | |||||
| <version>3.7.1</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.squareup.okhttp3</groupId> | |||||
| <artifactId>okhttp</artifactId> | |||||
| <version>3.7.0</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.httpcomponents</groupId> | |||||
| <artifactId>httpclient</artifactId> | |||||
| <version>${httpclient.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.httpcomponents</groupId> | |||||
| <artifactId>httpmime</artifactId> | |||||
| <version>${httpclient.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-codec</groupId> | |||||
| <artifactId>commons-codec</artifactId> | |||||
| <version>1.10</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>commons-io</groupId> | |||||
| <artifactId>commons-io</artifactId> | |||||
| <version>2.5</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.apache.commons</groupId> | |||||
| <artifactId>commons-lang3</artifactId> | |||||
| <version>3.5</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.slf4j</groupId> | |||||
| <artifactId>slf4j-api</artifactId> | |||||
| <version>1.7.24</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.thoughtworks.xstream</groupId> | |||||
| <artifactId>xstream</artifactId> | |||||
| <version>1.4.9</version> | |||||
| </dependency> | |||||
| <!-- 由于guava较新的21.0版本需要jdk8,故而此处采用较低版本 --> | |||||
| <dependency> | |||||
| <groupId>com.google.guava</groupId> | |||||
| <artifactId>guava</artifactId> | |||||
| <version>20.0</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.google.code.gson</groupId> | |||||
| <artifactId>gson</artifactId> | |||||
| <version>2.8.0</version> | |||||
| </dependency> | |||||
| <!-- 测试所用依赖 --> | |||||
| <dependency> | |||||
| <groupId>joda-time</groupId> | |||||
| <artifactId>joda-time</artifactId> | |||||
| <version>2.9.7</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>ch.qos.logback</groupId> | |||||
| <artifactId>logback-classic</artifactId> | |||||
| <version>1.1.11</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.google.inject</groupId> | |||||
| <artifactId>guice</artifactId> | |||||
| <version>3.0</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.testng</groupId> | |||||
| <artifactId>testng</artifactId> | |||||
| <version>6.10</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.mockito</groupId> | |||||
| <artifactId>mockito-all</artifactId> | |||||
| <version>1.9.5</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.eclipse.jetty</groupId> | |||||
| <artifactId>jetty-server</artifactId> | |||||
| <version>${jetty.version}</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.eclipse.jetty</groupId> | |||||
| <artifactId>jetty-servlet</artifactId> | |||||
| <version>${jetty.version}</version> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>redis.clients</groupId> | |||||
| <artifactId>jedis</artifactId> | |||||
| <version>2.9.0</version> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.projectlombok</groupId> | |||||
| <artifactId>lombok</artifactId> | |||||
| <version>1.16.18</version> | |||||
| <scope>compile</scope> | |||||
| </dependency> | |||||
| </dependencies> | |||||
| </dependencyManagement> | |||||
| <distributionManagement> | |||||
| <snapshotRepository> | |||||
| <id>ossrh</id> | |||||
| <url>https://oss.sonatype.org/content/repositories/snapshots</url> | |||||
| </snapshotRepository> | |||||
| <repository> | |||||
| <id>ossrh</id> | |||||
| <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url> | |||||
| </repository> | |||||
| </distributionManagement> | |||||
| <profiles> | |||||
| <profile> | |||||
| <id>doclint-java8-disable</id> | |||||
| <activation> | |||||
| <jdk>[1.8,)</jdk> | |||||
| </activation> | |||||
| <properties> | |||||
| <javadoc.opts>-Xdoclint:none</javadoc.opts> | |||||
| </properties> | |||||
| </profile> | |||||
| <profile> | |||||
| <id>release</id> | |||||
| <build> | |||||
| <plugins> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-source-plugin</artifactId> | |||||
| <version>2.2.1</version> | |||||
| <executions> | |||||
| <execution> | |||||
| <id>attach-sources</id> | |||||
| <goals> | |||||
| <goal>jar-no-fork</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-javadoc-plugin</artifactId> | |||||
| <version>2.9.1</version> | |||||
| <executions> | |||||
| <execution> | |||||
| <id>attach-javadocs</id> | |||||
| <goals> | |||||
| <goal>jar</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| <configuration> | |||||
| <additionalparam>${javadoc.opts}</additionalparam> | |||||
| <charset>UTF-8</charset> | |||||
| <locale>zh_CN</locale> | |||||
| </configuration> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-gpg-plugin</artifactId> | |||||
| <version>1.6</version> | |||||
| <executions> | |||||
| <execution> | |||||
| <id>sign-artifacts</id> | |||||
| <phase>verify</phase> | |||||
| <goals> | |||||
| <goal>sign</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| </plugin> | |||||
| </plugins> | |||||
| </build> | |||||
| </profile> | |||||
| </profiles> | |||||
| <build> | |||||
| <pluginManagement> | |||||
| <plugins> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-surefire-plugin</artifactId> | |||||
| <version>2.17</version> | |||||
| <configuration> | |||||
| <skip>true</skip> | |||||
| </configuration> | |||||
| </plugin> | |||||
| </plugins> | |||||
| </pluginManagement> | |||||
| <plugins> | |||||
| <plugin> | |||||
| <groupId>org.sonatype.plugins</groupId> | |||||
| <artifactId>nexus-staging-maven-plugin</artifactId> | |||||
| <version>1.6.3</version> | |||||
| <extensions>true</extensions> | |||||
| <configuration> | |||||
| <serverId>ossrh</serverId> | |||||
| <nexusUrl>https://oss.sonatype.org/</nexusUrl> | |||||
| <autoReleaseAfterClose>true</autoReleaseAfterClose> | |||||
| </configuration> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-release-plugin</artifactId> | |||||
| <version>2.5.1</version> | |||||
| <configuration> | |||||
| <autoVersionSubmodules>true</autoVersionSubmodules> | |||||
| <useReleaseProfile>false</useReleaseProfile> | |||||
| <releaseProfiles>release</releaseProfiles> | |||||
| <goals>deploy</goals> | |||||
| </configuration> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-compiler-plugin</artifactId> | |||||
| <version>3.6.0</version> | |||||
| <configuration> | |||||
| <encoding>UTF-8</encoding> | |||||
| </configuration> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-checkstyle-plugin</artifactId> | |||||
| <version>2.17</version> | |||||
| <configuration> | |||||
| <configLocation>quality-checks/google_checks.xml</configLocation> | |||||
| <includeTestSourceDirectory>true</includeTestSourceDirectory> | |||||
| <consoleOutput>true</consoleOutput> | |||||
| <failsOnError>true</failsOnError> | |||||
| </configuration> | |||||
| <executions> | |||||
| <execution> | |||||
| <phase>verify</phase> | |||||
| <goals> | |||||
| <goal>check</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| </plugin> | |||||
| </plugins> | |||||
| </build> | |||||
| </project> | |||||
| @@ -0,0 +1,62 @@ | |||||
| 消息机制未实现,下面为通知回调中设置的代码部分 | |||||
| ``` | |||||
| @RestController | |||||
| @RequestMapping("notify") | |||||
| public class NotifyController extends WechatThridBaseController { | |||||
| @Autowired | |||||
| protected WxOpenServiceDemo wxOpenService; | |||||
| @RequestMapping("receive_ticket") | |||||
| public Object receiveTicket(@RequestBody(required = false) String requestBody, @RequestParam("timestamp") String timestamp, | |||||
| @RequestParam("nonce") String nonce, @RequestParam("signature") String signature, | |||||
| @RequestParam(name = "encrypt_type", required = false) String encType, | |||||
| @RequestParam(name = "msg_signature", required = false) String msgSignature) { | |||||
| this.logger.info( | |||||
| "\n接收微信请求:[signature=[{}], encType=[{}], msgSignature=[{}]," | |||||
| + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", | |||||
| signature, encType, msgSignature, timestamp, nonce, requestBody); | |||||
| if (!StringUtils.equalsIgnoreCase("aes", encType) || !wxOpenService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) { | |||||
| throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); | |||||
| } | |||||
| // aes加密的消息 | |||||
| WxOpenXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedXml(requestBody, wxOpenService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature); | |||||
| this.logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); | |||||
| String out = null; | |||||
| try { | |||||
| out = wxOpenService.getWxOpenComponentService().route(inMessage); | |||||
| } catch (WxErrorException e) { | |||||
| throw new ResponseException(ErrorCodeEnum.ERROR, e); | |||||
| } | |||||
| this.logger.debug("\n组装回复信息:{}", out); | |||||
| return out; | |||||
| } | |||||
| @RequestMapping("{appId}/callback") | |||||
| public Object callback(@RequestBody(required = false)String requestBody, | |||||
| @PathVariable ("appId") String appId, | |||||
| @RequestParam("signature") String signature, | |||||
| @RequestParam("timestamp") String timestamp, | |||||
| @RequestParam("nonce") String nonce, | |||||
| @RequestParam("openid") String openid, | |||||
| @RequestParam("encrypt_type") String encType, | |||||
| @RequestParam("msg_signature") String msgSignature) { | |||||
| this.logger.info( | |||||
| "\n接收微信请求:[appId=[{}], openid=[{}], signature=[{}], encType=[{}], msgSignature=[{}]," | |||||
| + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", | |||||
| appId, openid, signature, encType, msgSignature, timestamp, nonce, requestBody); | |||||
| logger.info("query:"+getHttpServletRequest().getQueryString()+"\nbody:"+requestBody); | |||||
| if (!StringUtils.equalsIgnoreCase("aes", encType) || !wxOpenService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) { | |||||
| throw new IllegalArgumentException("非法请求,可能属于伪造的请求!"); | |||||
| } | |||||
| String out = ""; | |||||
| // aes加密的消息 | |||||
| WxMpXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedMpXml(requestBody, wxOpenService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature); | |||||
| this.logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString()); | |||||
| //wxOpenService.getWxOpenComponentService().getWxMpServiceByAppid(appId); | |||||
| return out; | |||||
| } | |||||
| } | |||||
| ``` | |||||
| @@ -0,0 +1,97 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <project | |||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| 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"> | |||||
| <modelVersion>4.0.0</modelVersion> | |||||
| <parent> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>weixin-java-parent</artifactId> | |||||
| <version>2.8.6.BETA</version> | |||||
| </parent> | |||||
| <artifactId>weixin-java-open</artifactId> | |||||
| <name>WeiXin Java Tools - Open</name> | |||||
| <description>微信开放平台Java SDK</description> | |||||
| <developers> | |||||
| <developer> | |||||
| <name>007</name> | |||||
| <email>007gzs@gmail.com</email> | |||||
| <url>https://github.com/007gzs</url> | |||||
| </developer> | |||||
| </developers> | |||||
| <dependencies> | |||||
| <dependency> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>weixin-java-common</artifactId> | |||||
| <version>${project.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>weixin-java-mp</artifactId> | |||||
| <version>${project.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.jodd</groupId> | |||||
| <artifactId>jodd-http</artifactId> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.squareup.okhttp3</groupId> | |||||
| <artifactId>okhttp</artifactId> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.testng</groupId> | |||||
| <artifactId>testng</artifactId> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.google.inject</groupId> | |||||
| <artifactId>guice</artifactId> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.eclipse.jetty</groupId> | |||||
| <artifactId>jetty-server</artifactId> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.eclipse.jetty</groupId> | |||||
| <artifactId>jetty-servlet</artifactId> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>joda-time</groupId> | |||||
| <artifactId>joda-time</artifactId> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>redis.clients</groupId> | |||||
| <artifactId>jedis</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>ch.qos.logback</groupId> | |||||
| <artifactId>logback-classic</artifactId> | |||||
| <scope>test</scope> | |||||
| </dependency> | |||||
| </dependencies> | |||||
| <build> | |||||
| <plugins> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-surefire-plugin</artifactId> | |||||
| <configuration> | |||||
| <suiteXmlFiles> | |||||
| <suiteXmlFile>src/test/resources/testng.xml</suiteXmlFile> | |||||
| </suiteXmlFiles> | |||||
| </configuration> | |||||
| </plugin> | |||||
| </plugins> | |||||
| </build> | |||||
| </project> | |||||
| @@ -0,0 +1,82 @@ | |||||
| package me.chanjar.weixin.open.api; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.mp.api.WxMpService; | |||||
| import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | |||||
| import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerOptionResult; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public interface WxOpenComponentService { | |||||
| String API_COMPONENT_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/component/api_component_token"; | |||||
| String API_CREATE_PREAUTHCODE_URL = "https://api.weixin.qq.com/cgi-bin/component/api_create_preauthcode"; | |||||
| String API_QUERY_AUTH_URL = "https://api.weixin.qq.com/cgi-bin/component/api_query_auth"; | |||||
| String API_AUTHORIZER_TOKEN_URL = "https://api.weixin.qq.com /cgi-bin/component/api_authorizer_token"; | |||||
| String API_GET_AUTHORIZER_INFO_URL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_info"; | |||||
| String API_GET_AUTHORIZER_OPTION_URL = "https://api.weixin.qq.com/cgi-bin/component/api_get_authorizer_option"; | |||||
| String API_SET_AUTHORIZER_OPTION_URL = "https://api.weixin.qq.com/cgi-bin/component/ api_set_authorizer_option"; | |||||
| String COMPONENT_LOGIN_PAGE_URL = "https://mp.weixin.qq.com/cgi-bin/componentloginpage?component_appid=%s&pre_auth_code=%s&redirect_uri=%s"; | |||||
| String CONNECT_OAUTH2_AUTHORIZE_URL = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s&component_appid=%s#wechat_redirect"; | |||||
| /** | |||||
| * 用code换取oauth2的access token | |||||
| */ | |||||
| String OAUTH2_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&code=%s&grant_type=authorization_code&component_appid=%s"; | |||||
| /** | |||||
| * 刷新oauth2的access token | |||||
| */ | |||||
| String OAUTH2_REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s&component_appid==%s"; | |||||
| WxMpService getWxMpServiceByAppid(String appid); | |||||
| WxOpenConfigStorage getWxOpenConfigStorage(); | |||||
| boolean checkSignature(String timestamp, String nonce, String signature); | |||||
| String getComponentAccessToken(boolean forceRefresh) throws WxErrorException; | |||||
| /** | |||||
| * 获取用户授权页URL(来路URL和成功跳转URL 的域名都需要为三方平台设置的 登录授权的发起页域名) | |||||
| */ | |||||
| String getPreAuthUrl(String redirectURI) throws WxErrorException; | |||||
| String route(WxOpenXmlMessage wxMessage) throws WxErrorException; | |||||
| /** | |||||
| * 使用授权码换取公众号或小程序的接口调用凭据和授权信息 | |||||
| */ | |||||
| WxOpenQueryAuthResult getQueryAuth(String authorizationCode) throws WxErrorException; | |||||
| /** | |||||
| * 获取授权方的帐号基本信息 | |||||
| */ | |||||
| WxOpenAuthorizerInfoResult getAuthorizerInfo(String authorizerAppid) throws WxErrorException; | |||||
| /** | |||||
| * 获取授权方的选项设置信息 | |||||
| */ | |||||
| WxOpenAuthorizerOptionResult getAuthorizerOption(String authorizerAppid, String optionName) throws WxErrorException; | |||||
| /** | |||||
| * 设置授权方的选项信息 | |||||
| */ | |||||
| WxError setAuthorizerOption(String authorizerAppid, String optionName, String optionValue) throws WxErrorException; | |||||
| String getAuthorizerAccessToken(String appid, boolean forceRefresh) throws WxErrorException; | |||||
| WxMpOAuth2AccessToken oauth2getAccessToken(String appid, String code) throws WxErrorException; | |||||
| boolean checkSignature(String appId, String timestamp, String nonce, String signature); | |||||
| WxMpOAuth2AccessToken oauth2refreshAccessToken(String appid, String refreshToken) throws WxErrorException; | |||||
| String oauth2buildAuthorizationUrl(String appid, String redirectURI, String scope, String state); | |||||
| } | |||||
| @@ -0,0 +1,106 @@ | |||||
| package me.chanjar.weixin.open.api; | |||||
| import me.chanjar.weixin.mp.api.WxMpConfigStorage; | |||||
| import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; | |||||
| import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public interface WxOpenConfigStorage { | |||||
| void setComponentAppId(String componentAppId); | |||||
| void setComponentAppSecret(String componentAppSecret); | |||||
| void setComponentToken(String componentToken); | |||||
| void setComponentAesKey(String componentAesKey); | |||||
| String getComponentAppId(); | |||||
| String getComponentAppSecret(); | |||||
| String getComponentToken(); | |||||
| String getComponentAesKey(); | |||||
| String getComponentVerifyTicket(); | |||||
| void setComponentVerifyTicket(String componentVerifyTicket); | |||||
| String getComponentAccessToken(); | |||||
| boolean isComponentAccessTokenExpired(); | |||||
| void updateComponentAccessTokent(WxOpenComponentAccessToken componentAccessToken); | |||||
| WxMpConfigStorage getWxMpConfigStorage(String appId); | |||||
| /** | |||||
| * 应该是线程安全的 | |||||
| * | |||||
| * @param componentAccessToken 新的accessToken值 | |||||
| * @param expiresInSeconds 过期时间,以秒为单位 | |||||
| */ | |||||
| void updateComponentAccessTokent(String componentAccessToken, int expiresInSeconds); | |||||
| /** | |||||
| * 是否自动刷新token | |||||
| */ | |||||
| boolean autoRefreshToken(); | |||||
| String getAuthorizerRefreshToken(String appId); | |||||
| void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken); | |||||
| String getAuthorizerAccessToken(String appId); | |||||
| boolean isAuthorizerAccessTokenExpired(String appId); | |||||
| /** | |||||
| * 强制将access token过期掉 | |||||
| */ | |||||
| void expireAuthorizerAccessToken(String appId); | |||||
| /** | |||||
| * 应该是线程安全的 | |||||
| * | |||||
| * @param authorizerAccessToken 要更新的WxAccessToken对象 | |||||
| */ | |||||
| void updateAuthorizerAccessToken(String appId, WxOpenAuthorizerAccessToken authorizerAccessToken); | |||||
| /** | |||||
| * 应该是线程安全的 | |||||
| * | |||||
| * @param authorizerAccessToken 新的accessToken值 | |||||
| * @param expiresInSeconds 过期时间,以秒为单位 | |||||
| */ | |||||
| void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds); | |||||
| String getJsapiTicket(String appId); | |||||
| boolean isJsapiTicketExpired(String appId); | |||||
| /** | |||||
| * 强制将jsapi ticket过期掉 | |||||
| */ | |||||
| void expireJsapiTicket(String appId); | |||||
| /** | |||||
| * 应该是线程安全的 | |||||
| * | |||||
| * @param jsapiTicket 新的jsapi ticket值 | |||||
| * @param expiresInSeconds 过期时间,以秒为单位 | |||||
| */ | |||||
| void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds); | |||||
| String getCardApiTicket(String appId); | |||||
| boolean isCardApiTicketExpired(String appId); | |||||
| /** | |||||
| * 强制将卡券api ticket过期掉 | |||||
| */ | |||||
| void expireCardApiTicket(String appId); | |||||
| /** | |||||
| * 应该是线程安全的 | |||||
| * | |||||
| * @param cardApiTicket 新的cardApi ticket值 | |||||
| * @param expiresInSeconds 过期时间,以秒为单位 | |||||
| */ | |||||
| void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds); | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| package me.chanjar.weixin.open.api; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public interface WxOpenService { | |||||
| WxOpenComponentService getWxOpenComponentService(); | |||||
| WxOpenConfigStorage getWxOpenConfigStorage(); | |||||
| void setWxOpenConfigStorage(WxOpenConfigStorage wxOpenConfigStorage); | |||||
| /** | |||||
| * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求 | |||||
| */ | |||||
| String get(String url, String queryParam) throws WxErrorException; | |||||
| /** | |||||
| * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求 | |||||
| */ | |||||
| String post(String url, String postData) throws WxErrorException; | |||||
| } | |||||
| @@ -0,0 +1,218 @@ | |||||
| package me.chanjar.weixin.open.api.impl; | |||||
| import com.google.gson.JsonObject; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.crypto.SHA1; | |||||
| import me.chanjar.weixin.common.util.http.URIUtil; | |||||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||||
| import me.chanjar.weixin.mp.api.WxMpService; | |||||
| import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | |||||
| import me.chanjar.weixin.open.api.WxOpenComponentService; | |||||
| import me.chanjar.weixin.open.api.WxOpenConfigStorage; | |||||
| import me.chanjar.weixin.open.api.WxOpenService; | |||||
| import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; | |||||
| import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; | |||||
| import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerOptionResult; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult; | |||||
| import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.util.Hashtable; | |||||
| import java.util.Map; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenComponentServiceImpl implements WxOpenComponentService { | |||||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | |||||
| private WxOpenService wxOpenService; | |||||
| private static final Map<String, WxMpService> wxOpenMpServiceMap = new Hashtable<>(); | |||||
| public WxOpenComponentServiceImpl(WxOpenService wxOpenService) { | |||||
| this.wxOpenService = wxOpenService; | |||||
| } | |||||
| @Override | |||||
| public WxMpService getWxMpServiceByAppid(String appId) { | |||||
| WxMpService wxMpService = wxOpenMpServiceMap.get(appId); | |||||
| if (wxMpService == null) { | |||||
| synchronized (wxOpenMpServiceMap) { | |||||
| wxMpService = wxOpenMpServiceMap.get(appId); | |||||
| if (wxMpService == null) { | |||||
| wxMpService = new WxOpenMpServiceImpl(this, appId, getWxOpenConfigStorage().getWxMpConfigStorage(appId)); | |||||
| wxOpenMpServiceMap.put(appId, wxMpService); | |||||
| } | |||||
| } | |||||
| } | |||||
| return wxMpService; | |||||
| } | |||||
| public WxOpenService getWxOpenService() { | |||||
| return wxOpenService; | |||||
| } | |||||
| @Override | |||||
| public WxOpenConfigStorage getWxOpenConfigStorage() { | |||||
| return wxOpenService.getWxOpenConfigStorage(); | |||||
| } | |||||
| @Override | |||||
| public boolean checkSignature(String timestamp, String nonce, String signature) { | |||||
| try { | |||||
| return SHA1.gen(getWxOpenConfigStorage().getComponentToken(), timestamp, nonce) | |||||
| .equals(signature); | |||||
| } catch (Exception e) { | |||||
| this.log.error("Checking signature failed, and the reason is :" + e.getMessage()); | |||||
| return false; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public String getComponentAccessToken(boolean forceRefresh) throws WxErrorException { | |||||
| if (this.getWxOpenConfigStorage().isComponentAccessTokenExpired() || forceRefresh) { | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||||
| jsonObject.addProperty("component_appsecret", getWxOpenConfigStorage().getComponentAppSecret()); | |||||
| jsonObject.addProperty("component_verify_ticket", getWxOpenConfigStorage().getComponentVerifyTicket()); | |||||
| String responseContent = this.getWxOpenService().post(API_COMPONENT_TOKEN_URL, jsonObject.toString()); | |||||
| WxOpenComponentAccessToken componentAccessToken = WxOpenComponentAccessToken.fromJson(responseContent); | |||||
| getWxOpenConfigStorage().updateComponentAccessTokent(componentAccessToken); | |||||
| } | |||||
| return this.getWxOpenConfigStorage().getComponentAccessToken(); | |||||
| } | |||||
| private String post(String uri, String postData) throws WxErrorException { | |||||
| String componentAccessToken = getComponentAccessToken(false); | |||||
| String uriWithComponentAccessToken = uri + (uri.contains("?") ? "&" : "?") + "component_access_token=" + componentAccessToken; | |||||
| return getWxOpenService().post(uriWithComponentAccessToken, postData); | |||||
| } | |||||
| private String get(String uri) throws WxErrorException { | |||||
| String componentAccessToken = getComponentAccessToken(false); | |||||
| String uriWithComponentAccessToken = uri + (uri.contains("?") ? "&" : "?") + "component_access_token=" + componentAccessToken; | |||||
| return getWxOpenService().get(uriWithComponentAccessToken, null); | |||||
| } | |||||
| @Override | |||||
| public String getPreAuthUrl(String redirectURI) throws WxErrorException { | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||||
| String responseContent = post(API_CREATE_PREAUTHCODE_URL, jsonObject.toString()); | |||||
| jsonObject = WxGsonBuilder.create().fromJson(responseContent, JsonObject.class); | |||||
| return String.format(COMPONENT_LOGIN_PAGE_URL, getWxOpenConfigStorage().getComponentAppId(), jsonObject.get("pre_auth_code").getAsString(), URIUtil.encodeURIComponent(redirectURI)); | |||||
| } | |||||
| @Override | |||||
| public String route(final WxOpenXmlMessage wxMessage) throws WxErrorException { | |||||
| if (wxMessage == null) { | |||||
| throw new NullPointerException("message is empty"); | |||||
| } | |||||
| if (StringUtils.equalsIgnoreCase(wxMessage.getInfoType(), "component_verify_ticket")) { | |||||
| getWxOpenConfigStorage().setComponentVerifyTicket(wxMessage.getComponentVerifyTicket()); | |||||
| return "success"; | |||||
| } | |||||
| //新增、跟新授权 | |||||
| if (StringUtils.equalsAnyIgnoreCase(wxMessage.getInfoType(), "authorized", "updateauthorized")) { | |||||
| WxOpenQueryAuthResult queryAuth = wxOpenService.getWxOpenComponentService().getQueryAuth(wxMessage.getAuthorizationCode()); | |||||
| if (queryAuth == null || queryAuth.getAuthorizationInfo() == null || queryAuth.getAuthorizationInfo().getAuthorizerAppid() == null) { | |||||
| throw new NullPointerException("getQueryAuth"); | |||||
| } | |||||
| WxOpenAuthorizationInfo authorizationInfo = queryAuth.getAuthorizationInfo(); | |||||
| if (authorizationInfo.getAuthorizerAccessToken() != null) { | |||||
| getWxOpenConfigStorage().updateAuthorizerAccessToken(authorizationInfo.getAuthorizerAppid(), | |||||
| authorizationInfo.getAuthorizerAccessToken(), authorizationInfo.getExpiresIn()); | |||||
| } | |||||
| if (authorizationInfo.getAuthorizerRefreshToken() != null) { | |||||
| getWxOpenConfigStorage().setAuthorizerRefreshToken(authorizationInfo.getAuthorizerAppid(), authorizationInfo.getAuthorizerRefreshToken()); | |||||
| } | |||||
| return "success"; | |||||
| } | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public WxOpenQueryAuthResult getQueryAuth(String authorizationCode) throws WxErrorException { | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||||
| jsonObject.addProperty("authorization_code", authorizationCode); | |||||
| String responseContent = post(API_QUERY_AUTH_URL, jsonObject.toString()); | |||||
| return WxOpenGsonBuilder.create().fromJson(responseContent, WxOpenQueryAuthResult.class); | |||||
| } | |||||
| @Override | |||||
| public WxOpenAuthorizerInfoResult getAuthorizerInfo(String authorizerAppid) throws WxErrorException { | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||||
| jsonObject.addProperty("authorizer_appid", authorizerAppid); | |||||
| String responseContent = post(API_GET_AUTHORIZER_INFO_URL, jsonObject.toString()); | |||||
| return WxOpenGsonBuilder.create().fromJson(responseContent, WxOpenAuthorizerInfoResult.class); | |||||
| } | |||||
| @Override | |||||
| public WxOpenAuthorizerOptionResult getAuthorizerOption(String authorizerAppid, String optionName) throws WxErrorException { | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||||
| jsonObject.addProperty("authorizer_appid", authorizerAppid); | |||||
| jsonObject.addProperty("option_name", optionName); | |||||
| String responseContent = post(API_GET_AUTHORIZER_OPTION_URL, jsonObject.toString()); | |||||
| return WxOpenGsonBuilder.create().fromJson(responseContent, WxOpenAuthorizerOptionResult.class); | |||||
| } | |||||
| @Override | |||||
| public WxError setAuthorizerOption(String authorizerAppid, String optionName, String optionValue) throws WxErrorException { | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||||
| jsonObject.addProperty("authorizer_appid", authorizerAppid); | |||||
| jsonObject.addProperty("option_name", optionName); | |||||
| jsonObject.addProperty("option_value", optionValue); | |||||
| String responseContent = post(API_SET_AUTHORIZER_OPTION_URL, jsonObject.toString()); | |||||
| return WxGsonBuilder.create().fromJson(responseContent, WxError.class); | |||||
| } | |||||
| @Override | |||||
| public String getAuthorizerAccessToken(String appId, boolean forceRefresh) throws WxErrorException { | |||||
| if (this.getWxOpenConfigStorage().isAuthorizerAccessTokenExpired(appId) || forceRefresh) { | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("component_appid", getWxOpenConfigStorage().getComponentAppId()); | |||||
| jsonObject.addProperty("authorizer_appid", appId); | |||||
| jsonObject.addProperty("authorizer_refresh_token", getWxOpenConfigStorage().getAuthorizerRefreshToken(appId)); | |||||
| String responseContent = post(API_AUTHORIZER_TOKEN_URL, jsonObject.toString()); | |||||
| WxOpenAuthorizerAccessToken wxOpenAuthorizerAccessToken = WxOpenAuthorizerAccessToken.fromJson(responseContent); | |||||
| getWxOpenConfigStorage().updateAuthorizerAccessToken(appId, wxOpenAuthorizerAccessToken); | |||||
| } | |||||
| return this.getWxOpenConfigStorage().getAuthorizerAccessToken(appId); | |||||
| } | |||||
| @Override | |||||
| public WxMpOAuth2AccessToken oauth2getAccessToken(String appId, String code) throws WxErrorException { | |||||
| String url = String.format(OAUTH2_ACCESS_TOKEN_URL, appId, code, getWxOpenConfigStorage().getComponentAppId()); | |||||
| String responseContent = get(url); | |||||
| return WxMpOAuth2AccessToken.fromJson(responseContent); | |||||
| } | |||||
| @Override | |||||
| public boolean checkSignature(String appid, String timestamp, String nonce, String signature) { | |||||
| return false; | |||||
| } | |||||
| @Override | |||||
| public WxMpOAuth2AccessToken oauth2refreshAccessToken(String appId, String refreshToken) throws WxErrorException { | |||||
| String url = String.format(OAUTH2_REFRESH_TOKEN_URL, appId, refreshToken, getWxOpenConfigStorage().getComponentAppId()); | |||||
| String responseContent = get(url); | |||||
| return WxMpOAuth2AccessToken.fromJson(responseContent); | |||||
| } | |||||
| @Override | |||||
| public String oauth2buildAuthorizationUrl(String appId, String redirectURI, String scope, String state) { | |||||
| return String.format(CONNECT_OAUTH2_AUTHORIZE_URL, | |||||
| appId, URIUtil.encodeURIComponent(redirectURI), scope, StringUtils.trimToEmpty(state), getWxOpenConfigStorage().getComponentAppId()); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,386 @@ | |||||
| package me.chanjar.weixin.open.api.impl; | |||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | |||||
| import me.chanjar.weixin.common.util.ToStringUtils; | |||||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||||
| import me.chanjar.weixin.mp.api.WxMpConfigStorage; | |||||
| import me.chanjar.weixin.open.api.WxOpenConfigStorage; | |||||
| import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; | |||||
| import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; | |||||
| import java.io.File; | |||||
| import java.util.Hashtable; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.locks.Lock; | |||||
| import java.util.concurrent.locks.ReentrantLock; | |||||
| /** | |||||
| * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||||
| * | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenInMemoryConfigStorage implements WxOpenConfigStorage { | |||||
| private String componentAppId; | |||||
| private String componentAppSecret; | |||||
| private String componentToken; | |||||
| private String componentAesKey; | |||||
| private String componentVerifyTicket; | |||||
| private String componentAccessToken; | |||||
| private long componentExpiresTime; | |||||
| private Map<String, Token> authorizerRefreshTokens = new Hashtable<>(); | |||||
| private Map<String, Token> authorizerAccessTokens = new Hashtable<>(); | |||||
| private Map<String, Token> jsapiTickets = new Hashtable<>(); | |||||
| private Map<String, Token> cardApiTickets = new Hashtable<>(); | |||||
| @Override | |||||
| public void setComponentAppId(String componentAppId) { | |||||
| this.componentAppId = componentAppId; | |||||
| } | |||||
| @Override | |||||
| public void setComponentAppSecret(String componentAppSecret) { | |||||
| this.componentAppSecret = componentAppSecret; | |||||
| } | |||||
| @Override | |||||
| public void setComponentToken(String componentToken) { | |||||
| this.componentToken = componentToken; | |||||
| } | |||||
| @Override | |||||
| public void setComponentAesKey(String componentAesKey) { | |||||
| this.componentAesKey = componentAesKey; | |||||
| } | |||||
| @Override | |||||
| public String getComponentAppId() { | |||||
| return componentAppId; | |||||
| } | |||||
| @Override | |||||
| public String getComponentAppSecret() { | |||||
| return componentAppSecret; | |||||
| } | |||||
| @Override | |||||
| public String getComponentToken() { | |||||
| return componentToken; | |||||
| } | |||||
| @Override | |||||
| public String getComponentAesKey() { | |||||
| return componentAesKey; | |||||
| } | |||||
| @Override | |||||
| public String getComponentVerifyTicket() { | |||||
| return componentVerifyTicket; | |||||
| } | |||||
| @Override | |||||
| public void setComponentVerifyTicket(String componentVerifyTicket) { | |||||
| this.componentVerifyTicket = componentVerifyTicket; | |||||
| } | |||||
| @Override | |||||
| public String getComponentAccessToken() { | |||||
| return componentAccessToken; | |||||
| } | |||||
| @Override | |||||
| public boolean isComponentAccessTokenExpired() { | |||||
| return System.currentTimeMillis() > componentExpiresTime; | |||||
| } | |||||
| @Override | |||||
| public void updateComponentAccessTokent(WxOpenComponentAccessToken componentAccessToken) { | |||||
| updateComponentAccessTokent(componentAccessToken.getComponentAccessToken(), componentAccessToken.getExpiresIn()); | |||||
| } | |||||
| @Override | |||||
| public WxMpConfigStorage getWxMpConfigStorage(String appId) { | |||||
| return new WxOpenMpConfigStorage(this, appId); | |||||
| } | |||||
| @Override | |||||
| public void updateComponentAccessTokent(String componentAccessToken, int expiresInSeconds) { | |||||
| this.componentAccessToken = componentAccessToken; | |||||
| this.componentExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||||
| } | |||||
| @Override | |||||
| public boolean autoRefreshToken() { | |||||
| return true; | |||||
| } | |||||
| private String getTokenString(Map<String, Token> map, String key){ | |||||
| Token token = map.get(key); | |||||
| if(token == null || (token.expiresTime != null && System.currentTimeMillis() > token.expiresTime)){ | |||||
| return null; | |||||
| } | |||||
| return token.token; | |||||
| } | |||||
| private void expireToken(Map<String, Token> map, String key){ | |||||
| Token token = map.get(key); | |||||
| if(token != null){ | |||||
| token.expiresTime = 0L; | |||||
| } | |||||
| } | |||||
| private void updateToken(Map<String, Token> map, String key, String tokenString, Integer expiresInSeconds){ | |||||
| Token token = map.get(key); | |||||
| if(token == null){ | |||||
| token = new Token(); | |||||
| map.put(key, token); | |||||
| } | |||||
| token.token = tokenString; | |||||
| if(expiresInSeconds != null) { | |||||
| token.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public String getAuthorizerRefreshToken(String appId) { | |||||
| return getTokenString(authorizerRefreshTokens, appId); | |||||
| } | |||||
| @Override | |||||
| public void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken) { | |||||
| updateToken(authorizerRefreshTokens, appId, authorizerRefreshToken, null); | |||||
| } | |||||
| @Override | |||||
| public String getAuthorizerAccessToken(String appId) { | |||||
| return getTokenString(authorizerAccessTokens, appId); | |||||
| } | |||||
| @Override | |||||
| public boolean isAuthorizerAccessTokenExpired(String appId) { | |||||
| return getTokenString(authorizerAccessTokens, appId) == null; | |||||
| } | |||||
| @Override | |||||
| public void expireAuthorizerAccessToken(String appId) { | |||||
| expireToken(authorizerAccessTokens, appId); | |||||
| } | |||||
| @Override | |||||
| public void updateAuthorizerAccessToken(String appId, WxOpenAuthorizerAccessToken authorizerAccessToken) { | |||||
| updateAuthorizerAccessToken(appId, authorizerAccessToken.getAuthorizerAccessToken(), authorizerAccessToken.getExpiresIn()); | |||||
| } | |||||
| @Override | |||||
| public void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds) { | |||||
| updateToken(authorizerAccessTokens, appId, authorizerAccessToken, expiresInSeconds); | |||||
| } | |||||
| @Override | |||||
| public String getJsapiTicket(String appId) { | |||||
| return getTokenString(jsapiTickets, appId); | |||||
| } | |||||
| @Override | |||||
| public boolean isJsapiTicketExpired(String appId) { | |||||
| return getTokenString(jsapiTickets, appId) == null; | |||||
| } | |||||
| @Override | |||||
| public void expireJsapiTicket(String appId) { | |||||
| expireToken(jsapiTickets, appId); | |||||
| } | |||||
| @Override | |||||
| public void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds) { | |||||
| updateToken(jsapiTickets, appId, jsapiTicket, expiresInSeconds); | |||||
| } | |||||
| @Override | |||||
| public String getCardApiTicket(String appId) { | |||||
| return getTokenString(cardApiTickets, appId); | |||||
| } | |||||
| @Override | |||||
| public boolean isCardApiTicketExpired(String appId) { | |||||
| return getTokenString(cardApiTickets, appId) == null; | |||||
| } | |||||
| @Override | |||||
| public void expireCardApiTicket(String appId) { | |||||
| expireToken(cardApiTickets, appId); | |||||
| } | |||||
| @Override | |||||
| public void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds) { | |||||
| updateToken(cardApiTickets, appId, cardApiTicket, expiresInSeconds); | |||||
| } | |||||
| private static class Token{ | |||||
| private String token; | |||||
| private Long expiresTime; | |||||
| } | |||||
| private static class WxOpenMpConfigStorage implements WxMpConfigStorage{ | |||||
| private WxOpenConfigStorage wxOpenConfigStorage; | |||||
| private String appId; | |||||
| private WxOpenMpConfigStorage(WxOpenConfigStorage wxOpenConfigStorage, String appId){ | |||||
| this.wxOpenConfigStorage = wxOpenConfigStorage; | |||||
| this.appId = appId; | |||||
| } | |||||
| private Lock accessTokenLock = new ReentrantLock(); | |||||
| private Lock jsapiTicketLock = new ReentrantLock(); | |||||
| private Lock cardApiTicketLock = new ReentrantLock(); | |||||
| @Override | |||||
| public String getAccessToken() { | |||||
| return wxOpenConfigStorage.getAuthorizerAccessToken(appId); | |||||
| } | |||||
| @Override | |||||
| public Lock getAccessTokenLock() { | |||||
| return this.accessTokenLock; | |||||
| } | |||||
| @Override | |||||
| public boolean isAccessTokenExpired() { | |||||
| return wxOpenConfigStorage.isAuthorizerAccessTokenExpired(appId); | |||||
| } | |||||
| @Override | |||||
| public synchronized void updateAccessToken(WxAccessToken accessToken) { | |||||
| updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
| } | |||||
| @Override | |||||
| public synchronized void updateAccessToken(String accessToken, int expiresInSeconds) { | |||||
| wxOpenConfigStorage.updateAuthorizerAccessToken(appId, accessToken, expiresInSeconds); | |||||
| } | |||||
| @Override | |||||
| public void expireAccessToken() { | |||||
| wxOpenConfigStorage.expireAuthorizerAccessToken(appId); | |||||
| } | |||||
| @Override | |||||
| public String getJsapiTicket() { | |||||
| return wxOpenConfigStorage.getJsapiTicket(appId); | |||||
| } | |||||
| @Override | |||||
| public Lock getJsapiTicketLock() { | |||||
| return this.jsapiTicketLock; | |||||
| } | |||||
| @Override | |||||
| public boolean isJsapiTicketExpired() { | |||||
| return wxOpenConfigStorage.isJsapiTicketExpired(appId); | |||||
| } | |||||
| @Override | |||||
| public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||||
| wxOpenConfigStorage.updateJsapiTicket(appId, jsapiTicket, expiresInSeconds); | |||||
| } | |||||
| @Override | |||||
| public void expireJsapiTicket() { | |||||
| wxOpenConfigStorage.expireJsapiTicket(appId); | |||||
| } | |||||
| /** | |||||
| * 卡券api_ticket | |||||
| */ | |||||
| @Override | |||||
| public String getCardApiTicket() { | |||||
| return wxOpenConfigStorage.getCardApiTicket(appId); | |||||
| } | |||||
| @Override | |||||
| public Lock getCardApiTicketLock() { | |||||
| return this.cardApiTicketLock; | |||||
| } | |||||
| @Override | |||||
| public boolean isCardApiTicketExpired() { | |||||
| return wxOpenConfigStorage.isCardApiTicketExpired(appId); | |||||
| } | |||||
| @Override | |||||
| public synchronized void updateCardApiTicket(String cardApiTicket, int expiresInSeconds) { | |||||
| wxOpenConfigStorage.updateCardApiTicket(appId, cardApiTicket, expiresInSeconds); | |||||
| } | |||||
| @Override | |||||
| public void expireCardApiTicket() { | |||||
| wxOpenConfigStorage.expireCardApiTicket(appId); | |||||
| } | |||||
| @Override | |||||
| public String getAppId() { | |||||
| return this.appId; | |||||
| } | |||||
| @Override | |||||
| public String getSecret() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public String getToken() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public long getExpiresTime() { | |||||
| return 0; | |||||
| } | |||||
| @Override | |||||
| public String getAesKey() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public String getOauth2redirectUri() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public String getHttpProxyHost() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public int getHttpProxyPort() { | |||||
| return 0; | |||||
| } | |||||
| @Override | |||||
| public String getHttpProxyUsername() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public String getHttpProxyPassword() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public String toString() { | |||||
| return ToStringUtils.toSimpleString(this); | |||||
| } | |||||
| @Override | |||||
| public File getTmpDirFile() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public ApacheHttpClientBuilder getApacheHttpClientBuilder() { | |||||
| return null; | |||||
| } | |||||
| @Override | |||||
| public boolean autoRefreshToken() { | |||||
| return true; | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,164 @@ | |||||
| package me.chanjar.weixin.open.api.impl; | |||||
| import redis.clients.jedis.Jedis; | |||||
| import redis.clients.jedis.JedisPool; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenInRedisConfigStorage extends WxOpenInMemoryConfigStorage { | |||||
| private final static String COMPONENT_VERIFY_TICKET_KEY = "wechat_component_verify_ticket:"; | |||||
| private final static String COMPONENT_ACCESS_TOKEN_KEY = "wechat_component_access_token:"; | |||||
| private final static String AUTHORIZER_REFRESH_TOKEN_KEY = "wechat_authorizer_refresh_token:"; | |||||
| private final static String AUTHORIZER_ACCESS_TOKEN_KEY = "wechat_authorizer_access_token:"; | |||||
| private final static String JSAPI_TICKET_KEY = "wechat_jsapi_ticket:"; | |||||
| private final static String CARD_API_TICKET_KEY = "wechat_card_api_ticket:"; | |||||
| protected final JedisPool jedisPool; | |||||
| private String componentVerifyTicketKey; | |||||
| private String componentAccessTokenKey; | |||||
| private String authorizerRefreshTokenKey; | |||||
| private String authorizerAccessTokenKey; | |||||
| private String jsapiTicketKey; | |||||
| private String cardApiTicket; | |||||
| public WxOpenInRedisConfigStorage(JedisPool jedisPool) { | |||||
| this.jedisPool = jedisPool; | |||||
| } | |||||
| @Override | |||||
| public void setComponentAppId(String componentAppId) { | |||||
| super.setComponentAppId(componentAppId); | |||||
| componentVerifyTicketKey = COMPONENT_VERIFY_TICKET_KEY.concat(componentAppId); | |||||
| componentAccessTokenKey = COMPONENT_ACCESS_TOKEN_KEY.concat(componentAppId); | |||||
| authorizerRefreshTokenKey = AUTHORIZER_REFRESH_TOKEN_KEY.concat(componentAppId); | |||||
| authorizerAccessTokenKey = AUTHORIZER_ACCESS_TOKEN_KEY.concat(componentAppId); | |||||
| jsapiTicketKey = JSAPI_TICKET_KEY.concat(componentAppId); | |||||
| cardApiTicket = CARD_API_TICKET_KEY.concat(componentAppId); | |||||
| } | |||||
| @Override | |||||
| public String getComponentVerifyTicket(){ | |||||
| try(Jedis jedis = jedisPool.getResource()){ | |||||
| return jedis.get(componentVerifyTicketKey); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void setComponentVerifyTicket(String componentVerifyTicket){ | |||||
| try(Jedis jedis = jedisPool.getResource()){ | |||||
| jedis.set(componentVerifyTicketKey, componentVerifyTicket); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public String getComponentAccessToken(){ | |||||
| try(Jedis jedis = jedisPool.getResource()){ | |||||
| return jedis.get(componentAccessTokenKey); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public boolean isComponentAccessTokenExpired(){ | |||||
| try(Jedis jedis = jedisPool.getResource()){ | |||||
| return jedis.ttl(componentAccessTokenKey) < 2; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void updateComponentAccessTokent(String componentAccessToken, int expiresInSeconds){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| jedis.setex(componentAccessTokenKey, expiresInSeconds - 200, componentAccessToken); | |||||
| } | |||||
| } | |||||
| private String getKey(String prefix, String appId){ | |||||
| return prefix.endsWith(":") ? prefix.concat(appId) : prefix.concat(":").concat(appId); | |||||
| } | |||||
| @Override | |||||
| public String getAuthorizerRefreshToken(String appId){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| return jedis.get(getKey(authorizerRefreshTokenKey, appId)); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void setAuthorizerRefreshToken(String appId, String authorizerRefreshToken){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| jedis.set(getKey(authorizerRefreshTokenKey, appId), authorizerRefreshToken); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public String getAuthorizerAccessToken(String appId){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| return jedis.get(getKey(authorizerAccessTokenKey, appId)); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public boolean isAuthorizerAccessTokenExpired(String appId){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| return jedis.ttl(getKey(authorizerAccessTokenKey, appId)) < 2; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void expireAuthorizerAccessToken(String appId){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| jedis.expire(getKey(authorizerAccessTokenKey, appId), 0); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void updateAuthorizerAccessToken(String appId, String authorizerAccessToken, int expiresInSeconds){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| jedis.setex(getKey(authorizerAccessTokenKey, appId), expiresInSeconds - 200, authorizerAccessToken); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public String getJsapiTicket(String appId){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| return jedis.get(getKey(jsapiTicketKey, appId)); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public boolean isJsapiTicketExpired(String appId){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| return jedis.ttl(getKey(jsapiTicketKey, appId)) < 2; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void expireJsapiTicket(String appId){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| jedis.expire(getKey(jsapiTicketKey, appId), 0); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void updateJsapiTicket(String appId, String jsapiTicket, int expiresInSeconds){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| jedis.setex(getKey(jsapiTicketKey, appId), expiresInSeconds - 200, jsapiTicket); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public String getCardApiTicket(String appId){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| return jedis.get(getKey(jsapiTicketKey, appId)); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public boolean isCardApiTicketExpired(String appId){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| return jedis.ttl(getKey(cardApiTicket, appId)) < 2; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void expireCardApiTicket(String appId){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| jedis.expire(getKey(cardApiTicket, appId), 0); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void updateCardApiTicket(String appId, String cardApiTicket, int expiresInSeconds){ | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| jedis.setex(getKey(cardApiTicket, appId), expiresInSeconds - 200, cardApiTicket); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,49 @@ | |||||
| package me.chanjar.weixin.open.api.impl; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.mp.api.WxMpConfigStorage; | |||||
| import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; | |||||
| import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | |||||
| import me.chanjar.weixin.open.api.WxOpenComponentService; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenMpServiceImpl extends WxMpServiceImpl { | |||||
| private WxOpenComponentService wxOpenComponentService; | |||||
| private WxMpConfigStorage wxMpConfigStorage; | |||||
| private String appId; | |||||
| public WxOpenMpServiceImpl(WxOpenComponentService wxOpenComponentService, String appId, WxMpConfigStorage wxMpConfigStorage){ | |||||
| this.wxOpenComponentService = wxOpenComponentService; | |||||
| this.appId = appId; | |||||
| this.wxMpConfigStorage = wxMpConfigStorage; | |||||
| } | |||||
| @Override | |||||
| public WxMpConfigStorage getWxMpConfigStorage(){ | |||||
| return wxMpConfigStorage; | |||||
| } | |||||
| @Override | |||||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||||
| return wxOpenComponentService.getAuthorizerAccessToken(appId, forceRefresh); | |||||
| } | |||||
| @Override | |||||
| public WxMpOAuth2AccessToken oauth2getAccessToken(String code) throws WxErrorException { | |||||
| return wxOpenComponentService.oauth2getAccessToken(appId, code); | |||||
| } | |||||
| // @Override | |||||
| // public boolean checkSignature(String timestamp, String nonce, String signature) { | |||||
| // return wxOpenComponentService.checkSignature(appId, timestamp, nonce, signature); | |||||
| // } | |||||
| @Override | |||||
| public WxMpOAuth2AccessToken oauth2refreshAccessToken(String refreshToken) throws WxErrorException { | |||||
| return wxOpenComponentService.oauth2refreshAccessToken(appId, refreshToken); | |||||
| } | |||||
| @Override | |||||
| public String oauth2buildAuthorizationUrl(String redirectURI, String scope, String state) { | |||||
| return wxOpenComponentService.oauth2buildAuthorizationUrl(appId, redirectURI, scope, state); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,63 @@ | |||||
| package me.chanjar.weixin.open.api.impl; | |||||
| import me.chanjar.weixin.common.bean.result.WxError; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import me.chanjar.weixin.open.api.WxOpenComponentService; | |||||
| import me.chanjar.weixin.open.api.WxOpenConfigStorage; | |||||
| import me.chanjar.weixin.open.api.WxOpenService; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.io.IOException; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public abstract class WxOpenServiceAbstractImpl<H, P> implements WxOpenService, RequestHttp<H, P> { | |||||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | |||||
| private WxOpenConfigStorage wxOpenConfigStorage; | |||||
| protected WxOpenComponentService wxOpenComponentService = new WxOpenComponentServiceImpl(this); | |||||
| @Override | |||||
| public WxOpenComponentService getWxOpenComponentService() { | |||||
| return wxOpenComponentService; | |||||
| } | |||||
| @Override | |||||
| public WxOpenConfigStorage getWxOpenConfigStorage(){ | |||||
| return wxOpenConfigStorage; | |||||
| } | |||||
| public void setWxOpenConfigStorage(WxOpenConfigStorage wxOpenConfigStorage){ | |||||
| this.wxOpenConfigStorage = wxOpenConfigStorage; | |||||
| } | |||||
| protected synchronized <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | |||||
| try { | |||||
| T result = executor.execute(uri, data); | |||||
| this.log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uri, data, result); | |||||
| return result; | |||||
| } catch (WxErrorException e) { | |||||
| WxError error = e.getError(); | |||||
| // /* | |||||
| // * 发生以下情况时尝试刷新access_token | |||||
| // * 40001 获取access_token时AppSecret错误,或者access_token无效 | |||||
| // * 42001 access_token超时 | |||||
| // * 40014 不合法的access_token,请开发者认真比对access_token的有效性(如是否过期),或查看是否正在为恰当的公众号调用接口 | |||||
| // */ | |||||
| // if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) { | |||||
| // // 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||||
| // this.configStorage.expireAccessToken(); | |||||
| // return execute(executor, uri, data); | |||||
| // } | |||||
| if (error.getErrorCode() != 0) { | |||||
| this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【错误信息】:{}", uri, data, error); | |||||
| throw new WxErrorException(error, e); | |||||
| } | |||||
| return null; | |||||
| } catch (IOException e) { | |||||
| this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uri, data, e.getMessage()); | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| package me.chanjar.weixin.open.api.impl; | |||||
| import me.chanjar.weixin.common.exception.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.HttpType; | |||||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | |||||
| import org.apache.http.HttpHost; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| /** | |||||
| * apache-http方式实现 | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenServiceApacheHttpClientImpl extends WxOpenServiceAbstractImpl<CloseableHttpClient, HttpHost> { | |||||
| private CloseableHttpClient httpClient = DefaultApacheHttpClientBuilder.get().build(); | |||||
| private HttpHost httpProxy = null; | |||||
| @Override | |||||
| public CloseableHttpClient getRequestHttpClient() { | |||||
| return httpClient; | |||||
| } | |||||
| @Override | |||||
| public HttpHost getRequestHttpProxy() { | |||||
| return httpProxy; | |||||
| } | |||||
| @Override | |||||
| public HttpType getRequestType() { | |||||
| return HttpType.APACHE_HTTP; | |||||
| } | |||||
| @Override | |||||
| public String get(String url, String queryParam) throws WxErrorException { | |||||
| return execute(SimpleGetRequestExecutor.create(this), url, queryParam); | |||||
| } | |||||
| @Override | |||||
| public String post(String url, String postData) throws WxErrorException { | |||||
| return execute(SimplePostRequestExecutor.create(this), url, postData); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,8 @@ | |||||
| package me.chanjar.weixin.open.api.impl; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenServiceImpl extends WxOpenServiceApacheHttpClientImpl { | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| package me.chanjar.weixin.open.bean; | |||||
| import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenAuthorizerAccessToken implements Serializable{ | |||||
| private String authorizerAccessToken; | |||||
| private int expiresIn = -1; | |||||
| public static WxOpenAuthorizerAccessToken fromJson(String json) { | |||||
| return WxOpenGsonBuilder.create().fromJson(json, WxOpenAuthorizerAccessToken.class); | |||||
| } | |||||
| public String getAuthorizerAccessToken() { | |||||
| return authorizerAccessToken; | |||||
| } | |||||
| public void setAuthorizerAccessToken(String authorizerAccessToken) { | |||||
| this.authorizerAccessToken = authorizerAccessToken; | |||||
| } | |||||
| public int getExpiresIn() { | |||||
| return expiresIn; | |||||
| } | |||||
| public void setExpiresIn(int expiresIn) { | |||||
| this.expiresIn = expiresIn; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,35 @@ | |||||
| package me.chanjar.weixin.open.bean; | |||||
| import me.chanjar.weixin.open.util.json.WxOpenGsonBuilder; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenComponentAccessToken implements Serializable{ | |||||
| private String componentAccessToken; | |||||
| private int expiresIn = -1; | |||||
| public static WxOpenComponentAccessToken fromJson(String json) { | |||||
| return WxOpenGsonBuilder.create().fromJson(json, WxOpenComponentAccessToken.class); | |||||
| } | |||||
| public String getComponentAccessToken() { | |||||
| return componentAccessToken; | |||||
| } | |||||
| public void setComponentAccessToken(String componentAccessToken) { | |||||
| this.componentAccessToken = componentAccessToken; | |||||
| } | |||||
| public int getExpiresIn() { | |||||
| return expiresIn; | |||||
| } | |||||
| public void setExpiresIn(int expiresIn) { | |||||
| this.expiresIn = expiresIn; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,18 @@ | |||||
| package me.chanjar.weixin.open.bean.auth; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| @Data | |||||
| public class WxOpenAuthorizationInfo implements Serializable{ | |||||
| private String authorizerAppid; | |||||
| private String authorizerAccessToken; | |||||
| private int expiresIn; | |||||
| private String authorizerRefreshToken; | |||||
| private List<Integer> funcInfo; | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| package me.chanjar.weixin.open.bean.auth; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| import java.util.Map; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| @Data | |||||
| public class WxOpenAuthorizerInfo implements Serializable{ | |||||
| private String nickName; | |||||
| private String headImg; | |||||
| private Integer serviceTypeInfo; | |||||
| private Integer verifyTypeInfo; | |||||
| private String userName; | |||||
| private String principalName; | |||||
| private Map<String, Integer> businessInfo; | |||||
| private String alias; | |||||
| private String qrcodeUrl; | |||||
| } | |||||
| @@ -0,0 +1,100 @@ | |||||
| package me.chanjar.weixin.open.bean.message; | |||||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | |||||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | |||||
| import lombok.Data; | |||||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | |||||
| import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; | |||||
| import me.chanjar.weixin.open.api.WxOpenConfigStorage; | |||||
| import me.chanjar.weixin.open.util.WxOpenCryptUtil; | |||||
| import me.chanjar.weixin.open.util.xml.XStreamTransformer; | |||||
| import org.apache.commons.io.IOUtils; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| @XStreamAlias("xml") | |||||
| @Data | |||||
| public class WxOpenXmlMessage implements Serializable{ | |||||
| @XStreamAlias("AppId") | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||||
| private String appId; | |||||
| @XStreamAlias("CreateTime") | |||||
| private Long createTime; | |||||
| @XStreamAlias("InfoType") | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||||
| private String infoType; | |||||
| @XStreamAlias("ComponentVerifyTicket") | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||||
| private String componentVerifyTicket; | |||||
| @XStreamAlias("AuthorizerAppid") | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||||
| private String authorizerAppid; | |||||
| @XStreamAlias("AuthorizationCode") | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||||
| private String authorizationCode; | |||||
| @XStreamAlias("AuthorizationCodeExpiredTime") | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||||
| private Long authorizationCodeExpiredTime; | |||||
| @XStreamAlias("PreAuthCode") | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||||
| private String preAuthCode; | |||||
| public static WxOpenXmlMessage fromXml(String xml) { | |||||
| //修改微信变态的消息内容格式,方便解析 | |||||
| xml = xml.replace("</PicList><PicList>", ""); | |||||
| return XStreamTransformer.fromXml(WxOpenXmlMessage.class, xml); | |||||
| } | |||||
| public static WxOpenXmlMessage fromXml(InputStream is) { | |||||
| return XStreamTransformer.fromXml(WxOpenXmlMessage.class, is); | |||||
| } | |||||
| /** | |||||
| * 从加密字符串转换 | |||||
| * | |||||
| * @param encryptedXml 密文 | |||||
| * @param wxOpenConfigStorage 配置存储器对象 | |||||
| * @param timestamp 时间戳 | |||||
| * @param nonce 随机串 | |||||
| * @param msgSignature 签名串 | |||||
| */ | |||||
| public static WxOpenXmlMessage fromEncryptedXml(String encryptedXml, | |||||
| WxOpenConfigStorage wxOpenConfigStorage, String timestamp, String nonce, | |||||
| String msgSignature) { | |||||
| WxOpenCryptUtil cryptUtil = new WxOpenCryptUtil(wxOpenConfigStorage); | |||||
| String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, | |||||
| encryptedXml); | |||||
| return fromXml(plainText); | |||||
| } | |||||
| public static WxMpXmlMessage fromEncryptedMpXml(String encryptedXml, | |||||
| WxOpenConfigStorage wxOpenConfigStorage, String timestamp, String nonce, | |||||
| String msgSignature) { | |||||
| WxOpenCryptUtil cryptUtil = new WxOpenCryptUtil(wxOpenConfigStorage); | |||||
| String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, | |||||
| encryptedXml); | |||||
| return WxMpXmlMessage.fromXml(plainText); | |||||
| } | |||||
| public static WxOpenXmlMessage fromEncryptedXml(InputStream is, | |||||
| WxOpenConfigStorage wxOpenConfigStorage, String timestamp, String nonce, | |||||
| String msgSignature) { | |||||
| try { | |||||
| return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxOpenConfigStorage, | |||||
| timestamp, nonce, msgSignature); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,16 @@ | |||||
| package me.chanjar.weixin.open.bean.result; | |||||
| import lombok.Data; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizerInfo; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| @Data | |||||
| public class WxOpenAuthorizerInfoResult implements Serializable{ | |||||
| private WxOpenAuthorizationInfo authorizationInfo; | |||||
| private WxOpenAuthorizerInfo authorizerInfo; | |||||
| } | |||||
| @@ -0,0 +1,15 @@ | |||||
| package me.chanjar.weixin.open.bean.result; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| @Data | |||||
| public class WxOpenAuthorizerOptionResult implements Serializable{ | |||||
| String authorizerAppid; | |||||
| String optionName; | |||||
| String optionValue; | |||||
| } | |||||
| @@ -0,0 +1,14 @@ | |||||
| package me.chanjar.weixin.open.bean.result; | |||||
| import lombok.Data; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| @Data | |||||
| public class WxOpenQueryAuthResult implements Serializable{ | |||||
| private WxOpenAuthorizationInfo authorizationInfo; | |||||
| } | |||||
| @@ -0,0 +1,28 @@ | |||||
| package me.chanjar.weixin.open.util; | |||||
| import me.chanjar.weixin.open.api.WxOpenConfigStorage; | |||||
| import org.apache.commons.codec.binary.Base64; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenCryptUtil extends me.chanjar.weixin.common.util.crypto.WxCryptUtil { | |||||
| /** | |||||
| * 构造函数 | |||||
| * | |||||
| * @param wxOpenConfigStorage | |||||
| */ | |||||
| public WxOpenCryptUtil(WxOpenConfigStorage wxOpenConfigStorage) { | |||||
| /* | |||||
| * @param token 公众平台上,开发者设置的token | |||||
| * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey | |||||
| * @param appId 公众平台appid | |||||
| */ | |||||
| String encodingAesKey = wxOpenConfigStorage.getComponentAesKey(); | |||||
| String token = wxOpenConfigStorage.getComponentToken(); | |||||
| String appId = wxOpenConfigStorage.getComponentAppId(); | |||||
| this.token = token; | |||||
| this.appidOrCorpid = appId; | |||||
| this.aesKey = Base64.decodeBase64(encodingAesKey + "="); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,45 @@ | |||||
| package me.chanjar.weixin.open.util.json; | |||||
| import com.google.gson.*; | |||||
| import me.chanjar.weixin.common.util.json.GsonHelper; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; | |||||
| import java.lang.reflect.Type; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenAuthorizationInfoGsonAdapter implements JsonDeserializer<WxOpenAuthorizationInfo> { | |||||
| @Override | |||||
| public WxOpenAuthorizationInfo deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { | |||||
| WxOpenAuthorizationInfo authorizationInfo = new WxOpenAuthorizationInfo(); | |||||
| JsonObject jsonObject = jsonElement.getAsJsonObject(); | |||||
| authorizationInfo.setAuthorizerAppid(GsonHelper.getString(jsonObject, "authorizer_appid")); | |||||
| authorizationInfo.setAuthorizerAccessToken(GsonHelper.getString(jsonObject, "authorizer_access_token")); | |||||
| authorizationInfo.setExpiresIn(GsonHelper.getPrimitiveInteger(jsonObject, "expires_in")); | |||||
| authorizationInfo.setAuthorizerRefreshToken(GsonHelper.getString(jsonObject, "authorizer_refresh_token")); | |||||
| List<Integer> funcInfo = new ArrayList<>(); | |||||
| JsonArray jsonArray = GsonHelper.getAsJsonArray(jsonObject.get("func_info")); | |||||
| if(jsonArray != null && !jsonArray.isJsonNull()){ | |||||
| for(int i = 0; i < jsonArray.size(); i++){ | |||||
| jsonObject = jsonArray.get(i).getAsJsonObject(); | |||||
| if(jsonObject == null || jsonObject.isJsonNull()){ | |||||
| continue; | |||||
| } | |||||
| jsonObject = jsonObject.getAsJsonObject("funcscope_category"); | |||||
| if(jsonObject == null || jsonObject.isJsonNull()){ | |||||
| continue; | |||||
| } | |||||
| Integer id = GsonHelper.getInteger(jsonObject, "id"); | |||||
| if(id == null) { | |||||
| continue; | |||||
| } | |||||
| funcInfo.add(id); | |||||
| } | |||||
| } | |||||
| authorizationInfo.setFuncInfo(funcInfo); | |||||
| return authorizationInfo; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| package me.chanjar.weixin.open.util.json; | |||||
| import com.google.gson.*; | |||||
| import me.chanjar.weixin.common.util.json.GsonHelper; | |||||
| import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; | |||||
| import java.lang.reflect.Type; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenAuthorizerAccessTokenGsonAdapter implements JsonDeserializer<WxOpenAuthorizerAccessToken> { | |||||
| @Override | |||||
| public WxOpenAuthorizerAccessToken deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { | |||||
| WxOpenAuthorizerAccessToken authorizerAccessToken = new WxOpenAuthorizerAccessToken(); | |||||
| JsonObject jsonObject = jsonElement.getAsJsonObject(); | |||||
| authorizerAccessToken.setAuthorizerAccessToken(GsonHelper.getString(jsonObject, "authorizer_access_token")); | |||||
| authorizerAccessToken.setExpiresIn(GsonHelper.getPrimitiveInteger(jsonObject, "expires_in")); | |||||
| return authorizerAccessToken; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,38 @@ | |||||
| package me.chanjar.weixin.open.util.json; | |||||
| import com.google.gson.*; | |||||
| import com.google.gson.reflect.TypeToken; | |||||
| import me.chanjar.weixin.common.util.json.GsonHelper; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizerInfo; | |||||
| import java.lang.reflect.Type; | |||||
| import java.util.Map; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenAuthorizerInfoGsonAdapter implements JsonDeserializer<WxOpenAuthorizerInfo> { | |||||
| @Override | |||||
| public WxOpenAuthorizerInfo deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { | |||||
| WxOpenAuthorizerInfo authorizationInfo = new WxOpenAuthorizerInfo(); | |||||
| JsonObject jsonObject = jsonElement.getAsJsonObject(); | |||||
| authorizationInfo.setNickName(GsonHelper.getString(jsonObject, "nick_name")); | |||||
| authorizationInfo.setHeadImg(GsonHelper.getString(jsonObject, "head_img")); | |||||
| authorizationInfo.setUserName(GsonHelper.getString(jsonObject, "user_name")); | |||||
| authorizationInfo.setPrincipalName(GsonHelper.getString(jsonObject, "principal_name")); | |||||
| authorizationInfo.setAlias(GsonHelper.getString(jsonObject, "alias")); | |||||
| authorizationInfo.setQrcodeUrl(GsonHelper.getString(jsonObject, "qrcode_url")); | |||||
| if(jsonObject.has("service_type_info")) { | |||||
| authorizationInfo.setServiceTypeInfo(GsonHelper.getInteger(jsonObject.getAsJsonObject("service_type_info"), "id")); | |||||
| } | |||||
| if(jsonObject.has("verify_type_info")) { | |||||
| authorizationInfo.setVerifyTypeInfo(GsonHelper.getInteger(jsonObject.getAsJsonObject("verify_type_info"), "id")); | |||||
| } | |||||
| Map<String, Integer> businessInfo = WxOpenGsonBuilder.create().fromJson(jsonObject.get("business_info"), | |||||
| new TypeToken<Map<String, Integer>>() { | |||||
| }.getType()); | |||||
| authorizationInfo.setBusinessInfo(businessInfo); | |||||
| return authorizationInfo; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,32 @@ | |||||
| package me.chanjar.weixin.open.util.json; | |||||
| import com.google.gson.*; | |||||
| import com.google.gson.reflect.TypeToken; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizerInfo; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult; | |||||
| import java.lang.reflect.Type; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenAuthorizerInfoResultGsonAdapter implements JsonDeserializer<WxOpenAuthorizerInfoResult> { | |||||
| @Override | |||||
| public WxOpenAuthorizerInfoResult deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { | |||||
| WxOpenAuthorizerInfoResult authorizerInfoResult = new WxOpenAuthorizerInfoResult(); | |||||
| JsonObject jsonObject = jsonElement.getAsJsonObject(); | |||||
| WxOpenAuthorizationInfo authorizationInfo = WxOpenGsonBuilder.INSTANCE.create().fromJson(jsonObject.get("authorization_info"), | |||||
| new TypeToken<WxOpenAuthorizationInfo>() { | |||||
| }.getType()); | |||||
| authorizerInfoResult.setAuthorizationInfo(authorizationInfo); | |||||
| WxOpenAuthorizerInfo authorizerInfo = WxOpenGsonBuilder.INSTANCE.create().fromJson(jsonObject.get("authorizer_info"), | |||||
| new TypeToken<WxOpenAuthorizerInfo>() { | |||||
| }.getType()); | |||||
| authorizerInfoResult.setAuthorizerInfo(authorizerInfo); | |||||
| return authorizerInfoResult; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,22 @@ | |||||
| package me.chanjar.weixin.open.util.json; | |||||
| import com.google.gson.*; | |||||
| import me.chanjar.weixin.common.util.json.GsonHelper; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerOptionResult; | |||||
| import java.lang.reflect.Type; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenAuthorizerOptionResultGsonAdapter implements JsonDeserializer<WxOpenAuthorizerOptionResult> { | |||||
| @Override | |||||
| public WxOpenAuthorizerOptionResult deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { | |||||
| WxOpenAuthorizerOptionResult authorizerOptionResult = new WxOpenAuthorizerOptionResult(); | |||||
| JsonObject jsonObject = jsonElement.getAsJsonObject(); | |||||
| authorizerOptionResult.setAuthorizerAppid(GsonHelper.getString(jsonObject, "authorizer_appid")); | |||||
| authorizerOptionResult.setOptionName(GsonHelper.getString(jsonObject, "option_name")); | |||||
| authorizerOptionResult.setOptionValue(GsonHelper.getString(jsonObject, "option_value")); | |||||
| return authorizerOptionResult; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,21 @@ | |||||
| package me.chanjar.weixin.open.util.json; | |||||
| import com.google.gson.*; | |||||
| import me.chanjar.weixin.common.util.json.GsonHelper; | |||||
| import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; | |||||
| import java.lang.reflect.Type; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenComponentAccessTokenGsonAdapter implements JsonDeserializer<WxOpenComponentAccessToken> { | |||||
| @Override | |||||
| public WxOpenComponentAccessToken deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { | |||||
| WxOpenComponentAccessToken componentAccessToken = new WxOpenComponentAccessToken(); | |||||
| JsonObject jsonObject = jsonElement.getAsJsonObject(); | |||||
| componentAccessToken.setComponentAccessToken(GsonHelper.getString(jsonObject, "component_access_token")); | |||||
| componentAccessToken.setExpiresIn(GsonHelper.getPrimitiveInteger(jsonObject, "expires_in")); | |||||
| return componentAccessToken; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,36 @@ | |||||
| package me.chanjar.weixin.open.util.json; | |||||
| import com.google.gson.Gson; | |||||
| import com.google.gson.GsonBuilder; | |||||
| import me.chanjar.weixin.open.bean.WxOpenAuthorizerAccessToken; | |||||
| import me.chanjar.weixin.open.bean.WxOpenComponentAccessToken; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizerInfo; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerOptionResult; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenGsonBuilder { | |||||
| public static final GsonBuilder INSTANCE = new GsonBuilder(); | |||||
| static { | |||||
| INSTANCE.disableHtmlEscaping(); | |||||
| INSTANCE.registerTypeAdapter(WxOpenComponentAccessToken.class, new WxOpenComponentAccessTokenGsonAdapter()); | |||||
| INSTANCE.registerTypeAdapter(WxOpenAuthorizerAccessToken.class, new WxOpenAuthorizerAccessTokenGsonAdapter()); | |||||
| INSTANCE.registerTypeAdapter(WxOpenAuthorizationInfo.class, new WxOpenAuthorizationInfoGsonAdapter()); | |||||
| INSTANCE.registerTypeAdapter(WxOpenAuthorizerInfo.class, new WxOpenAuthorizerInfoGsonAdapter()); | |||||
| INSTANCE.registerTypeAdapter(WxOpenQueryAuthResult.class, new WxOpenQueryAuthResultGsonAdapter()); | |||||
| INSTANCE.registerTypeAdapter(WxOpenAuthorizerInfoResult.class, new WxOpenAuthorizerInfoResultGsonAdapter()); | |||||
| INSTANCE.registerTypeAdapter(WxOpenAuthorizerOptionResult.class, new WxOpenAuthorizerOptionResultGsonAdapter()); | |||||
| } | |||||
| public static Gson create() { | |||||
| return INSTANCE.create(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,26 @@ | |||||
| package me.chanjar.weixin.open.util.json; | |||||
| import com.google.gson.*; | |||||
| import com.google.gson.reflect.TypeToken; | |||||
| import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; | |||||
| import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult; | |||||
| import java.lang.reflect.Type; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxOpenQueryAuthResultGsonAdapter implements JsonDeserializer<WxOpenQueryAuthResult> { | |||||
| @Override | |||||
| public WxOpenQueryAuthResult deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { | |||||
| WxOpenQueryAuthResult queryAuthResult = new WxOpenQueryAuthResult(); | |||||
| JsonObject jsonObject = jsonElement.getAsJsonObject(); | |||||
| WxOpenAuthorizationInfo authorizationInfo = WxOpenGsonBuilder.INSTANCE.create().fromJson(jsonObject.get("authorization_info"), | |||||
| new TypeToken<WxOpenAuthorizationInfo>() { | |||||
| }.getType()); | |||||
| queryAuthResult.setAuthorizationInfo(authorizationInfo); | |||||
| return queryAuthResult; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,90 @@ | |||||
| package me.chanjar.weixin.open.util.xml; | |||||
| import com.thoughtworks.xstream.XStream; | |||||
| import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||||
| import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage; | |||||
| import java.io.InputStream; | |||||
| import java.util.*; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class XStreamTransformer { | |||||
| private static final Map<Class<?>, XStream> CLASS_2_XSTREAM_INSTANCE = new HashMap<>(); | |||||
| static { | |||||
| registerClass(WxOpenXmlMessage.class); | |||||
| } | |||||
| /** | |||||
| * xml -> pojo | |||||
| */ | |||||
| @SuppressWarnings("unchecked") | |||||
| public static <T> T fromXml(Class<T> clazz, String xml) { | |||||
| T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(xml); | |||||
| return object; | |||||
| } | |||||
| @SuppressWarnings("unchecked") | |||||
| public static <T> T fromXml(Class<T> clazz, InputStream is) { | |||||
| T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is); | |||||
| return object; | |||||
| } | |||||
| /** | |||||
| * pojo -> xml | |||||
| */ | |||||
| public static <T> String toXml(Class<T> clazz, T object) { | |||||
| return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object); | |||||
| } | |||||
| /** | |||||
| * 注册扩展消息的解析器 | |||||
| * | |||||
| * @param clz 类型 | |||||
| * @param xStream xml解析器 | |||||
| */ | |||||
| private static void register(Class<?> clz, XStream xStream) { | |||||
| CLASS_2_XSTREAM_INSTANCE.put(clz, xStream); | |||||
| } | |||||
| /** | |||||
| * 会自动注册该类及其子类 | |||||
| * | |||||
| * @param clz 要注册的类 | |||||
| */ | |||||
| private static void registerClass(Class<?> clz) { | |||||
| XStream xstream = XStreamInitializer.getInstance(); | |||||
| xstream.setClassLoader(Thread.currentThread().getContextClassLoader()); | |||||
| xstream.processAnnotations(clz); | |||||
| xstream.processAnnotations(getInnerClasses(clz)); | |||||
| if (clz.equals(WxOpenXmlMessage.class)) { | |||||
| // 操蛋的微信,模板消息推送成功的消息是MsgID,其他消息推送过来是MsgId | |||||
| xstream.aliasField("MsgID", WxOpenXmlMessage.class, "msgId"); | |||||
| } | |||||
| register(clz, xstream); | |||||
| } | |||||
| private static Class<?>[] getInnerClasses(Class<?> clz) { | |||||
| Class<?>[] innerClasses = clz.getClasses(); | |||||
| if (innerClasses == null) { | |||||
| return null; | |||||
| } | |||||
| List<Class<?>> result = new ArrayList<>(); | |||||
| result.addAll(Arrays.asList(innerClasses)); | |||||
| for (Class<?> inner : innerClasses) { | |||||
| Class<?>[] innerClz = getInnerClasses(inner); | |||||
| if (innerClz == null) { | |||||
| continue; | |||||
| } | |||||
| result.addAll(Arrays.asList(innerClz)); | |||||
| } | |||||
| return result.toArray(new Class<?>[0]); | |||||
| } | |||||
| } | |||||