Browse Source

Merge branch 'develop'

master
Binary Wang 7 years ago
parent
commit
491095f1d5
100 changed files with 4185 additions and 1663 deletions
  1. +91
    -66
      pom.xml
  2. +0
    -12
      weixin-java-common/build.gradle
  3. +57
    -3
      weixin-java-common/pom.xml
  4. +3
    -3
      weixin-java-common/src/main/java/me/chanjar/weixin/common/annotation/Required.java
  5. +1
    -41
      weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java
  6. +3
    -2
      weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMediaUploadResult.java
  7. +6
    -6
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java
  8. +1
    -1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java
  9. +18
    -16
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/ToStringUtils.java
  10. +11
    -2
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java
  11. +84
    -95
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
  12. +2
    -2
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java
  13. +8
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java
  14. +18
    -74
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaDownloadRequestExecutor.java
  15. +19
    -38
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java
  16. +39
    -7
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestExecutor.java
  17. +22
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java
  18. +18
    -32
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java
  19. +19
    -45
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java
  20. +61
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/WxDnsResolver.java
  21. +2
    -1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java
  22. +50
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java
  23. +306
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java
  24. +87
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java
  25. +55
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaUploadRequestExecutor.java
  26. +61
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java
  27. +35
    -3
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java
  28. +1
    -1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/InputStreamResponseHandler.java
  29. +1
    -1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java
  30. +81
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java
  31. +44
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java
  32. +48
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java
  33. +60
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java
  34. +89
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java
  35. +55
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaUploadRequestExecutor.java
  36. +117
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpProxyInfo.java
  37. +58
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java
  38. +58
    -0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java
  39. +0
    -1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java
  40. +6
    -1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java
  41. +4
    -4
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java
  42. +2
    -2
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java
  43. +8
    -6
      weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerTest.java
  44. +2
    -2
      weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxAccessTokenTest.java
  45. +2
    -2
      weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxErrorTest.java
  46. +57
    -59
      weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxMenuTest.java
  47. +3
    -4
      weixin-java-common/src/test/java/me/chanjar/weixin/common/session/SessionTest.java
  48. +36
    -0
      weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/SHA1Test.java
  49. +3
    -4
      weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java
  50. +3
    -7
      weixin-java-common/src/test/resources/logback-test.xml
  51. +2
    -2
      weixin-java-common/src/test/resources/testng.xml
  52. +0
    -12
      weixin-java-cp/build.gradle
  53. +23
    -2
      weixin-java-cp/pom.xml
  54. +57
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpDepartmentService.java
  55. +57
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java
  56. +91
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMenuService.java
  57. +67
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java
  58. +153
    -245
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java
  59. +0
    -697
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java
  60. +66
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTagService.java
  61. +83
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java
  62. +543
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/AbstractWxCpServiceImpl.java
  63. +65
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpDepartmentServiceImpl.java
  64. +50
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java
  65. +64
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMenuServiceImpl.java
  66. +66
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOAuth2ServiceImpl.java
  67. +105
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java
  68. +11
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java
  69. +76
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java
  70. +104
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java
  71. +116
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTagServiceImpl.java
  72. +118
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java
  73. +7
    -7
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java
  74. +8
    -8
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java
  75. +103
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessageSendResult.java
  76. +2
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTag.java
  77. +85
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagAddOrRemoveUsersResult.java
  78. +70
    -21
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
  79. +48
    -50
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java
  80. +0
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java
  81. +1
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java
  82. +3
    -4
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessage.java
  83. +0
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java
  84. +0
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java
  85. +0
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java
  86. +1
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java
  87. +0
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/NewArticle.java
  88. +1
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java
  89. +2
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
  90. +2
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java
  91. +12
    -2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java
  92. +2
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageHandler.java
  93. +2
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageInterceptor.java
  94. +1
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageMatcher.java
  95. +18
    -4
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java
  96. +7
    -3
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouterRule.java
  97. +51
    -47
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpCryptUtil.java
  98. +1
    -1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java
  99. +2
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java
  100. +24
    -0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMenuGsonAdapter.java

+ 91
- 66
pom.xml View File

@@ -1,11 +1,12 @@
<?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">
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.6.0</version>
<version>2.7.0</version>
<packaging>pom</packaging>
<name>WeiXin Java Tools - Parent</name>
<description>微信公众号、企业号上级POM</description>
@@ -74,6 +75,11 @@
<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>
@@ -87,6 +93,7 @@
<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>

@@ -98,73 +105,90 @@
<downloadJavadocs>true</downloadJavadocs>
<downloadSources>true</downloadSources>
<httpclient.version>4.5</httpclient.version>
<slf4j.version>1.7.10</slf4j.version>
<logback.version>1.1.2</logback.version>
<gson.version>2.7</gson.version>
<guava.version>19.0</guava.version>
<commons-lang3.version>3.5</commons-lang3.version>
<commons-io.version>2.5</commons-io.version>
<commons-codec.version>1.10</commons-codec.version>
<jetty.version>9.3.0.RC0</jetty.version>
<jedis.version>2.9.0</jedis.version>
</properties>

<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.9</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
<version>${httpclient.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>${commons-codec.version}</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>${commons-lang3.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
</dependencies>

<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>
@@ -174,7 +198,7 @@
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>6.8.7</version>
<version>6.10</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -198,7 +222,8 @@
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>${jedis.version}</version>
<version>2.9.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
</dependencyManagement>


+ 0
- 12
weixin-java-common/build.gradle View File

@@ -1,12 +0,0 @@

description = 'WeiXin Java Tools - Common'
dependencies {
compile group: 'com.thoughtworks.xstream', name: 'xstream', version:'1.4.7'
testCompile group: 'junit', name: 'junit', version:'4.11'
testCompile group: 'org.testng', name: 'testng', version:'6.8.7'
testCompile group: 'org.mockito', name: 'mockito-all', version:'1.9.5'
testCompile group: 'com.google.inject', name: 'guice', version:'3.0'
testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.RC0'
testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.RC0'
}
test.useTestNG()

+ 57
- 3
weixin-java-common/pom.xml View File

@@ -1,12 +1,13 @@
<?xml version="1.0"?>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
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.6.0</version>
<version>2.7.0</version>
</parent>

<artifactId>weixin-java-common</artifactId>
@@ -14,6 +15,59 @@
<description>微信公众号、企业号Java SDK Common</description>

<dependencies>
<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.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>

<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>


+ 3
- 3
weixin-java-common/src/main/java/me/chanjar/weixin/common/annotation/Required.java View File

@@ -7,13 +7,13 @@ import java.lang.annotation.Target;

/**
* 标识某个字段是否是必填的
*
* <p>
* Created by Binary Wang on 2016/9/25.
* @author binarywang (https://github.com/binarywang)
*
* @author binarywang (https://github.com/binarywang)
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Required {

}
}

+ 1
- 41
weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java View File

@@ -24,7 +24,6 @@ public class WxConsts {
public static final String XML_MSG_HARDWARE = "hardware";
public static final String XML_TRANSFER_CUSTOMER_SERVICE = "transfer_customer_service";


///////////////////////
// 主动发送消息(即客服消息)的消息类型
///////////////////////
@@ -88,45 +87,6 @@ public class WxConsts {
public static final String EVT_LOCATION_SELECT = "location_select";
public static final String EVT_TEMPLATESENDJOBFINISH = "TEMPLATESENDJOBFINISH";
public static final String EVT_ENTER_AGENT = "enter_agent";
public static final String EVT_CARD_PASS_CHECK = "card_pass_check";
public static final String EVT_CARD_NOT_PASS_CHECK = "card_not_pass_check";
public static final String EVT_USER_GET_CARD = "user_get_card";
public static final String EVT_USER_DEL_CARD = "user_del_card";
public static final String EVT_USER_CONSUME_CARD = "user_consume_card";
public static final String EVT_USER_PAY_FROM_PAY_CELL = "user_pay_from_pay_cell";
public static final String EVT_USER_VIEW_CARD = "user_view_card";
public static final String EVT_USER_ENTER_SESSION_FROM_CARD = "user_enter_session_from_card";
public static final String EVT_CARD_SKU_REMIND = "card_sku_remind"; // 库存报警
public static final String EVT_KF_CREATE_SESSION = "kf_create_session"; // 客服接入会话
public static final String EVT_KF_CLOSE_SESSION = "kf_close_session"; // 客服关闭会话
public static final String EVT_KF_SWITCH_SESSION = "kf_switch_session"; // 客服转接会话
public static final String EVT_POI_CHECK_NOTIFY = "poi_check_notify"; //门店审核事件推送
public static final String EVN_SUBMIT_MEMBERCARD_USER_INFO = "submit_membercard_user_info"; //接收会员信息事件推送
//以下为微信认证事件
/**
* 资质认证成功
*/
public static final String EVT_QUALIFICATION_VERIFY_SUCCESS = "qualification_verify_success";
/**
* 资质认证失败
*/
public static final String EVT_QUALIFICATION_VERIFY_FAIL = "qualification_verify_fail";
/**
* 名称认证成功
*/
public static final String EVT_NAMING_VERIFY_SUCCESS = "naming_verify_success";
/**
* 名称认证失败
*/
public static final String EVT_NAMING_VERIFY_FAIL = "naming_verify_fail";
/**
* 年审通知
*/
public static final String EVT_ANNUAL_RENEW = "annual_renew";
/**
* 认证过期失效通知
*/
public static final String EVT_VERIFY_EXPIRED = "verify_expired";

///////////////////////
// 上传多媒体文件的类型
@@ -185,7 +145,7 @@ public class WxConsts {
* 跳转图文消息URL
*/
public static final String BUTTON_VIEW_LIMITED = "view_limited";
/**
* 不弹出授权页面,直接跳转,只能获取用户openid
*/


+ 3
- 2
weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/result/WxMediaUploadResult.java View File

@@ -1,6 +1,8 @@
package me.chanjar.weixin.common.bean.result;

import me.chanjar.weixin.common.util.json.WxGsonBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import java.io.Serializable;

@@ -50,8 +52,7 @@ public class WxMediaUploadResult implements Serializable {

@Override
public String toString() {
return "WxUploadResult [type=" + this.type + ", media_id=" + this.mediaId + ", thumb_media_id=" + this.thumbMediaId
+ ", created_at=" + this.createdAt + "]";
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}

}

+ 6
- 6
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java View File

@@ -14,7 +14,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
public class StandardSessionManager implements WxSessionManager, InternalSessionManager {

protected static final StringManager sm =
StringManager.getManager(Constants.Package);
StringManager.getManager(Constants.Package);
/**
* The descriptive name of this Manager implementation (for logging).
*/
@@ -82,7 +82,7 @@ public class StandardSessionManager implements WxSessionManager, InternalSession
public WxSession getSession(String sessionId, boolean create) {
if (sessionId == null) {
throw new IllegalStateException
(sm.getString("sessionManagerImpl.getSession.ise"));
(sm.getString("sessionManagerImpl.getSession.ise"));
}

InternalSession session = findSession(sessionId);
@@ -135,15 +135,15 @@ public class StandardSessionManager implements WxSessionManager, InternalSession
public InternalSession createSession(String sessionId) {
if (sessionId == null) {
throw new IllegalStateException
(sm.getString("sessionManagerImpl.createSession.ise"));
(sm.getString("sessionManagerImpl.createSession.ise"));
}

if ((this.maxActiveSessions >= 0) &&
(getActiveSessions() >= this.maxActiveSessions)) {
(getActiveSessions() >= this.maxActiveSessions)) {
this.rejectedSessions++;
throw new TooManyActiveSessionsException(
sm.getString("sessionManagerImpl.createSession.tmase"),
this.maxActiveSessions);
sm.getString("sessionManagerImpl.createSession.tmase"),
this.maxActiveSessions);
}

// Recycle or create a Session instance


+ 1
- 1
weixin-java-common/src/main/java/me/chanjar/weixin/common/session/TooManyActiveSessionsException.java View File

@@ -21,7 +21,7 @@ package me.chanjar.weixin.common.session;
* reached and the server is refusing to create any new sessions.
*/
public class TooManyActiveSessionsException
extends IllegalStateException {
extends IllegalStateException {
private static final long serialVersionUID = 1L;

/**


+ 18
- 16
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/ToStringUtils.java View File

@@ -13,25 +13,10 @@ import org.apache.commons.lang3.builder.ToStringStyle;
*/
public class ToStringUtils {
public static final ToStringStyle THE_STYLE = new SimpleMultiLineToStringStyle();
private static class SimpleMultiLineToStringStyle extends ToStringStyle {
private static final long serialVersionUID = 4645306494220335355L;
private static final String LINE_SEPARATOR = "\n";
private static final String NULL_TEXT = "<null>";

public SimpleMultiLineToStringStyle() {
super();
this.setContentStart("[");
this.setFieldSeparator(LINE_SEPARATOR + " ");
this.setFieldSeparatorAtStart(true);
this.setContentEnd(LINE_SEPARATOR + "]");
this.setNullText(NULL_TEXT);
this.setUseShortClassName(true);
this.setUseIdentityHashCode(false);
}
}

/**
* 用于产生去掉空值属性并以换行符分割各属性键值的toString字符串
*
* @param obj
*/
public static String toSimpleString(Object obj) {
@@ -58,4 +43,21 @@ public class ToStringUtils {

return result.deleteCharAt(result.length() - 1).toString();
}

private static class SimpleMultiLineToStringStyle extends ToStringStyle {
private static final long serialVersionUID = 4645306494220335355L;
private static final String LINE_SEPARATOR = "\n";
private static final String NULL_TEXT = "<null>";

public SimpleMultiLineToStringStyle() {
super();
this.setContentStart("[");
this.setFieldSeparator(LINE_SEPARATOR + " ");
this.setFieldSeparatorAtStart(true);
this.setContentEnd(LINE_SEPARATOR + "]");
this.setNullText(NULL_TEXT);
this.setUseShortClassName(true);
this.setUseIdentityHashCode(false);
}
}
}

+ 11
- 2
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/SHA1.java View File

@@ -1,8 +1,9 @@
package me.chanjar.weixin.common.util.crypto;

import java.util.Arrays;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;

import java.util.Arrays;

/**
* Created by Daniel Qian on 14/10/19.
@@ -13,6 +14,10 @@ public class SHA1 {
* 串接arr参数,生成sha1 digest
*/
public static String gen(String... arr) {
if (StringUtils.isAnyEmpty(arr)) {
throw new IllegalArgumentException("非法请求参数,有部分参数为空 : " + Arrays.toString(arr));
}

Arrays.sort(arr);
StringBuilder sb = new StringBuilder();
for (String a : arr) {
@@ -25,6 +30,10 @@ public class SHA1 {
* 用&串接arr参数,生成sha1 digest
*/
public static String genWithAmple(String... arr) {
if (StringUtils.isAnyEmpty(arr)) {
throw new IllegalArgumentException("非法请求参数,有部分参数为空 : " + Arrays.toString(arr));
}

Arrays.sort(arr);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {


+ 84
- 95
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java View File

@@ -1,26 +1,9 @@
/**
* 对公众平台发送给公众账号的消息加解密示例代码.
*
* @copyright Copyright (c) 1998-2014 Tencent Inc.
* <p>
* 针对org.apache.commons.codec.binary.Base64,
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
*/

// ------------------------------------------------------------------------

/**
* 针对org.apache.commons.codec.binary.Base64,
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
*/
package me.chanjar.weixin.common.util.crypto;

import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Random;
import org.apache.commons.codec.binary.Base64;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
@@ -28,12 +11,19 @@ import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Random;

import org.apache.commons.codec.binary.Base64;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;

/**
* 对公众平台发送给公众账号的消息加解密示例代码.
*
* @copyright Copyright (c) 1998-2014 Tencent Inc.
* 针对org.apache.commons.codec.binary.Base64,
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
*/
public class WxCryptUtil {

private static final Base64 base64 = new Base64();
@@ -61,9 +51,9 @@ public class WxCryptUtil {
/**
* 构造函数
*
* @param token 公众平台上,开发者设置的token
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
* @param appidOrCorpid 公众平台appid/corpid
* @param token 公众平台上,开发者设置的token
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
* @param appidOrCorpid 公众平台appid/corpid
*/
public WxCryptUtil(String token, String encodingAesKey,
String appidOrCorpid) {
@@ -84,6 +74,66 @@ public class WxCryptUtil {
}
}

/**
* 将一个数字转换成生成4个字节的网络字节序bytes数组
*
* @param number
*/
private static byte[] number2BytesInNetworkOrder(int number) {
byte[] orderBytes = new byte[4];
orderBytes[3] = (byte) (number & 0xFF);
orderBytes[2] = (byte) (number >> 8 & 0xFF);
orderBytes[1] = (byte) (number >> 16 & 0xFF);
orderBytes[0] = (byte) (number >> 24 & 0xFF);
return orderBytes;
}

/**
* 4个字节的网络字节序bytes数组还原成一个数字
*
* @param bytesInNetworkOrder
*/
private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) {
int sourceNumber = 0;
for (int i = 0; i < 4; i++) {
sourceNumber <<= 8;
sourceNumber |= bytesInNetworkOrder[i] & 0xff;
}
return sourceNumber;
}

/**
* 随机生成16位字符串
*/
private static String genRandomStr() {
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 16; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}

/**
* 生成xml消息
*
* @param encrypt 加密后的消息密文
* @param signature 安全签名
* @param timestamp 时间戳
* @param nonce 随机字符串
* @return 生成的xml字符串
*/
private static String generateXml(String encrypt, String signature,
String timestamp, String nonce) {
String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n"
+ "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n"
+ "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n"
+ "</xml>";
return String.format(format, encrypt, signature, timestamp, nonce);
}

/**
* 将公众平台回复用户的消息加密打包.
* <ol>
@@ -119,7 +169,7 @@ public class WxCryptUtil {
byte[] randomStringBytes = randomStr.getBytes(CHARSET);
byte[] plainTextBytes = plainText.getBytes(CHARSET);
byte[] bytesOfSizeInNetworkOrder = number2BytesInNetworkOrder(
plainTextBytes.length);
plainTextBytes.length);
byte[] appIdBytes = this.appidOrCorpid.getBytes(CHARSET);

// randomStr + networkBytesOrder + text + appid
@@ -168,8 +218,7 @@ public class WxCryptUtil {
* @param encryptedXml 密文,对应POST请求的数据
* @return 解密后的原文
*/
public String decrypt(String msgSignature, String timeStamp, String nonce,
String encryptedXml) {
public String decrypt(String msgSignature, String timeStamp, String nonce, String encryptedXml) {
// 密钥,公众账号的app corpSecret
// 提取密文
String cipherText = extractEncryptPart(encryptedXml);
@@ -198,7 +247,7 @@ public class WxCryptUtil {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec key_spec = new SecretKeySpec(this.aesKey, "AES");
IvParameterSpec iv = new IvParameterSpec(
Arrays.copyOfRange(this.aesKey, 0, 16));
Arrays.copyOfRange(this.aesKey, 0, 16));
cipher.init(Cipher.DECRYPT_MODE, key_spec, iv);

// 使用BASE64对密文进行解码
@@ -221,9 +270,9 @@ public class WxCryptUtil {
int xmlLength = bytesNetworkOrder2Number(networkOrder);

xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength),
CHARSET);
CHARSET);
from_appid = new String(
Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);
Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -237,64 +286,4 @@ public class WxCryptUtil {

}

/**
* 将一个数字转换成生成4个字节的网络字节序bytes数组
*
* @param number
*/
private static byte[] number2BytesInNetworkOrder(int number) {
byte[] orderBytes = new byte[4];
orderBytes[3] = (byte) (number & 0xFF);
orderBytes[2] = (byte) (number >> 8 & 0xFF);
orderBytes[1] = (byte) (number >> 16 & 0xFF);
orderBytes[0] = (byte) (number >> 24 & 0xFF);
return orderBytes;
}

/**
* 4个字节的网络字节序bytes数组还原成一个数字
*
* @param bytesInNetworkOrder
*/
private static int bytesNetworkOrder2Number(byte[] bytesInNetworkOrder) {
int sourceNumber = 0;
for (int i = 0; i < 4; i++) {
sourceNumber <<= 8;
sourceNumber |= bytesInNetworkOrder[i] & 0xff;
}
return sourceNumber;
}

/**
* 随机生成16位字符串
*/
private static String genRandomStr() {
String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 16; i++) {
int number = random.nextInt(base.length());
sb.append(base.charAt(number));
}
return sb.toString();
}

/**
* 生成xml消息
*
* @param encrypt 加密后的消息密文
* @param signature 安全签名
* @param timestamp 时间戳
* @param nonce 随机字符串
* @return 生成的xml字符串
*/
private static String generateXml(String encrypt, String signature,
String timestamp, String nonce) {
String format = "<xml>\n" + "<Encrypt><![CDATA[%1$s]]></Encrypt>\n"
+ "<MsgSignature><![CDATA[%2$s]]></MsgSignature>\n"
+ "<TimeStamp>%3$s</TimeStamp>\n" + "<Nonce><![CDATA[%4$s]]></Nonce>\n"
+ "</xml>";
return String.format(format, encrypt, signature, timestamp, nonce);
}

}

+ 2
- 2
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java View File

@@ -25,7 +25,7 @@ public class FileUtils {
}

tmpFile.deleteOnExit();
try (FileOutputStream fos = new FileOutputStream(tmpFile)) {
int read = 0;
byte[] bytes = new byte[1024 * 100];
@@ -35,7 +35,7 @@ public class FileUtils {

fos.flush();
return tmpFile;
}
}
}

/**


+ 8
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpType.java View File

@@ -0,0 +1,8 @@
package me.chanjar.weixin.common.util.http;

/**
* Created by ecoolper on 2017/4/28.
*/
public enum HttpType {
JODD_HTTP, APACHE_HTTP, OK_HTTP;
}

+ 18
- 74
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaDownloadRequestExecutor.java View File

@@ -1,93 +1,37 @@
package me.chanjar.weixin.common.util.http;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.fs.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;
import me.chanjar.weixin.common.util.http.apache.ApacheMediaDownloadRequestExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaDownloadRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaDownloadRequestExecutor;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* 下载媒体文件请求执行器,请求的参数是String, 返回的结果是File
* 视频文件不支持下载
*
* @author Daniel Qian
*/
public class MediaDownloadRequestExecutor implements RequestExecutor<File, String> {
public abstract class MediaDownloadRequestExecutor<H, P> implements RequestExecutor<File, String> {

private File tmpDirFile;

public MediaDownloadRequestExecutor() {
}

public MediaDownloadRequestExecutor(File tmpDirFile) {
protected RequestHttp<H, P> requestHttp;
protected File tmpDirFile;
public MediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
this.requestHttp = requestHttp;
this.tmpDirFile = tmpDirFile;
}

@Override
public File execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException {
if (queryParam != null) {
if (uri.indexOf('?') == -1) {
uri += '?';
}
uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
}

HttpGet httpGet = new HttpGet(uri);
if (httpProxy != null) {
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
httpGet.setConfig(config);
}

try (CloseableHttpResponse response = httpclient.execute(httpGet);
InputStream inputStream = InputStreamResponseHandler.INSTANCE
.handleResponse(response)) {

Header[] contentTypeHeader = response.getHeaders("Content-Type");
if (contentTypeHeader != null && contentTypeHeader.length > 0) {
if (contentTypeHeader[0].getValue().startsWith(ContentType.APPLICATION_JSON.getMimeType())) {
// application/json; encoding=utf-8 下载媒体文件出错
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
throw new WxErrorException(WxError.fromJson(responseContent));
}
}

String fileName = getFileName(response);
if (StringUtils.isBlank(fileName)) {
public static RequestExecutor<File, String> create(RequestHttp requestHttp, File tmpDirFile) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
return new ApacheMediaDownloadRequestExecutor(requestHttp, tmpDirFile);
case JODD_HTTP:
return new JoddHttpMediaDownloadRequestExecutor(requestHttp, tmpDirFile);
case OK_HTTP:
return new OkHttpMediaDownloadRequestExecutor(requestHttp, tmpDirFile);
default:
return null;
}

String[] nameAndExt = fileName.split("\\.");
return FileUtils.createTmpFile(inputStream, nameAndExt[0], nameAndExt[1], this.tmpDirFile);

} finally {
httpGet.releaseConnection();
}

}

private String getFileName(CloseableHttpResponse response) throws WxErrorException {
Header[] contentDispositionHeader = response.getHeaders("Content-disposition");
if(contentDispositionHeader == null || contentDispositionHeader.length == 0){
throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build());
}

Pattern p = Pattern.compile(".*filename=\"(.*)\"");
Matcher m = p.matcher(contentDispositionHeader[0].getValue());
if(m.matches()){
return m.group(1);
}
throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build());
}

}

+ 19
- 38
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/MediaUploadRequestExecutor.java View File

@@ -1,53 +1,34 @@
package me.chanjar.weixin.common.util.http;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.exception.WxErrorException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import me.chanjar.weixin.common.util.http.apache.ApacheMediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpMediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpMediaUploadRequestExecutor;

import java.io.File;
import java.io.IOException;

/**
* 上传媒体文件请求执行器,请求的参数是File, 返回的结果是String
*
* @author Daniel Qian
*/
public class MediaUploadRequestExecutor implements RequestExecutor<WxMediaUploadResult, File> {
public abstract class MediaUploadRequestExecutor<H, P> implements RequestExecutor<WxMediaUploadResult, File> {
protected RequestHttp<H, P> requestHttp;

@Override
public WxMediaUploadResult execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, File file) throws WxErrorException, IOException {
HttpPost httpPost = new HttpPost(uri);
if (httpProxy != null) {
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
httpPost.setConfig(config);
}
if (file != null) {
HttpEntity entity = MultipartEntityBuilder
.create()
.addBinaryBody("media", file)
.setMode(HttpMultipartMode.RFC6532)
.build();
httpPost.setEntity(entity);
httpPost.setHeader("Content-Type", ContentType.MULTIPART_FORM_DATA.toString());
}
try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return WxMediaUploadResult.fromJson(responseContent);
} finally {
httpPost.releaseConnection();
public MediaUploadRequestExecutor(RequestHttp requestHttp) {
this.requestHttp = requestHttp;
}

public static RequestExecutor<WxMediaUploadResult, File> create(RequestHttp requestHttp) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
return new ApacheMediaUploadRequestExecutor(requestHttp);
case JODD_HTTP:
return new JoddHttpMediaUploadRequestExecutor(requestHttp);
case OK_HTTP:
return new OkHttpMediaUploadRequestExecutor(requestHttp);
default:
return null;
}
}



+ 39
- 7
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestExecutor.java View File

@@ -1,8 +1,6 @@
package me.chanjar.weixin.common.util.http;

import me.chanjar.weixin.common.exception.WxErrorException;
import org.apache.http.HttpHost;
import org.apache.http.impl.client.CloseableHttpClient;

import java.io.IOException;

@@ -15,13 +13,47 @@ import java.io.IOException;
public interface RequestExecutor<T, E> {

/**
* @param httpclient 传入的httpClient
* @param httpProxy http代理对象,如果没有配置代理则为空
* @param uri uri
* @param data 数据
* @param uri uri
* @param data 数据
* @throws WxErrorException
* @throws IOException
*/
T execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, E data) throws WxErrorException, IOException;
T execute(String uri, E data) throws WxErrorException, IOException;

/**
* apache-http实现方式
* @param httpclient
* @param httpProxy
* @param uri
* @param data
* @return
* @throws WxErrorException
* @throws IOException
*//*
T executeApache(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, E data) throws WxErrorException, IOException;

*//**
* jodd-http实现方式
* @param provider
* @param proxyInfo
* @param uri
* @param data
* @return
* @throws WxErrorException
* @throws IOException
*//*
T executeJodd(HttpConnectionProvider provider, ProxyInfo proxyInfo, String uri, E data) throws WxErrorException, IOException;


*//** okhttp实现方式
* @param pool
* @param proxyInfo
* @param uri
* @param data
* @return
* @throws WxErrorException
* @throws IOException
*//*
T executeOkhttp(ConnectionPool pool, final OkHttpProxyInfo proxyInfo, String uri, E data) throws WxErrorException, IOException;
*/
}

+ 22
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/RequestHttp.java View File

@@ -0,0 +1,22 @@
package me.chanjar.weixin.common.util.http;

/**
* Created by ecoolper on 2017/4/22.
*/
public interface RequestHttp<H, P> {

/**
* 返回httpClient
*
*/
H getRequestHttpClient();

/**
* 返回httpProxy
*
*/
P getRequestHttpProxy();

HttpType getRequestType();

}

+ 18
- 32
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java View File

@@ -1,45 +1,31 @@
package me.chanjar.weixin.common.util.http;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;

import java.io.IOException;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientSimpleGetRequestExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimpleGetRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimpleGetRequestExecutor;

/**
* 简单的GET请求执行器,请求的参数是String, 返回的结果也是String
*
* @author Daniel Qian
*/
public class SimpleGetRequestExecutor implements RequestExecutor<String, String> {
public abstract class SimpleGetRequestExecutor<H, P> implements RequestExecutor<String, String> {
protected RequestHttp<H, P> requestHttp;

@Override
public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String queryParam) throws WxErrorException, IOException {
if (queryParam != null) {
if (uri.indexOf('?') == -1) {
uri += '?';
}
uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
}
HttpGet httpGet = new HttpGet(uri);
if (httpProxy != null) {
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
httpGet.setConfig(config);
}
public SimpleGetRequestExecutor(RequestHttp<H, P> requestHttp) {
this.requestHttp = requestHttp;
}

try (CloseableHttpResponse response = httpclient.execute(httpGet)) {
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return responseContent;
} finally {
httpGet.releaseConnection();
public static RequestExecutor<String, String> create(RequestHttp requestHttp) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
return new ApacheHttpClientSimpleGetRequestExecutor(requestHttp);
case JODD_HTTP:
return new JoddHttpSimpleGetRequestExecutor(requestHttp);
case OK_HTTP:
return new OkHttpSimpleGetRequestExecutor(requestHttp);
default:
throw new IllegalArgumentException("非法请求参数");
}
}



+ 19
- 45
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java View File

@@ -1,58 +1,32 @@
package me.chanjar.weixin.common.util.http;

import java.io.IOException;

import org.apache.http.Consts;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.apache.ApacheSimplePostRequestExecutor;
import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimplePostRequestExecutor;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimplePostRequestExecutor;

/**
* 用装饰模式实现
* 简单的POST请求执行器,请求的参数是String, 返回的结果也是String
*
* @author Daniel Qian
*/
public class SimplePostRequestExecutor implements RequestExecutor<String, String> {

@Override
public String execute(CloseableHttpClient httpclient, HttpHost httpProxy, String uri, String postEntity) throws WxErrorException, IOException {
HttpPost httpPost = new HttpPost(uri);
if (httpProxy != null) {
RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
httpPost.setConfig(config);
}
public abstract class SimplePostRequestExecutor<H, P> implements RequestExecutor<String, String> {
protected RequestHttp<H, P> requestHttp;

if (postEntity != null) {
StringEntity entity = new StringEntity(postEntity, Consts.UTF_8);
httpPost.setEntity(entity);
}

try (CloseableHttpResponse response = httpclient.execute(httpPost)) {
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
if (responseContent.isEmpty()) {
throw new WxErrorException(
WxError.newBuilder().setErrorCode(9999).setErrorMsg("无响应内容")
.build());
}

if (responseContent.startsWith("<xml>")) {
//xml格式输出直接返回
return responseContent;
}
public SimplePostRequestExecutor(RequestHttp requestHttp) {
this.requestHttp = requestHttp;
}

WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return responseContent;
} finally {
httpPost.releaseConnection();
public static RequestExecutor<String, String> create(RequestHttp requestHttp) {
switch (requestHttp.getRequestType()) {
case APACHE_HTTP:
return new ApacheSimplePostRequestExecutor(requestHttp);
case JODD_HTTP:
return new JoddHttpSimplePostRequestExecutor(requestHttp);
case OK_HTTP:
return new OkHttpSimplePostRequestExecutor(requestHttp);
default:
return null;
}
}



+ 61
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/WxDnsResolver.java View File

@@ -0,0 +1,61 @@
package me.chanjar.weixin.common.util.http;

import org.apache.http.conn.DnsResolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;

/**
* 微信DNS域名解析器,将微信域名绑定到指定IP
* --------------------------------------------
* 适用于服务器端调用微信服务器需要开通出口防火墙情况
* <p>
* Created by Andy Huo on 17/03/28.
*/
public class WxDnsResolver implements DnsResolver {

private final static String WECHAT_API_URL = "api.weixin.qq.com";
private static Map<String, InetAddress[]> MAPPINGS = new HashMap<String, InetAddress[]>();
protected final Logger log = LoggerFactory.getLogger(WxDnsResolver.class);
private String wxApiIp;

public WxDnsResolver(String ip) {

this.wxApiIp = ip;
this.init();
}

private void init() {
if (log.isDebugEnabled()) {
log.debug("init wechat dns config with ip {}", wxApiIp);
}
try {
MAPPINGS.put(WECHAT_API_URL, new InetAddress[]{InetAddress.getByName(wxApiIp)});
} catch (UnknownHostException e) {
//如果初始化DNS配置失败则使用默认配置,不影响服务的启动
log.error("init WxDnsResolver error", e);
MAPPINGS = new HashMap<String, InetAddress[]>();
}

}

@Override
public InetAddress[] resolve(String host) throws UnknownHostException {


return MAPPINGS.containsKey(host) ? MAPPINGS.get(host) : new InetAddress[0];
}

public String getWxApiIp() {
return wxApiIp;
}

public void setWxApiIp(String wxApiIp) {
this.wxApiIp = wxApiIp;
this.init();
}
}

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/ApacheHttpClientBuilder.java → weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java View File

@@ -1,10 +1,11 @@
package me.chanjar.weixin.common.util.http;
package me.chanjar.weixin.common.util.http.apache;

import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;

/**
* httpclient build interface
*
* @author kakotor
*/
public interface ApacheHttpClientBuilder {

+ 50
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java View File

@@ -0,0 +1,50 @@
package me.chanjar.weixin.common.util.http.apache;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;

import java.io.IOException;

/**
* Created by ecoolper on 2017/5/4.
*/
public class ApacheHttpClientSimpleGetRequestExecutor extends SimpleGetRequestExecutor<CloseableHttpClient, HttpHost> {

public ApacheHttpClientSimpleGetRequestExecutor(RequestHttp requestHttp) {
super(requestHttp);
}

@Override
public String execute(String uri, String queryParam) throws WxErrorException, IOException {
if (queryParam != null) {
if (uri.indexOf('?') == -1) {
uri += '?';
}
uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
}
HttpGet httpGet = new HttpGet(uri);
if (requestHttp.getRequestHttpProxy() != null) {
RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
httpGet.setConfig(config);
}

try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet)) {
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return responseContent;
} finally {
httpGet.releaseConnection();
}
}

}

+ 306
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java View File

@@ -0,0 +1,306 @@
package me.chanjar.weixin.common.util.http.apache;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.annotation.NotThreadSafe;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.DnsResolver;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* httpclient 连接管理器 自带DNS解析
* <p>
* 大部分代码拷贝自:DefaultApacheHttpClientBuilder
*
* @author Andy.Huo
*/
@NotThreadSafe
public class ApacheHttpDnsClientBuilder implements ApacheHttpClientBuilder {
protected final Logger log = LoggerFactory.getLogger(ApacheHttpDnsClientBuilder.class);
private final AtomicBoolean prepared = new AtomicBoolean(false);
private int connectionRequestTimeout = 3000;
private int connectionTimeout = 5000;
private int soTimeout = 5000;
private int idleConnTimeout = 60000;
private int checkWaitTime = 60000;
private int maxConnPerHost = 10;
private int maxTotalConn = 50;
private String userAgent;

private DnsResolver dnsResover;

private HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
return false;
}
};
private SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory();
private PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory();
private String httpProxyHost;
private int httpProxyPort;
private String httpProxyUsername;
private String httpProxyPassword;

/**
* 闲置连接监控线程
*/
private IdleConnectionMonitorThread idleConnectionMonitorThread;
private HttpClientBuilder httpClientBuilder;

private ApacheHttpDnsClientBuilder() {
}

public static ApacheHttpDnsClientBuilder get() {
return new ApacheHttpDnsClientBuilder();
}

@Override
public ApacheHttpClientBuilder httpProxyHost(String httpProxyHost) {
this.httpProxyHost = httpProxyHost;
return this;
}

@Override
public ApacheHttpClientBuilder httpProxyPort(int httpProxyPort) {
this.httpProxyPort = httpProxyPort;
return this;
}

@Override
public ApacheHttpClientBuilder httpProxyUsername(String httpProxyUsername) {
this.httpProxyUsername = httpProxyUsername;
return this;
}

@Override
public ApacheHttpClientBuilder httpProxyPassword(String httpProxyPassword) {
this.httpProxyPassword = httpProxyPassword;
return this;
}

@Override
public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory) {
this.sslConnectionSocketFactory = sslConnectionSocketFactory;
return this;
}

/**
* 获取链接的超时时间设置,默认3000ms
* <p>
* 设置为零时不超时,一直等待. 设置为负数是使用系统默认设置(非上述的3000ms的默认值,而是httpclient的默认设置).
* </p>
*
* @param connectionRequestTimeout 获取链接的超时时间设置(单位毫秒),默认3000ms
*/
public void setConnectionRequestTimeout(int connectionRequestTimeout) {
this.connectionRequestTimeout = connectionRequestTimeout;
}

/**
* 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用
* <p>
* 设置为零时不超时,一直等待. 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置).
* </p>
*
* @param connectionTimeout 建立链接的超时时间设置(单位毫秒),默认5000ms
*/
public void setConnectionTimeout(int connectionTimeout) {
this.connectionTimeout = connectionTimeout;
}

/**
* 默认NIO的socket超时设置,默认5000ms.
*
* @param soTimeout 默认NIO的socket超时设置,默认5000ms.
* @see java.net.SocketOptions#SO_TIMEOUT
*/
public void setSoTimeout(int soTimeout) {
this.soTimeout = soTimeout;
}

/**
* 空闲链接的超时时间,默认60000ms.
* <p>
* 超时的链接将在下一次空闲链接检查是被销毁
* </p>
*
* @param idleConnTimeout 空闲链接的超时时间,默认60000ms.
*/
public void setIdleConnTimeout(int idleConnTimeout) {
this.idleConnTimeout = idleConnTimeout;
}

/**
* 检查空间链接的间隔周期,默认60000ms.
*
* @param checkWaitTime 检查空间链接的间隔周期,默认60000ms.
*/
public void setCheckWaitTime(int checkWaitTime) {
this.checkWaitTime = checkWaitTime;
}

/**
* 每路的最大链接数,默认10
*
* @param maxConnPerHost 每路的最大链接数,默认10
*/
public void setMaxConnPerHost(int maxConnPerHost) {
this.maxConnPerHost = maxConnPerHost;
}

/**
* 最大总连接数,默认50
*
* @param maxTotalConn 最大总连接数,默认50
*/
public void setMaxTotalConn(int maxTotalConn) {
this.maxTotalConn = maxTotalConn;
}

/**
* 自定义httpclient的User Agent
*
* @param userAgent User Agent
*/
public void setUserAgent(String userAgent) {
this.userAgent = userAgent;
}

public IdleConnectionMonitorThread getIdleConnectionMonitorThread() {
return this.idleConnectionMonitorThread;
}

private synchronized void prepare() {
if (prepared.get()) {
return;
}
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", this.plainConnectionSocketFactory).register("https", this.sslConnectionSocketFactory)
.build();

@SuppressWarnings("resource")
PoolingHttpClientConnectionManager connectionManager;
if (dnsResover != null) {
if (log.isDebugEnabled()) {
log.debug("specified dns resolver.");
}
connectionManager = new PoolingHttpClientConnectionManager(registry, dnsResover);
} else {
if (log.isDebugEnabled()) {
log.debug("Not specified dns resolver.");
}
connectionManager = new PoolingHttpClientConnectionManager(registry);
}

connectionManager.setMaxTotal(this.maxTotalConn);
connectionManager.setDefaultMaxPerRoute(this.maxConnPerHost);
connectionManager
.setDefaultSocketConfig(SocketConfig.copy(SocketConfig.DEFAULT).setSoTimeout(this.soTimeout).build());

this.idleConnectionMonitorThread = new IdleConnectionMonitorThread(connectionManager, this.idleConnTimeout,
this.checkWaitTime);
this.idleConnectionMonitorThread.setDaemon(true);
this.idleConnectionMonitorThread.start();

this.httpClientBuilder = HttpClients.custom().setConnectionManager(connectionManager)
.setConnectionManagerShared(true)
.setDefaultRequestConfig(RequestConfig.custom().setSocketTimeout(this.soTimeout)
.setConnectTimeout(this.connectionTimeout)
.setConnectionRequestTimeout(this.connectionRequestTimeout).build())
.setRetryHandler(this.httpRequestRetryHandler);

if (StringUtils.isNotBlank(this.httpProxyHost) && StringUtils.isNotBlank(this.httpProxyUsername)) {
// 使用代理服务器 需要用户认证的代理服务器
CredentialsProvider provider = new BasicCredentialsProvider();
provider.setCredentials(new AuthScope(this.httpProxyHost, this.httpProxyPort),
new UsernamePasswordCredentials(this.httpProxyUsername, this.httpProxyPassword));
this.httpClientBuilder.setDefaultCredentialsProvider(provider);
}

if (StringUtils.isNotBlank(this.userAgent)) {
this.httpClientBuilder.setUserAgent(this.userAgent);
}
prepared.set(true);
}

@Override
public CloseableHttpClient build() {
if (!prepared.get()) {
prepare();
}
return this.httpClientBuilder.build();
}

public DnsResolver getDnsResover() {
return dnsResover;
}

public void setDnsResover(DnsResolver dnsResover) {
this.dnsResover = dnsResover;
}

public static class IdleConnectionMonitorThread extends Thread {
private final HttpClientConnectionManager connMgr;
private final int idleConnTimeout;
private final int checkWaitTime;
private volatile boolean shutdown;

public IdleConnectionMonitorThread(HttpClientConnectionManager connMgr, int idleConnTimeout,
int checkWaitTime) {
super("IdleConnectionMonitorThread");
this.connMgr = connMgr;
this.idleConnTimeout = idleConnTimeout;
this.checkWaitTime = checkWaitTime;
}

@Override
public void run() {
try {
while (!this.shutdown) {
synchronized (this) {
wait(this.checkWaitTime);
this.connMgr.closeExpiredConnections();
this.connMgr.closeIdleConnections(this.idleConnTimeout, TimeUnit.MILLISECONDS);
}
}
} catch (InterruptedException ignore) {
}
}

public void trigger() {
synchronized (this) {
notifyAll();
}
}

public void shutdown() {
this.shutdown = true;
synchronized (this) {
notifyAll();
}
}
}

}

+ 87
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java View File

@@ -0,0 +1,87 @@
package me.chanjar.weixin.common.util.http.apache;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.fs.FileUtils;
import me.chanjar.weixin.common.util.http.MediaDownloadRequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.CloseableHttpClient;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Created by ecoolper on 2017/5/5.
*/
public class ApacheMediaDownloadRequestExecutor extends MediaDownloadRequestExecutor<CloseableHttpClient, HttpHost> {


public ApacheMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
super(requestHttp, tmpDirFile);
}

@Override
public File execute(String uri, String queryParam) throws WxErrorException, IOException {
if (queryParam != null) {
if (uri.indexOf('?') == -1) {
uri += '?';
}
uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
}

HttpGet httpGet = new HttpGet(uri);
if (requestHttp.getRequestHttpProxy() != null) {
RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
httpGet.setConfig(config);
}

try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet);
InputStream inputStream = InputStreamResponseHandler.INSTANCE
.handleResponse(response)) {
Header[] contentTypeHeader = response.getHeaders("Content-Type");
if (contentTypeHeader != null && contentTypeHeader.length > 0) {
if (contentTypeHeader[0].getValue().startsWith(ContentType.APPLICATION_JSON.getMimeType())) {
// application/json; encoding=utf-8 下载媒体文件出错
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
throw new WxErrorException(WxError.fromJson(responseContent));
}
}

String fileName = getFileName(response);
if (StringUtils.isBlank(fileName)) {
return null;
}

String[] nameAndExt = fileName.split("\\.");
return FileUtils.createTmpFile(inputStream, nameAndExt[0], nameAndExt[1], super.tmpDirFile);

} finally {
httpGet.releaseConnection();
}
}

private String getFileName(CloseableHttpResponse response) throws WxErrorException {
Header[] contentDispositionHeader = response.getHeaders("Content-disposition");
if (contentDispositionHeader == null || contentDispositionHeader.length == 0) {
throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build());
}

Pattern p = Pattern.compile(".*filename=\"(.*)\"");
Matcher m = p.matcher(contentDispositionHeader[0].getValue());
if (m.matches()) {
return m.group(1);
}
throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build());
}

}

+ 55
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaUploadRequestExecutor.java View File

@@ -0,0 +1,55 @@
package me.chanjar.weixin.common.util.http.apache;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;

import java.io.File;
import java.io.IOException;

/**
* Created by ecoolper on 2017/5/5.
*/
public class ApacheMediaUploadRequestExecutor extends MediaUploadRequestExecutor<CloseableHttpClient, HttpHost> {
public ApacheMediaUploadRequestExecutor(RequestHttp requestHttp) {
super(requestHttp);
}

@Override
public WxMediaUploadResult execute(String uri, File file) throws WxErrorException, IOException {
HttpPost httpPost = new HttpPost(uri);
if (requestHttp.getRequestHttpProxy() != null) {
RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
httpPost.setConfig(config);
}
if (file != null) {
HttpEntity entity = MultipartEntityBuilder
.create()
.addBinaryBody("media", file)
.setMode(HttpMultipartMode.RFC6532)
.build();
httpPost.setEntity(entity);
}
try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) {
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return WxMediaUploadResult.fromJson(responseContent);
} finally {
httpPost.releaseConnection();
}
}
}

+ 61
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java View File

@@ -0,0 +1,61 @@
package me.chanjar.weixin.common.util.http.apache;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
import org.apache.http.Consts;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;

import java.io.IOException;

/**
* Created by ecoolper on 2017/5/4.
*/
public class ApacheSimplePostRequestExecutor extends SimplePostRequestExecutor<CloseableHttpClient, HttpHost> {

public ApacheSimplePostRequestExecutor(RequestHttp requestHttp) {
super(requestHttp);
}

@Override
public String execute(String uri, String postEntity) throws WxErrorException, IOException {
HttpPost httpPost = new HttpPost(uri);
if (requestHttp.getRequestHttpProxy() != null) {
RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build();
httpPost.setConfig(config);
}

if (postEntity != null) {
StringEntity entity = new StringEntity(postEntity, Consts.UTF_8);
httpPost.setEntity(entity);
}

try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) {
String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
if (responseContent.isEmpty()) {
throw new WxErrorException(
WxError.newBuilder().setErrorCode(9999).setErrorMsg("无响应内容")
.build());
}

if (responseContent.startsWith("<xml>")) {
//xml格式输出直接返回
return responseContent;
}

WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return responseContent;
} finally {
httpPost.releaseConnection();
}
}
}

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/DefaultApacheHttpClientBuilder.java → weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java View File

@@ -1,4 +1,4 @@
package me.chanjar.weixin.common.util.http;
package me.chanjar.weixin.common.util.http.apache;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.annotation.NotThreadSafe;
@@ -14,16 +14,24 @@ import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@@ -188,7 +196,7 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
}

private synchronized void prepare() {
if(prepared.get()){
if (prepared.get()) {
return;
}
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
@@ -214,6 +222,7 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
this.httpClientBuilder = HttpClients.custom()
.setConnectionManager(connectionManager)
.setConnectionManagerShared(true)
.setSSLSocketFactory(this.buildSSLConnectionSocketFactory())
.setDefaultRequestConfig(
RequestConfig.custom()
.setSocketTimeout(this.soTimeout)
@@ -240,9 +249,32 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
prepared.set(true);
}

private SSLConnectionSocketFactory buildSSLConnectionSocketFactory() {
try {
SSLContext sslcontext = SSLContexts.custom()
//忽略掉对服务器端证书的校验
.loadTrustMaterial(new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();

return new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
this.log.error(e.getMessage(), e);
}

return null;
}

@Override
public CloseableHttpClient build() {
if(!prepared.get()){
if (!prepared.get()) {
prepare();
}
return this.httpClientBuilder.build();

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/InputStreamResponseHandler.java → weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/InputStreamResponseHandler.java View File

@@ -1,4 +1,4 @@
package me.chanjar.weixin.common.util.http;
package me.chanjar.weixin.common.util.http.apache;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/Utf8ResponseHandler.java → weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java View File

@@ -1,4 +1,4 @@
package me.chanjar.weixin.common.util.http;
package me.chanjar.weixin.common.util.http.apache;

import org.apache.http.Consts;
import org.apache.http.HttpEntity;

+ 81
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java View File

@@ -0,0 +1,81 @@
package me.chanjar.weixin.common.util.http.jodd;

import jodd.http.HttpConnectionProvider;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;
import jodd.http.ProxyInfo;
import jodd.util.StringPool;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.fs.FileUtils;
import me.chanjar.weixin.common.util.http.MediaDownloadRequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;
import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Created by ecoolper on 2017/5/5.
*/
public class JoddHttpMediaDownloadRequestExecutor extends MediaDownloadRequestExecutor<HttpConnectionProvider, ProxyInfo> {


public JoddHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
super(requestHttp, tmpDirFile);
}

@Override
public File execute(String uri, String queryParam) throws WxErrorException, IOException {
if (queryParam != null) {
if (uri.indexOf('?') == -1) {
uri += '?';
}
uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
}

HttpRequest request = HttpRequest.get(uri);
if (requestHttp.getRequestHttpProxy() != null) {
requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy());
}
request.withConnectionProvider(requestHttp.getRequestHttpClient());

HttpResponse response = request.send();
response.charset(StringPool.UTF_8);

String contentType = response.header("Content-Type");
if (contentType != null && contentType.startsWith("application/json")) {
// application/json; encoding=utf-8 下载媒体文件出错
throw new WxErrorException(WxError.fromJson(response.bodyText()));
}

String fileName = getFileName(response);
if (StringUtils.isBlank(fileName)) {
return null;
}

InputStream inputStream = new ByteArrayInputStream(response.bodyBytes());
String[] nameAndExt = fileName.split("\\.");
return FileUtils.createTmpFile(inputStream, nameAndExt[0], nameAndExt[1], super.tmpDirFile);
}

private String getFileName(HttpResponse response) throws WxErrorException {
String content = response.header("Content-disposition");
if (content == null || content.length() == 0) {
throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build());
}

Pattern p = Pattern.compile(".*filename=\"(.*)\"");
Matcher m = p.matcher(content);
if (m.matches()) {
return m.group(1);
}
throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build());
}

}

+ 44
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java View File

@@ -0,0 +1,44 @@
package me.chanjar.weixin.common.util.http.jodd;

import jodd.http.HttpConnectionProvider;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;
import jodd.http.ProxyInfo;
import jodd.util.StringPool;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;

import java.io.File;
import java.io.IOException;

/**
* Created by ecoolper on 2017/5/5.
*/
public class JoddHttpMediaUploadRequestExecutor extends MediaUploadRequestExecutor<HttpConnectionProvider, ProxyInfo> {
public JoddHttpMediaUploadRequestExecutor(RequestHttp requestHttp) {
super(requestHttp);
}

@Override
public WxMediaUploadResult execute(String uri, File file) throws WxErrorException, IOException {
HttpRequest request = HttpRequest.post(uri);
if (requestHttp.getRequestHttpProxy() != null) {
requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy());
}
request.withConnectionProvider(requestHttp.getRequestHttpClient());
request.form("media", file);
HttpResponse response = request.send();
response.charset(StringPool.UTF_8);

String responseContent = response.bodyText();
WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return WxMediaUploadResult.fromJson(responseContent);
}
}

+ 48
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java View File

@@ -0,0 +1,48 @@
package me.chanjar.weixin.common.util.http.jodd;

import jodd.http.*;
import jodd.util.StringPool;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;

import java.io.IOException;

/**
* Created by ecoolper on 2017/5/4.
*/
public class JoddHttpSimpleGetRequestExecutor extends SimpleGetRequestExecutor<HttpConnectionProvider, ProxyInfo> {

public JoddHttpSimpleGetRequestExecutor(RequestHttp requestHttp) {
super(requestHttp);
}

@Override
public String execute(String uri, String queryParam) throws WxErrorException, IOException {
if (queryParam != null) {
if (uri.indexOf('?') == -1) {
uri += '?';
}
uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
}

HttpRequest request = HttpRequest.get(uri);
if (requestHttp.getRequestHttpProxy() != null) {
requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy());
}
request.withConnectionProvider(requestHttp.getRequestHttpClient());
HttpResponse response = request.send();
response.charset(StringPool.UTF_8);

String responseContent = response.bodyText();

WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return responseContent;
}

}

+ 60
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java View File

@@ -0,0 +1,60 @@
package me.chanjar.weixin.common.util.http.jodd;

import jodd.http.HttpConnectionProvider;
import jodd.http.HttpRequest;
import jodd.http.HttpResponse;
import jodd.http.ProxyInfo;
import jodd.util.StringPool;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;

import java.io.IOException;

/**
* Created by ecoolper on 2017/5/4.
*/
public class JoddHttpSimplePostRequestExecutor extends SimplePostRequestExecutor<HttpConnectionProvider, ProxyInfo> {

public JoddHttpSimplePostRequestExecutor(RequestHttp requestHttp) {
super(requestHttp);
}

@Override
public String execute(String uri, String postEntity) throws WxErrorException, IOException {
HttpConnectionProvider provider = requestHttp.getRequestHttpClient();
ProxyInfo proxyInfo = requestHttp.getRequestHttpProxy();

HttpRequest request = HttpRequest.post(uri);
if (proxyInfo != null) {
provider.useProxy(proxyInfo);
}
request.withConnectionProvider(provider);
if (postEntity != null) {
request.bodyText(postEntity);
}
HttpResponse response = request.send();
response.charset(StringPool.UTF_8);

String responseContent = response.bodyText();
if (responseContent.isEmpty()) {
throw new WxErrorException(
WxError.newBuilder().setErrorCode(9999).setErrorMsg("无响应内容")
.build());
}

if (responseContent.startsWith("<xml>")) {
//xml格式输出直接返回
return responseContent;
}

WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return responseContent;
}

}

+ 89
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java View File

@@ -0,0 +1,89 @@
package me.chanjar.weixin.common.util.http.okhttp;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.fs.FileUtils;
import me.chanjar.weixin.common.util.http.MediaDownloadRequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;
import okhttp3.*;
import org.apache.commons.lang3.StringUtils;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Created by ecoolper on 2017/5/5.
*/
public class OkHttpMediaDownloadRequestExecutor extends MediaDownloadRequestExecutor<ConnectionPool, OkHttpProxyInfo> {


public OkHttpMediaDownloadRequestExecutor(RequestHttp requestHttp, File tmpDirFile) {
super(requestHttp, tmpDirFile);
}

@Override
public File execute(String uri, String queryParam) throws WxErrorException, IOException {
if (queryParam != null) {
if (uri.indexOf('?') == -1) {
uri += '?';
}
uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
}

OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(requestHttp.getRequestHttpClient());
//设置代理
if (requestHttp.getRequestHttpProxy() != null) {
clientBuilder.proxy(requestHttp.getRequestHttpProxy().getProxy());
}
//设置授权
clientBuilder.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(requestHttp.getRequestHttpProxy().getProxyUsername(), requestHttp.getRequestHttpProxy().getProxyPassword());
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
});
//得到httpClient
OkHttpClient client = clientBuilder.build();

Request request = new Request.Builder().url(uri).get().build();

Response response = client.newCall(request).execute();

String contentType = response.header("Content-Type");
if (contentType != null && contentType.startsWith("application/json")) {
// application/json; encoding=utf-8 下载媒体文件出错
throw new WxErrorException(WxError.fromJson(response.body().string()));
}

String fileName = getFileName(response);
if (StringUtils.isBlank(fileName)) {
return null;
}

InputStream inputStream = new ByteArrayInputStream(response.body().bytes());
String[] nameAndExt = fileName.split("\\.");
return FileUtils.createTmpFile(inputStream, nameAndExt[0], nameAndExt[1], super.tmpDirFile);
}

private String getFileName(Response response) throws WxErrorException {
String content = response.header("Content-disposition");
if (content == null || content.length() == 0) {
throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build());
}

Pattern p = Pattern.compile(".*filename=\"(.*)\"");
Matcher m = p.matcher(content);
if (m.matches()) {
return m.group(1);
}
throw new WxErrorException(WxError.newBuilder().setErrorMsg("无法获取到文件名").build());
}

}

+ 55
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaUploadRequestExecutor.java View File

@@ -0,0 +1,55 @@
package me.chanjar.weixin.common.util.http.okhttp;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;
import okhttp3.*;

import java.io.File;
import java.io.IOException;

/**
* Created by ecoolper on 2017/5/5.
*/
public class OkHttpMediaUploadRequestExecutor extends MediaUploadRequestExecutor<ConnectionPool, OkHttpProxyInfo> {

public OkHttpMediaUploadRequestExecutor(RequestHttp requestHttp) {
super(requestHttp);
}

@Override
public WxMediaUploadResult execute(String uri, File file) throws WxErrorException, IOException {
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(requestHttp.getRequestHttpClient());
//设置代理
if (requestHttp.getRequestHttpProxy() != null) {
clientBuilder.proxy(requestHttp.getRequestHttpProxy().getProxy());
}
//设置授权
clientBuilder.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(requestHttp.getRequestHttpProxy().getProxyUsername(), requestHttp.getRequestHttpProxy().getProxyPassword());
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
});
//得到httpClient
OkHttpClient client = clientBuilder.build();

RequestBody fileBody = RequestBody.create(MediaType.parse("multipart/form-data"), file);
RequestBody body = new MultipartBody.Builder().addFormDataPart("media", null, fileBody).build();
Request request = new Request.Builder().url(uri).post(body).build();

Response response = client.newCall(request).execute();
String responseContent = response.body().string();
WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return WxMediaUploadResult.fromJson(responseContent);
}

}

+ 117
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpProxyInfo.java View File

@@ -0,0 +1,117 @@
package me.chanjar.weixin.common.util.http.okhttp;

import java.net.InetSocketAddress;
import java.net.Proxy;

/**
* Created by ecoolper on 2017/4/26.
* Proxy information.
*/
public class OkHttpProxyInfo {
private final String proxyAddress;
private final int proxyPort;
private final String proxyUsername;
private final String proxyPassword;
private final ProxyType proxyType;
public OkHttpProxyInfo(ProxyType proxyType, String proxyHost, int proxyPort, String proxyUser, String proxyPassword) {
this.proxyType = proxyType;
this.proxyAddress = proxyHost;
this.proxyPort = proxyPort;
this.proxyUsername = proxyUser;
this.proxyPassword = proxyPassword;
}

/**
* Creates directProxy.
*/
public static OkHttpProxyInfo directProxy() {
return new OkHttpProxyInfo(ProxyType.NONE, null, 0, null, null);
}

// ---------------------------------------------------------------- factory

/**
* Creates SOCKS4 proxy.
*/
public static OkHttpProxyInfo socks4Proxy(String proxyAddress, int proxyPort, String proxyUser) {
return new OkHttpProxyInfo(ProxyType.SOCKS4, proxyAddress, proxyPort, proxyUser, null);
}

/**
* Creates SOCKS5 proxy.
*/
public static OkHttpProxyInfo socks5Proxy(String proxyAddress, int proxyPort, String proxyUser, String proxyPassword) {
return new OkHttpProxyInfo(ProxyType.SOCKS5, proxyAddress, proxyPort, proxyUser, proxyPassword);
}

/**
* Creates HTTP proxy.
*/
public static OkHttpProxyInfo httpProxy(String proxyAddress, int proxyPort, String proxyUser, String proxyPassword) {
return new OkHttpProxyInfo(ProxyType.HTTP, proxyAddress, proxyPort, proxyUser, proxyPassword);
}

/**
* Returns proxy type.
*/
public ProxyType getProxyType() {
return proxyType;
}

// ---------------------------------------------------------------- getter

/**
* Returns proxy address.
*/
public String getProxyAddress() {
return proxyAddress;
}

/**
* Returns proxy port.
*/
public int getProxyPort() {
return proxyPort;
}

/**
* Returns proxy user name or <code>null</code> if
* no authentication required.
*/
public String getProxyUsername() {
return proxyUsername;
}

/**
* Returns proxy password or <code>null</code>.
*/
public String getProxyPassword() {
return proxyPassword;
}

/**
* 返回 java.net.Proxy
*
* @return
*/
public Proxy getProxy() {
Proxy proxy = null;
if (getProxyType().equals(ProxyType.SOCKS5)) {
proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(getProxyAddress(), getProxyPort()));
} else if (getProxyType().equals(ProxyType.SOCKS4)) {
proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(getProxyAddress(), getProxyPort()));
} else if (getProxyType().equals(ProxyType.HTTP)) {
proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(getProxyAddress(), getProxyPort()));
} else if (getProxyType().equals(ProxyType.NONE)) {
proxy = new Proxy(Proxy.Type.DIRECT, new InetSocketAddress(getProxyAddress(), getProxyPort()));
}
return proxy;
}

/**
* Proxy types.
*/
public enum ProxyType {
NONE, HTTP, SOCKS4, SOCKS5
}
}

+ 58
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java View File

@@ -0,0 +1,58 @@
package me.chanjar.weixin.common.util.http.okhttp;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
import okhttp3.*;

import java.io.IOException;

/**
* Created by ecoolper on 2017/5/4.
*/
public class OkHttpSimpleGetRequestExecutor extends SimpleGetRequestExecutor<ConnectionPool, OkHttpProxyInfo> {

public OkHttpSimpleGetRequestExecutor(RequestHttp requestHttp) {
super(requestHttp);
}

@Override
public String execute(String uri, String queryParam) throws WxErrorException, IOException {
if (queryParam != null) {
if (uri.indexOf('?') == -1) {
uri += '?';
}
uri += uri.endsWith("?") ? queryParam : '&' + queryParam;
}

OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(requestHttp.getRequestHttpClient());
//设置代理
if (requestHttp.getRequestHttpProxy() != null) {
clientBuilder.proxy(requestHttp.getRequestHttpProxy().getProxy());
}
//设置授权
clientBuilder.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(requestHttp.getRequestHttpProxy().getProxyUsername(), requestHttp.getRequestHttpProxy().getProxyPassword());
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
});
//得到httpClient
OkHttpClient client = clientBuilder.build();

Request request = new Request.Builder().url(uri).build();

Response response = client.newCall(request).execute();
String responseContent = response.body().string();
WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return responseContent;
}

}

+ 58
- 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java View File

@@ -0,0 +1,58 @@
package me.chanjar.weixin.common.util.http.okhttp;

import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
import okhttp3.*;

import java.io.IOException;

/**
* Created by ecoolper on 2017/5/4.
*/
public class OkHttpSimplePostRequestExecutor extends SimplePostRequestExecutor<ConnectionPool, OkHttpProxyInfo> {

public OkHttpSimplePostRequestExecutor(RequestHttp requestHttp) {
super(requestHttp);
}

@Override
public String execute(String uri, String postEntity) throws WxErrorException, IOException {
ConnectionPool pool = requestHttp.getRequestHttpClient();
final OkHttpProxyInfo proxyInfo = requestHttp.getRequestHttpProxy();

OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(pool);
//设置代理
if (proxyInfo != null) {
clientBuilder.proxy(proxyInfo.getProxy());
}
//设置授权
clientBuilder.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(proxyInfo.getProxyUsername(), proxyInfo.getProxyPassword());
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
});
//得到httpClient
OkHttpClient client = clientBuilder.build();


MediaType mediaType = MediaType.parse("text/plain; charset=utf-8");
RequestBody body = RequestBody.create(mediaType, postEntity);

Request request = new Request.Builder().url(uri).post(body).build();

Response response = client.newCall(request).execute();
String responseContent = response.body().string();
WxError error = WxError.fromJson(responseContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
return responseContent;
}

}

+ 0
- 1
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxGsonBuilder.java View File

@@ -2,7 +2,6 @@ package me.chanjar.weixin.common.util.json;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.bean.result.WxError;


+ 6
- 1
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/WxMenuGsonAdapter.java View File

@@ -90,9 +90,14 @@ public class WxMenuGsonAdapter implements JsonSerializer<WxMenu>, JsonDeserializ
* 操蛋的微信
* 创建菜单时是 { button : ... }
* 查询菜单时是 { menu : { button : ... } }
* 现在企业号升级为企业微信后,没有此问题,因此需要单独处理
*/
WxMenu menu = new WxMenu();
JsonArray buttonsJson = json.getAsJsonObject().get("menu").getAsJsonObject().get("button").getAsJsonArray();
return this.buildMenuFromJson(buttonsJson);
}

protected WxMenu buildMenuFromJson(JsonArray buttonsJson) {
WxMenu menu = new WxMenu();
for (int i = 0; i < buttonsJson.size(); i++) {
JsonObject buttonJson = buttonsJson.get(i).getAsJsonObject();
WxMenuButton button = convertFromJson(buttonJson);


+ 4
- 4
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/res/StringManager.java View File

@@ -47,7 +47,7 @@ import java.util.*;
public class StringManager {

private static final Map<String, Map<Locale, StringManager>> managers =
new Hashtable<>();
new Hashtable<>();
private static int LOCALE_CACHE_SIZE = 10;
/**
* The ResourceBundle for this StringManager.
@@ -103,7 +103,7 @@ public class StringManager {
* @param packageName The package name
*/
public static final synchronized StringManager getManager(
String packageName) {
String packageName) {
return getManager(packageName, Locale.getDefault());
}

@@ -116,7 +116,7 @@ public class StringManager {
* @param locale The Locale
*/
public static final synchronized StringManager getManager(
String packageName, Locale locale) {
String packageName, Locale locale) {

Map<Locale, StringManager> map = managers.get(packageName);
if (map == null) {
@@ -133,7 +133,7 @@ public class StringManager {

@Override
protected boolean removeEldestEntry(
Map.Entry<Locale, StringManager> eldest) {
Map.Entry<Locale, StringManager> eldest) {
return size() > (LOCALE_CACHE_SIZE - 1);
}
};


+ 2
- 2
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java View File

@@ -1,7 +1,5 @@
package me.chanjar.weixin.common.util.xml;

import java.io.Writer;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
@@ -10,6 +8,8 @@ import com.thoughtworks.xstream.io.xml.XppDriver;
import com.thoughtworks.xstream.security.NullPermission;
import com.thoughtworks.xstream.security.PrimitiveTypePermission;

import java.io.Writer;

public class XStreamInitializer {

public static XStream getInstance() {


weixin-java-common/src/test/java/me/chanjar/weixin/common/util/WxMessageInMemoryDuplicateCheckerTest.java → weixin-java-common/src/test/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateCheckerTest.java View File

@@ -1,34 +1,36 @@
package me.chanjar.weixin.common.util;
package me.chanjar.weixin.common.api;

import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker;
import org.testng.Assert;
import org.testng.annotations.Test;

import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

@Test
public class WxMessageInMemoryDuplicateCheckerTest {
private WxMessageInMemoryDuplicateChecker checker = new WxMessageInMemoryDuplicateChecker(2000l, 1000l);

public void test() throws InterruptedException {
Long[] msgIds = new Long[]{1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l};
WxMessageInMemoryDuplicateChecker checker = new WxMessageInMemoryDuplicateChecker(2000l, 1000l);

// 第一次检查
for (Long msgId : msgIds) {
boolean result = checker.isDuplicate(String.valueOf(msgId));
Assert.assertFalse(result);
assertFalse(result);
}

// 过1秒再检查
Thread.sleep(1000l);
for (Long msgId : msgIds) {
boolean result = checker.isDuplicate(String.valueOf(msgId));
Assert.assertTrue(result);
assertTrue(result);
}

// 过1.5秒再检查
Thread.sleep(1500l);
for (Long msgId : msgIds) {
boolean result = checker.isDuplicate(String.valueOf(msgId));
Assert.assertFalse(result);
assertFalse(result);
}

}

+ 2
- 2
weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxAccessTokenTest.java View File

@@ -1,7 +1,7 @@
package me.chanjar.weixin.common.bean;

import org.testng.Assert;
import org.testng.annotations.Test;
import org.testng.*;
import org.testng.annotations.*;

@Test
public class WxAccessTokenTest {


+ 2
- 2
weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxErrorTest.java View File

@@ -1,8 +1,8 @@
package me.chanjar.weixin.common.bean;

import me.chanjar.weixin.common.bean.result.WxError;
import org.testng.Assert;
import org.testng.annotations.Test;
import org.testng.*;
import org.testng.annotations.*;

@Test
public class WxErrorTest {


+ 57
- 59
weixin-java-common/src/test/java/me/chanjar/weixin/common/bean/WxMenuTest.java View File

@@ -1,12 +1,10 @@
package me.chanjar.weixin.common.bean;

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.bean.menu.WxMenuButton;
import me.chanjar.weixin.common.bean.menu.WxMenuRule;
import org.testng.*;
import org.testng.annotations.*;

@Test
public class WxMenuTest {
@@ -87,75 +85,75 @@ public class WxMenuTest {
Object[][] res = menuJson();
String json = "{ \"menu\" : " + res[0][0] + " }";
return new Object[][]{
new Object[]{json}
new Object[]{json}
};
}

@DataProvider(name = "wxPushMenu")
public Object[][] menuJson() {
String json =
"{"
+ "\"button\":["
+ "{"
+ "\"type\":\"click\","
+ "\"name\":\"今日歌曲\","
+ "\"key\":\"V1001_TODAY_MUSIC\""
+ "},"
+ "{"
+ "\"type\":\"click\","
+ "\"name\":\"歌手简介\","
+ "\"key\":\"V1001_TODAY_SINGER\""
+ "},"
+ "{"
+ "\"name\":\"菜单\","
+ "\"sub_button\":["
+ "{"
+ "\"type\":\"view\","
+ "\"name\":\"搜索\","
+ "\"url\":\"http://www.soso.com/\""
+ "},"
+ "{"
+ "\"type\":\"view\","
+ "\"name\":\"视频\","
+ "\"url\":\"http://v.qq.com/\""
+ "},"
+ "{"
+ "\"type\":\"click\","
+ "\"name\":\"赞一下我们\","
+ "\"key\":\"V1001_GOOD\""
+ "}"
+ "]"
+ "}"
+ "]"
+ "}";
"{"
+ "\"button\":["
+ "{"
+ "\"type\":\"click\","
+ "\"name\":\"今日歌曲\","
+ "\"key\":\"V1001_TODAY_MUSIC\""
+ "},"
+ "{"
+ "\"type\":\"click\","
+ "\"name\":\"歌手简介\","
+ "\"key\":\"V1001_TODAY_SINGER\""
+ "},"
+ "{"
+ "\"name\":\"菜单\","
+ "\"sub_button\":["
+ "{"
+ "\"type\":\"view\","
+ "\"name\":\"搜索\","
+ "\"url\":\"http://www.soso.com/\""
+ "},"
+ "{"
+ "\"type\":\"view\","
+ "\"name\":\"视频\","
+ "\"url\":\"http://v.qq.com/\""
+ "},"
+ "{"
+ "\"type\":\"click\","
+ "\"name\":\"赞一下我们\","
+ "\"key\":\"V1001_GOOD\""
+ "}"
+ "]"
+ "}"
+ "]"
+ "}";
return new Object[][]{
new Object[]{json}
new Object[]{json}
};
}

@DataProvider(name = "wxAddConditionalMenu")
public Object[][] addConditionalMenuJson() {
String json =
"{"
+ "\"button\":["
+ "{"
+ "\"type\":\"click\","
+ "\"name\":\"今日歌曲\","
+ "\"key\":\"V1001_TODAY_MUSIC\""
+ "}"
+ "],"
+ "\"matchrule\":{"
+ "\"group_id\":\"2\","
+ "\"sex\":\"1\","
+ "\"country\":\"中国\","
+ "\"province\":\"广东\","
+ "\"city\":\"广州\","
+ "\"client_platform_type\":\"2\","
+ "\"language\":\"zh_CN\""
+ "}"
+ "}";
"{"
+ "\"button\":["
+ "{"
+ "\"type\":\"click\","
+ "\"name\":\"今日歌曲\","
+ "\"key\":\"V1001_TODAY_MUSIC\""
+ "}"
+ "],"
+ "\"matchrule\":{"
+ "\"group_id\":\"2\","
+ "\"sex\":\"1\","
+ "\"country\":\"中国\","
+ "\"province\":\"广东\","
+ "\"city\":\"广州\","
+ "\"client_platform_type\":\"2\","
+ "\"language\":\"zh_CN\""
+ "}"
+ "}";
return new Object[][]{
new Object[]{json}
new Object[]{json}
};
}



+ 3
- 4
weixin-java-common/src/test/java/me/chanjar/weixin/common/session/SessionTest.java View File

@@ -1,8 +1,7 @@
package me.chanjar.weixin.common.session;

import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.testng.*;
import org.testng.annotations.*;

@Test
public class SessionTest {
@@ -11,7 +10,7 @@ public class SessionTest {
public Object[][] getSessionManager() {

return new Object[][]{
new Object[]{new StandardSessionManager()}
new Object[]{new StandardSessionManager()}
};

}


+ 36
- 0
weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/SHA1Test.java View File

@@ -0,0 +1,36 @@
package me.chanjar.weixin.common.util.crypto;

import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;

/**
* <pre>
* Created by BinaryWang on 2017/6/10.
* </pre>
*
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a>
*/
public class SHA1Test {
@Test
public void testGen() throws Exception {
final String result = SHA1.gen("123", "345");
assertNotNull(result);
assertEquals(result,"9f537aeb751ec72605f57f94a2f6dc3e3958e1dd");
}

@Test(expectedExceptions = {IllegalArgumentException.class})
public void testGen_illegalArguments() {
final String result = SHA1.gen(null, "", "345");
assertNotNull(result);
}

@Test
public void testGenWithAmple() throws Exception {
final String result = SHA1.genWithAmple("123", "345");
assertNotNull(result);
assertEquals(result,"20b896ccbd5a72dde5dbe0878ff985e4069771c6");
}

}

+ 3
- 4
weixin-java-common/src/test/java/me/chanjar/weixin/common/util/crypto/WxCryptUtilTest.java View File

@@ -1,6 +1,6 @@
package me.chanjar.weixin.common.util.crypto;

import org.testng.annotations.Test;
import org.testng.annotations.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
@@ -13,8 +13,7 @@ import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.StringReader;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import static org.testng.Assert.*;

@Test
public class WxCryptUtilTest {
@@ -77,7 +76,7 @@ public class WxCryptUtilTest {
}

public void testValidateSignatureError() throws ParserConfigurationException, SAXException,
IOException {
IOException {
try {
WxCryptUtil pc = new WxCryptUtil(this.token, this.encodingAesKey, this.appId);
String afterEncrpt = pc.encrypt(this.replyMsg);


+ 3
- 7
weixin-java-common/src/test/resources/logback-test.xml View File

@@ -1,17 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/2002/xmlspec/dtd/2.10/xmlspec.dtd">
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoders are assigned the type
ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %replace(%caller{1}){'Caller', ''} - %msg%n</pattern>
</encoder>
</appender>

<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="STDOUT"/>
</root>

<logger name="me.chanjar.weixin.common" level="debug" />
<logger name="me.chanjar.weixin.common" level="debug"/>
</configuration>

+ 2
- 2
weixin-java-common/src/test/resources/testng.xml View File

@@ -7,8 +7,8 @@
<class name="me.chanjar.weixin.common.bean.WxErrorTest"/>
<class name="me.chanjar.weixin.common.bean.WxMenuTest"/>
<class name="me.chanjar.weixin.common.util.crypto.WxCryptUtilTest"/>
<class name="me.chanjar.weixin.common.util.WxMessageInMemoryDuplicateCheckerTest"/>
<class name="me.chanjar.weixin.common.session.SessionTest" />
<class name="me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateCheckerTest"/>
<class name="me.chanjar.weixin.common.session.SessionTest"/>
</classes>
</test>
</suite>

+ 0
- 12
weixin-java-cp/build.gradle View File

@@ -1,12 +0,0 @@

description = 'WeiXin Java Tools - CP'
dependencies {
compile project(':weixin-java-common')
testCompile group: 'junit', name: 'junit', version:'4.11'
testCompile group: 'org.testng', name: 'testng', version:'6.8.7'
testCompile group: 'org.mockito', name: 'mockito-all', version:'1.9.5'
testCompile group: 'com.google.inject', name: 'guice', version:'3.0'
testCompile group: 'org.eclipse.jetty', name: 'jetty-server', version:'9.3.0.RC0'
testCompile group: 'org.eclipse.jetty', name: 'jetty-servlet', version:'9.3.0.RC0'
}
test.useTestNG()

+ 23
- 2
weixin-java-cp/pom.xml View File

@@ -1,12 +1,13 @@
<?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">
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.6.0</version>
<version>2.7.0</version>
</parent>

<artifactId>weixin-java-cp</artifactId>
@@ -19,10 +20,25 @@
<artifactId>weixin-java-common</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>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
@@ -48,6 +64,11 @@
<artifactId>jetty-servlet</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>


+ 57
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpDepartmentService.java View File

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

import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpDepart;

import java.util.List;

/**
* <pre>
* 部门管理接口
* Created by BinaryWang on 2017/6/24.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public interface WxCpDepartmentService {

/**
* <pre>
* 部门管理接口 - 创建部门
* 最多支持创建500个部门
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口
* </pre>
*
* @param depart 部门
* @return 部门id
*/
Integer create(WxCpDepart depart) throws WxErrorException;

/**
* <pre>
* 部门管理接口 - 查询所有部门
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口
* </pre>
*/
List<WxCpDepart> listAll() throws WxErrorException;

/**
* <pre>
* 部门管理接口 - 修改部门名
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口
* 如果id为0(未部门),1(黑名单),2(星标组),或者不存在的id,微信会返回系统繁忙的错误
* </pre>
*
* @param group 要更新的group,group的id,name必须设置
*/
void update(WxCpDepart group) throws WxErrorException;

/**
* <pre>
* 部门管理接口 - 删除部门
* </pre>
*
* @param departId 部门id
*/
void delete(Integer departId) throws WxErrorException;
}

+ 57
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMediaService.java View File

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

import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.exception.WxErrorException;

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

/**
* <pre>
* 媒体管理接口
* Created by BinaryWang on 2017/6/24.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public interface WxCpMediaService {

/**
* <pre>
* 上传多媒体文件
* 上传的多媒体文件有格式和大小限制,如下:
* 图片(image): 1M,支持JPG格式
* 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
* 视频(video):10MB,支持MP4格式
* 缩略图(thumb):64KB,支持JPG格式
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
* </pre>
*
* @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts}
* @param fileType 文件类型,请看{@link me.chanjar.weixin.common.api.WxConsts}
* @param inputStream 输入流
*/
WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputStream)
throws WxErrorException, IOException;

/**
* @param mediaType 媒体类型
* @param file 文件对象
* @see #upload(String, String, InputStream)
*/
WxMediaUploadResult upload(String mediaType, File file) throws WxErrorException;

/**
* <pre>
* 下载多媒体文件
* 根据微信文档,视频文件下载不了,会返回null
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
* </pre>
*
* @param mediaId 媒体id
* @return 保存到本地的临时文件
*/
File download(String mediaId) throws WxErrorException;

}

+ 91
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMenuService.java View File

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

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

/**
* <pre>
* 菜单管理相关接口
* Created by BinaryWang on 2017/6/24.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public interface WxCpMenuService {
/**
* <pre>
* 自定义菜单创建接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口
*
* 注意: 这个方法使用WxCpConfigStorage里的agentId
* </pre>
*
* @param menu 菜单对象
* @see #create(Integer, WxMenu)
*/
void create(WxMenu menu) throws WxErrorException;

/**
* <pre>
* 自定义菜单创建接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口
*
* 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出
* </pre>
*
* @param agentId 企业号应用的id
* @param menu 菜单对象
* @see #create(me.chanjar.weixin.common.bean.menu.WxMenu)
*/
void create(Integer agentId, WxMenu menu) throws WxErrorException;

/**
* <pre>
* 自定义菜单删除接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口
*
* 注意: 这个方法使用WxCpConfigStorage里的agentId
* </pre>
*
* @see #delete(Integer)
*/
void delete() throws WxErrorException;

/**
* <pre>
* 自定义菜单删除接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口
*
* 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出
* </pre>
*
* @param agentId 企业号应用的id
* @see #delete()
*/
void delete(Integer agentId) throws WxErrorException;

/**
* <pre>
* 自定义菜单查询接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口
*
* 注意: 这个方法使用WxCpConfigStorage里的agentId
* </pre>
*
* @see #get(Integer)
*/
WxMenu get() throws WxErrorException;

/**
* <pre>
* 自定义菜单查询接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口
*
* 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出
* </pre>
*
* @param agentId 企业号应用的id
* @see #get()
*/
WxMenu get(Integer agentId) throws WxErrorException;
}

+ 67
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOAuth2Service.java View File

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

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

/**
* <pre>
* OAuth2相关管理接口
* Created by BinaryWang on 2017/6/24.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public interface WxCpOAuth2Service {
/**
* <pre>
* 构造oauth2授权的url连接
* </pre>
*
* @param state 状态码
* @return url
*/
String buildAuthorizationUrl(String state);

/**
* <pre>
* 构造oauth2授权的url连接
* 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=企业获取code
* </pre>
*
* @param redirectUri 跳转链接地址
* @param state 状态码
* @return url
*/
String buildAuthorizationUrl(String redirectUri, String state);

/**
* <pre>
* 用oauth2获取用户信息
* http://qydev.weixin.qq.com/wiki/index.php?title=根据code获取成员信息
* 因为企业号oauth2.0必须在应用设置里设置通过ICP备案的可信域名,所以无法测试,因此这个方法很可能是坏的。
*
* 注意: 这个方法使用WxCpConfigStorage里的agentId
* </pre>
*
* @param code 微信oauth授权返回的代码
* @return [userid, deviceid]
* @see #getUserInfo(Integer, String)
*/
String[] getUserInfo(String code) throws WxErrorException;

/**
* <pre>
* 用oauth2获取用户信息
* http://qydev.weixin.qq.com/wiki/index.php?title=根据code获取成员信息
* 因为企业号oauth2.0必须在应用设置里设置通过ICP备案的可信域名,所以无法测试,因此这个方法很可能是坏的。
*
* 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出
* </pre>
*
* @param agentId 企业号应用的id
* @param code 微信oauth授权返回的代码
* @return [userid, deviceid]
* @see #getUserInfo(String)
*/
String[] getUserInfo(Integer agentId, String code) throws WxErrorException;

}

+ 153
- 245
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java View File

@@ -6,11 +6,11 @@ import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.session.WxSession;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
import me.chanjar.weixin.common.util.http.RequestExecutor;
import me.chanjar.weixin.cp.bean.WxCpDepart;
import me.chanjar.weixin.cp.bean.WxCpMessage;
import me.chanjar.weixin.cp.bean.WxCpTag;
import me.chanjar.weixin.cp.bean.WxCpUser;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.cp.bean.*;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;

import java.io.File;
import java.io.IOException;
@@ -35,16 +35,6 @@ public interface WxCpService {
*/
boolean checkSignature(String msgSignature, String timestamp, String nonce, String data);

/**
* <pre>
* 用在二次验证的时候
* 企业在员工验证成功后,调用本方法告诉企业号平台该员工关注成功。
* </pre>
*
* @param userId 用户id
*/
void userAuthenticated(String userId) throws WxErrorException;

/**
* 获取access_token, 不强制刷新access_token
*
@@ -96,337 +86,208 @@ public interface WxCpService {
WxJsapiSignature createJsapiSignature(String url) throws WxErrorException;

/**
* <pre>
* 上传多媒体文件
* 上传的多媒体文件有格式和大小限制,如下:
* 图片(image): 1M,支持JPG格式
* 语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
* 视频(video):10MB,支持MP4格式
* 缩略图(thumb):64KB,支持JPG格式
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
* </pre>
*
* @param mediaType 媒体类型, 请看{@link me.chanjar.weixin.common.api.WxConsts}
* @param fileType 文件类型,请看{@link me.chanjar.weixin.common.api.WxConsts}
* @param inputStream 输入流
* @deprecated 请使用 {@link WxCpMenuService#create(WxMenu)}
*/
WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream)
throws WxErrorException, IOException;
@Deprecated
void menuCreate(WxMenu menu) throws WxErrorException;

/**
* @param mediaType 媒体类型
* @param file 文件对象
* @see #mediaUpload(String, String, InputStream)
* @deprecated 请使用 {@link WxCpMenuService#create(Integer, WxMenu)}
*/
WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException;

/**
* <pre>
* 下载多媒体文件
* 根据微信文档,视频文件下载不了,会返回null
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=上传下载多媒体文件
* </pre>
*
* @param mediaId 媒体id
* @return 保存到本地的临时文件
*/
File mediaDownload(String mediaId) throws WxErrorException;
@Deprecated
void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException;

/**
* <pre>
* 发送消息
* 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
* </pre>
*
* @param message 要发送的消息对象
* @deprecated 请使用 {@link WxCpMenuService#delete()} }
*/
void messageSend(WxCpMessage message) throws WxErrorException;
@Deprecated
void menuDelete() throws WxErrorException;

/**
* <pre>
* 自定义菜单创建接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口
*
* 注意: 这个方法使用WxCpConfigStorage里的agentId
* </pre>
*
* @param menu 菜单对象
* @see #menuCreate(Integer, WxMenu)
* @deprecated 请使用 {@link WxCpMenuService#delete(Integer)}
*/
void menuCreate(WxMenu menu) throws WxErrorException;
@Deprecated
void menuDelete(Integer agentId) throws WxErrorException;

/**
* <pre>
* 自定义菜单创建接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单创建接口
*
* 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出
* </pre>
*
* @param agentId 企业号应用的id
* @param menu 菜单对象
* @see #menuCreate(me.chanjar.weixin.common.bean.menu.WxMenu)
* @deprecated 请使用 {@link WxCpMenuService#get() }
*/
void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException;
@Deprecated
WxMenu menuGet() throws WxErrorException;

/**
* <pre>
* 自定义菜单删除接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口
*
* 注意: 这个方法使用WxCpConfigStorage里的agentId
* </pre>
*
* @see #menuDelete(Integer)
* @deprecated 请使用 {@link WxCpMenuService#get(Integer)}
*/
void menuDelete() throws WxErrorException;
@Deprecated
WxMenu menuGet(Integer agentId) throws WxErrorException;

/**
* <pre>
* 自定义菜单删除接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单删除接口
*
* 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出
* 发送消息
* 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
* </pre>
*
* @param agentId 企业号应用的id
* @see #menuDelete()
* @param message 要发送的消息对象
*/
void menuDelete(Integer agentId) throws WxErrorException;
WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException;

/**
* <pre>
* 自定义菜单查询接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口
*
* 注意: 这个方法使用WxCpConfigStorage里的agentId
* </pre>
*
* @see #menuGet(Integer)
* @deprecated 请使用 {@link WxCpDepartmentService#create(WxCpDepart)}
*/
WxMenu menuGet() throws WxErrorException;
@Deprecated
Integer departCreate(WxCpDepart depart) throws WxErrorException;

/**
* <pre>
* 自定义菜单查询接口
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=自定义菜单查询接口
*
* 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出
* </pre>
*
* @param agentId 企业号应用的id
* @see #menuGet()
* @deprecated 请使用 {@link WxCpDepartmentService#update(WxCpDepart)}
*/
WxMenu menuGet(Integer agentId) throws WxErrorException;
@Deprecated
void departUpdate(WxCpDepart group) throws WxErrorException;

/**
* <pre>
* 部门管理接口 - 创建部门
* 最多支持创建500个部门
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口
* </pre>
*
* @param depart 部门
* @return 部门id
* @deprecated 请使用 {@link WxCpDepartmentService#delete(Integer)}
*/
Integer departCreate(WxCpDepart depart) throws WxErrorException;
@Deprecated
void departDelete(Integer departId) throws WxErrorException;

/**
* <pre>
* 部门管理接口 - 查询所有部门
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口
* </pre>
* @deprecated 请使用 {@link WxCpDepartmentService#listAll() }
*/
@Deprecated
List<WxCpDepart> departGet() throws WxErrorException;

/**
* <pre>
* 部门管理接口 - 修改部门名
* 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=部门管理接口
* 如果id为0(未部门),1(黑名单),2(星标组),或者不存在的id,微信会返回系统繁忙的错误
* </pre>
*
* @param group 要更新的group,group的id,name必须设置
* @deprecated 请使用 {@link WxCpMediaService#upload(String, String, InputStream)}
*/
void departUpdate(WxCpDepart group) throws WxErrorException;
@Deprecated
WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream)
throws WxErrorException, IOException;

/**
* <pre>
* 部门管理接口 - 删除部门
* </pre>
*
* @param departId 部门id
* @deprecated 请使用 {@link WxCpMediaService#upload(String, File)}
*/
void departDelete(Integer departId) throws WxErrorException;
@Deprecated
WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException;

/**
* <pre>
* 获取部门成员(详情)
*
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29
* </pre>
*
* @param departId 必填。部门id
* @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员
* @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加
* @deprecated 请使用 {@link WxCpMediaService#download(String)}
*/
List<WxCpUser> userList(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException;
@Deprecated
File mediaDownload(String mediaId) throws WxErrorException;

/**
* <pre>
* 获取部门成员
*
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98
* </pre>
*
* @param departId 必填。部门id
* @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员
* @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加
* @deprecated 请使用 {@link WxCpUserService#authenticate(String)}
*/
List<WxCpUser> departGetUsers(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException;
@Deprecated
void userAuthenticated(String userId) throws WxErrorException;

/**
* 新建用户
*
* @param user 用户对象
* @deprecated 请使用 {@link WxCpUserService#create(WxCpUser)}
*/
@Deprecated
void userCreate(WxCpUser user) throws WxErrorException;

/**
* 更新用户
*
* @param user 用户对象
* @deprecated 请使用 {@link WxCpUserService#update(WxCpUser)}
*/
@Deprecated
void userUpdate(WxCpUser user) throws WxErrorException;

/**
* 删除用户
*
* @param userid 用户id
* @deprecated 请使用 {@link WxCpUserService#delete(String...)}
*/
@Deprecated
void userDelete(String userid) throws WxErrorException;

/**
* <pre>
* 批量删除成员
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98
* </pre>
*
* @param userids 员工UserID列表。对应管理端的帐号
* @deprecated 请使用 {@link WxCpUserService#delete(String...)}
*/
@Deprecated
void userDelete(String[] userids) throws WxErrorException;

/**
* 获取用户
*
* @param userid 用户id
* @deprecated 请使用 {@link WxCpUserService#getById(String)}
*/
@Deprecated
WxCpUser userGet(String userid) throws WxErrorException;

/**
* 创建标签
*
* @param tagName 标签名
* @deprecated 请使用 {@link WxCpUserService#listByDepartment(Integer, Boolean, Integer)}
*/
@Deprecated
List<WxCpUser> userList(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException;

/**
* @deprecated 请使用 {@link WxCpUserService#listSimpleByDepartment(Integer, Boolean, Integer)}
*/
@Deprecated
List<WxCpUser> departGetUsers(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException;

/**
* @deprecated 请使用 {@link WxCpTagService#create(String)}
*/
@Deprecated
String tagCreate(String tagName) throws WxErrorException;

/**
* 更新标签
*
* @param tagId 标签id
* @param tagName 标签名
* @deprecated 请使用 {@link WxCpTagService#update(String, String)}
*/
@Deprecated
void tagUpdate(String tagId, String tagName) throws WxErrorException;

/**
* 删除标签
*
* @param tagId 标签id
* @deprecated 请使用 {@link WxCpTagService#delete(String)}
*/
@Deprecated
void tagDelete(String tagId) throws WxErrorException;

/**
* 获得标签列表
* @deprecated 请使用 {@link WxCpTagService#listAll()}
*/
@Deprecated
List<WxCpTag> tagGet() throws WxErrorException;

/**
* 获取标签成员
*
* @param tagId 标签ID
* @deprecated 请使用 {@link WxCpTagService#listUsersByTagId(String)}
*/
@Deprecated
List<WxCpUser> tagGetUsers(String tagId) throws WxErrorException;

/**
* 增加标签成员
*
* @param tagId 标签id
* @param userIds 用户ID 列表
* @deprecated 请使用 {@link WxCpTagService#addUsers2Tag(String, List, List)}
*/
@Deprecated
void tagAddUsers(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException;

/**
* <pre>
* 构造oauth2授权的url连接
* </pre>
*
* @param state 状态码
* @return url
* @deprecated 请使用 {@link WxCpTagService#removeUsersFromTag(String, List)}
*/
@Deprecated
void tagRemoveUsers(String tagId, List<String> userIds) throws WxErrorException;

/**
* @deprecated 请使用 {@link WxCpOAuth2Service#buildAuthorizationUrl(String)}
*/
@Deprecated
String oauth2buildAuthorizationUrl(String state);

/**
* <pre>
* 构造oauth2授权的url连接
* 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=企业获取code
* </pre>
*
* @param redirectUri 跳转链接地址
* @param state 状态码
* @return url
* @deprecated 请使用 {@link WxCpOAuth2Service#buildAuthorizationUrl(String, String)}
*/
@Deprecated
String oauth2buildAuthorizationUrl(String redirectUri, String state);

/**
* <pre>
* 用oauth2获取用户信息
* http://qydev.weixin.qq.com/wiki/index.php?title=根据code获取成员信息
* 因为企业号oauth2.0必须在应用设置里设置通过ICP备案的可信域名,所以无法测试,因此这个方法很可能是坏的。
*
* 注意: 这个方法使用WxCpConfigStorage里的agentId
* </pre>
*
* @param code 微信oauth授权返回的代码
* @return [userid, deviceid]
* @see #oauth2getUserInfo(Integer, String)
* @deprecated 请使用 {@link WxCpOAuth2Service#getUserInfo(String)}
*/
@Deprecated
String[] oauth2getUserInfo(String code) throws WxErrorException;

/**
* <pre>
* 用oauth2获取用户信息
* http://qydev.weixin.qq.com/wiki/index.php?title=根据code获取成员信息
* 因为企业号oauth2.0必须在应用设置里设置通过ICP备案的可信域名,所以无法测试,因此这个方法很可能是坏的。
*
* 注意: 这个方法不使用WxCpConfigStorage里的agentId,需要开发人员自己给出
* </pre>
*
* @param agentId 企业号应用的id
* @param code 微信oauth授权返回的代码
* @return [userid, deviceid]
* @see #oauth2getUserInfo(String)
* @deprecated 请使用 {@link WxCpOAuth2Service#getUserInfo(Integer, String)}
*/
@Deprecated
String[] oauth2getUserInfo(Integer agentId, String code) throws WxErrorException;


/**
* 移除标签成员
*
* @param tagId 标签id
* @param userIds 用户id列表
*/
void tagRemoveUsers(String tagId, List<String> userIds) throws WxErrorException;

/**
* <pre>
* 邀请成员关注
@@ -469,7 +330,7 @@ public interface WxCpService {
* <pre>
* Service没有实现某个API的时候,可以用这个,
* 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
* 可以参考,{@link me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor}的实现方法
* 可以参考,{@link MediaUploadRequestExecutor}的实现方法
* </pre>
*
* @param executor 执行器
@@ -480,13 +341,6 @@ public interface WxCpService {
*/
<T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException;

/**
* 注入 {@link WxCpConfigStorage} 的实现
*
* @param wxConfigProvider 配置对象
*/
void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider);

/**
* <pre>
* 设置当微信系统响应系统繁忙时,要等待多少 retrySleepMillis(ms) * 2^(重试次数 - 1) 再发起重试
@@ -550,4 +404,58 @@ public interface WxCpService {
* 获取异步任务结果
*/
String getTaskResult(String joinId) throws WxErrorException;

/**
* 初始化http请求对象
*/
void initHttp();

/**
* 获取WxMpConfigStorage 对象
*
* @return WxMpConfigStorage
*/
WxCpConfigStorage getWxCpConfigStorage();

/**
* 注入 {@link WxCpConfigStorage} 的实现
*
* @param wxConfigProvider 配置对象
*/
void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider);

/**
* 获取部门相关接口的服务类对象
*/
WxCpDepartmentService getDepartmentService();

/**
* 获取媒体相关接口的服务类对象
*/
WxCpMediaService getMediaService();

/**
* 获取菜单相关接口的服务类对象
*/
WxCpMenuService getMenuService();

/**
* 获取Oauth2相关接口的服务类对象
*/
WxCpOAuth2Service getOauth2Service();

/**
* 获取标签相关接口的服务类对象
*/
WxCpTagService getTagService();

/**
* 获取用户相关接口的服务类对象
*/
WxCpUserService getUserService();

/**
* http请求对象
*/
RequestHttp getRequestHttp();
}

+ 0
- 697
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpServiceImpl.java View File

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

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.session.StandardSessionManager;
import me.chanjar.weixin.common.session.WxSession;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.RandomUtils;
import me.chanjar.weixin.common.util.crypto.SHA1;
import me.chanjar.weixin.common.util.fs.FileUtils;
import me.chanjar.weixin.common.util.http.*;
import me.chanjar.weixin.common.util.json.GsonHelper;
import me.chanjar.weixin.cp.bean.WxCpDepart;
import me.chanjar.weixin.cp.bean.WxCpMessage;
import me.chanjar.weixin.cp.bean.WxCpTag;
import me.chanjar.weixin.cp.bean.WxCpUser;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public class WxCpServiceImpl implements WxCpService {

protected final Logger log = LoggerFactory.getLogger(WxCpServiceImpl.class);

/**
* 全局的是否正在刷新access token的锁
*/
protected final Object globalAccessTokenRefreshLock = new Object();

/**
* 全局的是否正在刷新jsapi_ticket的锁
*/
protected final Object globalJsapiTicketRefreshLock = new Object();

protected WxCpConfigStorage configStorage;

protected CloseableHttpClient httpClient;

protected HttpHost httpProxy;
protected WxSessionManager sessionManager = new StandardSessionManager();
/**
* 临时文件目录
*/
protected File tmpDirFile;
private int retrySleepMillis = 1000;
private int maxRetryTimes = 5;

@Override
public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) {
try {
return SHA1.gen(this.configStorage.getToken(), timestamp, nonce, data)
.equals(msgSignature);
} catch (Exception e) {
return false;
}
}

@Override
public void userAuthenticated(String userId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?userid=" + userId;
get(url, null);
}

@Override
public String getAccessToken() throws WxErrorException {
return getAccessToken(false);
}

@Override
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireAccessToken();
}
if (this.configStorage.isAccessTokenExpired()) {
synchronized (this.globalAccessTokenRefreshLock) {
if (this.configStorage.isAccessTokenExpired()) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+ "&corpid=" + this.configStorage.getCorpId()
+ "&corpsecret=" + this.configStorage.getCorpSecret();
try {
HttpGet httpGet = new HttpGet(url);
if (this.httpProxy != null) {
RequestConfig config = RequestConfig.custom()
.setProxy(this.httpProxy).build();
httpGet.setConfig(config);
}
String resultContent = null;
try (CloseableHttpClient httpclient = getHttpclient();
CloseableHttpResponse response = httpclient.execute(httpGet)) {
resultContent = new BasicResponseHandler().handleResponse(response);
} finally {
httpGet.releaseConnection();
}
WxError error = WxError.fromJson(resultContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateAccessToken(
accessToken.getAccessToken(), accessToken.getExpiresIn());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
return this.configStorage.getAccessToken();
}

@Override
public String getJsapiTicket() throws WxErrorException {
return getJsapiTicket(false);
}

@Override
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireJsapiTicket();
}
if (this.configStorage.isJsapiTicketExpired()) {
synchronized (this.globalJsapiTicketRefreshLock) {
if (this.configStorage.isJsapiTicketExpired()) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket";
String responseContent = execute(new SimpleGetRequestExecutor(), url, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
this.configStorage.updateJsapiTicket(jsapiTicket,
expiresInSeconds);
}
}
}
return this.configStorage.getJsapiTicket();
}

@Override
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException {
long timestamp = System.currentTimeMillis() / 1000;
String noncestr = RandomUtils.getRandomStr();
String jsapiTicket = getJsapiTicket(false);
String signature = SHA1.genWithAmple(
"jsapi_ticket=" + jsapiTicket,
"noncestr=" + noncestr,
"timestamp=" + timestamp,
"url=" + url
);
WxJsapiSignature jsapiSignature = new WxJsapiSignature();
jsapiSignature.setTimestamp(timestamp);
jsapiSignature.setNonceStr(noncestr);
jsapiSignature.setUrl(url);
jsapiSignature.setSignature(signature);

// Fixed bug
jsapiSignature.setAppId(this.configStorage.getCorpId());

return jsapiSignature;
}

@Override
public void messageSend(WxCpMessage message) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send";
post(url, message.toJson());
}

@Override
public void menuCreate(WxMenu menu) throws WxErrorException {
menuCreate(this.configStorage.getAgentId(), menu);
}

@Override
public void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/create?agentid="
+ this.configStorage.getAgentId();
post(url, menu.toJson());
}

@Override
public void menuDelete() throws WxErrorException {
menuDelete(this.configStorage.getAgentId());
}

@Override
public void menuDelete(Integer agentId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/delete?agentid=" + agentId;
get(url, null);
}

@Override
public WxMenu menuGet() throws WxErrorException {
return menuGet(this.configStorage.getAgentId());
}

@Override
public WxMenu menuGet(Integer agentId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/get?agentid=" + agentId;
try {
String resultContent = get(url, null);
return WxMenu.fromJson(resultContent);
} catch (WxErrorException e) {
// 46003 不存在的菜单数据
if (e.getError().getErrorCode() == 46003) {
return null;
}
throw e;
}
}

@Override
public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream)
throws WxErrorException, IOException {
return mediaUpload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType));
}

@Override
public WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType;
return execute(new MediaUploadRequestExecutor(), url, file);
}

@Override
public File mediaDownload(String mediaId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/media/get";
return execute(
new MediaDownloadRequestExecutor(
this.configStorage.getTmpDirFile()),
url, "media_id=" + mediaId);
}


@Override
public Integer departCreate(WxCpDepart depart) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create";
String responseContent = execute(
new SimplePostRequestExecutor(),
url,
depart.toJson());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id"));
}

@Override
public void departUpdate(WxCpDepart group) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/department/update";
post(url, group.toJson());
}

@Override
public void departDelete(Integer departId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/department/delete?id=" + departId;
get(url, null);
}

@Override
public List<WxCpDepart> departGet() throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/department/list";
String responseContent = get(url, null);
/*
* 操蛋的微信API,创建时返回的是 { group : { id : ..., name : ...} }
* 查询时返回的是 { groups : [ { id : ..., name : ..., count : ... }, ... ] }
*/
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.INSTANCE.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("department"),
new TypeToken<List<WxCpDepart>>() {
}.getType()
);
}

@Override
public void userCreate(WxCpUser user) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/create";
post(url, user.toJson());
}

@Override
public void userUpdate(WxCpUser user) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/update";
post(url, user.toJson());
}

@Override
public void userDelete(String userid) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/delete?userid=" + userid;
get(url, null);
}

@Override
public void userDelete(String[] userids) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/batchdelete";
JsonObject jsonObject = new JsonObject();
JsonArray jsonArray = new JsonArray();
for (String userid : userids) {
jsonArray.add(new JsonPrimitive(userid));
}
jsonObject.add("useridlist", jsonArray);
post(url, jsonObject.toString());
}

@Override
public WxCpUser userGet(String userid) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?userid=" + userid;
String responseContent = get(url, null);
return WxCpUser.fromJson(responseContent);
}

@Override
public List<WxCpUser> userList(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/list?department_id=" + departId;
String params = "";
if (fetchChild != null) {
params += "&fetch_child=" + (fetchChild ? "1" : "0");
}
if (status != null) {
params += "&status=" + status;
} else {
params += "&status=0";
}

String responseContent = get(url, params);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.INSTANCE.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("userlist"),
new TypeToken<List<WxCpUser>>() {
}.getType()
);
}

@Override
public List<WxCpUser> departGetUsers(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?department_id=" + departId;
String params = "";
if (fetchChild != null) {
params += "&fetch_child=" + (fetchChild ? "1" : "0");
}
if (status != null) {
params += "&status=" + status;
} else {
params += "&status=0";
}

String responseContent = get(url, params);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.INSTANCE.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("userlist"),
new TypeToken<List<WxCpUser>>() {
}.getType()
);
}

@Override
public String tagCreate(String tagName) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/create";
JsonObject o = new JsonObject();
o.addProperty("tagname", tagName);
String responseContent = post(url, o.toString());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return tmpJsonElement.getAsJsonObject().get("tagid").getAsString();
}

@Override
public void tagUpdate(String tagId, String tagName) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/update";
JsonObject o = new JsonObject();
o.addProperty("tagid", tagId);
o.addProperty("tagname", tagName);
post(url, o.toString());
}

@Override
public void tagDelete(String tagId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?tagid=" + tagId;
get(url, null);
}

@Override
public List<WxCpTag> tagGet() throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/list";
String responseContent = get(url, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.INSTANCE.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("taglist"),
new TypeToken<List<WxCpTag>>() {
}.getType()
);
}

@Override
public List<WxCpUser> tagGetUsers(String tagId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/get?tagid=" + tagId;
String responseContent = get(url, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.INSTANCE.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("userlist"),
new TypeToken<List<WxCpUser>>() {
}.getType()
);
}

@Override
public void tagAddUsers(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/addtagusers";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("tagid", tagId);
if (userIds != null) {
JsonArray jsonArray = new JsonArray();
for (String userId : userIds) {
jsonArray.add(new JsonPrimitive(userId));
}
jsonObject.add("userlist", jsonArray);
}
if (partyIds != null) {
JsonArray jsonArray = new JsonArray();
for (String userId : partyIds) {
jsonArray.add(new JsonPrimitive(userId));
}
jsonObject.add("partylist", jsonArray);
}
post(url, jsonObject.toString());
}

@Override
public void tagRemoveUsers(String tagId, List<String> userIds) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/deltagusers";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("tagid", tagId);
JsonArray jsonArray = new JsonArray();
for (String userId : userIds) {
jsonArray.add(new JsonPrimitive(userId));
}
jsonObject.add("userlist", jsonArray);
post(url, jsonObject.toString());
}

@Override
public String oauth2buildAuthorizationUrl(String state) {
return this.oauth2buildAuthorizationUrl(
this.configStorage.getOauth2redirectUri(),
state
);
}

@Override
public String oauth2buildAuthorizationUrl(String redirectUri, String state) {
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?";
url += "appid=" + this.configStorage.getCorpId();
url += "&redirect_uri=" + URIUtil.encodeURIComponent(redirectUri);
url += "&response_type=code";
url += "&scope=snsapi_base";
if (state != null) {
url += "&state=" + state;
}
url += "#wechat_redirect";
return url;
}

@Override
public String[] oauth2getUserInfo(String code) throws WxErrorException {
return oauth2getUserInfo(this.configStorage.getAgentId(), code);
}

@Override
public String[] oauth2getUserInfo(Integer agentId, String code) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?"
+ "code=" + code
+ "&agentid=" + agentId;
String responseText = get(url, null);
JsonElement je = new JsonParser().parse(responseText);
JsonObject jo = je.getAsJsonObject();
return new String[]{GsonHelper.getString(jo, "UserId"), GsonHelper.getString(jo, "DeviceId"), GsonHelper.getString(jo, "OpenId")};
}

@Override
public int invite(String userId, String inviteTips) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/invite/send";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("userid", userId);
if (StringUtils.isNotEmpty(inviteTips)) {
jsonObject.addProperty("invite_tips", inviteTips);
}
String responseContent = post(url, jsonObject.toString());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return tmpJsonElement.getAsJsonObject().get("type").getAsInt();
}

@Override
public String[] getCallbackIp() throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip";
String responseContent = get(url, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray();
String[] ips = new String[jsonArray.size()];
for (int i = 0; i < jsonArray.size(); i++) {
ips[i] = jsonArray.get(i).getAsString();
}
return ips;
}

@Override
public String get(String url, String queryParam) throws WxErrorException {
return execute(new SimpleGetRequestExecutor(), url, queryParam);
}

@Override
public String post(String url, String postData) throws WxErrorException {
return execute(new SimplePostRequestExecutor(), url, postData);
}

/**
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
*/
@Override
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
int retryTimes = 0;
do {
try {
T result = this.executeInternal(executor, uri, data);
this.log.debug("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}",uri, data, result);
return result;
} catch (WxErrorException e) {
if (retryTimes + 1 > this.maxRetryTimes) {
this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
//最后一次重试失败后,直接抛出异常,不再等待
throw new RuntimeException("微信服务端异常,超出重试次数");
}

WxError error = e.getError();
/*
* -1 系统繁忙, 1000ms后重试
*/
if (error.getErrorCode() == -1) {
int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
try {
this.log.debug("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
Thread.sleep(sleepMillis);
} catch (InterruptedException e1) {
throw new RuntimeException(e1);
}
} else {
throw e;
}
}
} while (retryTimes++ < this.maxRetryTimes);

this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
throw new RuntimeException("微信服务端异常,超出重试次数");
}

protected synchronized <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
if (uri.contains("access_token=")) {
throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
}
String accessToken = getAccessToken(false);

String uriWithAccessToken = uri;
uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;

try {
return executor.execute(getHttpclient(), this.httpProxy, uriWithAccessToken, data);
} catch (WxErrorException e) {
WxError error = e.getError();
/*
* 发生以下情况时尝试刷新access_token
* 40001 获取access_token时AppSecret错误,或者access_token无效
* 42001 access_token超时
*/
if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
// 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
this.configStorage.expireAccessToken();
return execute(executor, uri, data);
}

if (error.getErrorCode() != 0) {
this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[RESPONSE]: {}", uri, data, error);
throw new WxErrorException(error);
}
return null;
} catch (IOException e) {
this.log.error("\n[URL]: {}\n[PARAMS]: {}\n[EXCEPTION]: {}", uri, data, e.getMessage());
throw new RuntimeException(e);
}
}

protected CloseableHttpClient getHttpclient() {
return this.httpClient;
}

@Override
public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) {
this.configStorage = wxConfigProvider;
ApacheHttpClientBuilder apacheHttpClientBuilder = this.configStorage
.getApacheHttpClientBuilder();
if (null == apacheHttpClientBuilder) {
apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get();
}

apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
.httpProxyPort(this.configStorage.getHttpProxyPort())
.httpProxyUsername(this.configStorage.getHttpProxyUsername())
.httpProxyPassword(this.configStorage.getHttpProxyPassword());

if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort());
}

this.httpClient = apacheHttpClientBuilder.build();
}

@Override
public void setRetrySleepMillis(int retrySleepMillis) {
this.retrySleepMillis = retrySleepMillis;
}


@Override
public void setMaxRetryTimes(int maxRetryTimes) {
this.maxRetryTimes = maxRetryTimes;
}

@Override
public WxSession getSession(String id) {
if (this.sessionManager == null) {
return null;
}
return this.sessionManager.getSession(id);
}

@Override
public WxSession getSession(String id, boolean create) {
if (this.sessionManager == null) {
return null;
}
return this.sessionManager.getSession(id, create);
}


@Override
public void setSessionManager(WxSessionManager sessionManager) {
this.sessionManager = sessionManager;
}

@Override
public String replaceParty(String mediaId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("media_id", mediaId);
return post(url, jsonObject.toString());
}

@Override
public String replaceUser(String mediaId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("media_id", mediaId);
return post(url, jsonObject.toString());
}

@Override
public String getTaskResult(String joinId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid=" + joinId;
return get(url, null);
}

public File getTmpDirFile() {
return this.tmpDirFile;
}

public void setTmpDirFile(File tmpDirFile) {
this.tmpDirFile = tmpDirFile;
}


}

+ 66
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTagService.java View File

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

import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpTag;
import me.chanjar.weixin.cp.bean.WxCpTagAddOrRemoveUsersResult;
import me.chanjar.weixin.cp.bean.WxCpUser;

import java.util.List;

/**
* <pre>
* 标签管理接口
* Created by BinaryWang on 2017/6/24.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public interface WxCpTagService {
/**
* 创建标签
*
* @param tagName 标签名
*/
String create(String tagName) throws WxErrorException;

/**
* 更新标签
*
* @param tagId 标签id
* @param tagName 标签名
*/
void update(String tagId, String tagName) throws WxErrorException;

/**
* 删除标签
*
* @param tagId 标签id
*/
void delete(String tagId) throws WxErrorException;

/**
* 获得标签列表
*/
List<WxCpTag> listAll() throws WxErrorException;

/**
* 获取标签成员
*
* @param tagId 标签ID
*/
List<WxCpUser> listUsersByTagId(String tagId) throws WxErrorException;

/**
* 增加标签成员
* @param tagId 标签id
* @param userIds 用户ID 列表
*/
WxCpTagAddOrRemoveUsersResult addUsers2Tag(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException;

/**
* 移除标签成员
* @param tagId 标签id
* @param userIds 用户id列表
*/
WxCpTagAddOrRemoveUsersResult removeUsersFromTag(String tagId, List<String> userIds) throws WxErrorException;
}

+ 83
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java View File

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

import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.cp.bean.WxCpUser;

import java.util.List;

/**
* <pre>
* 用户管理接口
* Created by BinaryWang on 2017/6/24.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public interface WxCpUserService {
/**
* <pre>
* 用在二次验证的时候
* 企业在员工验证成功后,调用本方法告诉企业号平台该员工关注成功。
* </pre>
*
* @param userId 用户id
*/
void authenticate(String userId) throws WxErrorException;

/**
* <pre>
* 获取部门成员(详情)
*
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29
* </pre>
*
* @param departId 必填。部门id
* @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员
* @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加
*/
List<WxCpUser> listByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException;

/**
* <pre>
* 获取部门成员
*
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98
* </pre>
*
* @param departId 必填。部门id
* @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员
* @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加
*/
List<WxCpUser> listSimpleByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException;

/**
* 新建用户
*
* @param user 用户对象
*/
void create(WxCpUser user) throws WxErrorException;

/**
* 更新用户
*
* @param user 用户对象
*/
void update(WxCpUser user) throws WxErrorException;

/**
* <pre>
* 删除用户/批量删除成员
* http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E6.89.B9.E9.87.8F.E5.88.A0.E9.99.A4.E6.88.90.E5.91.98
* </pre>
*
* @param userIds 员工UserID列表。对应管理端的帐号
*/
void delete(String... userIds) throws WxErrorException;

/**
* 获取用户
*
* @param userid 用户id
*/
WxCpUser getById(String userid) throws WxErrorException;
}

+ 543
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/AbstractWxCpServiceImpl.java View File

@@ -0,0 +1,543 @@
package me.chanjar.weixin.cp.api.impl;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.session.StandardSessionManager;
import me.chanjar.weixin.common.session.WxSession;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.RandomUtils;
import me.chanjar.weixin.common.util.crypto.SHA1;
import me.chanjar.weixin.common.util.http.RequestExecutor;
import me.chanjar.weixin.common.util.http.RequestHttp;
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
import me.chanjar.weixin.cp.api.*;
import me.chanjar.weixin.cp.bean.*;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

public abstract class AbstractWxCpServiceImpl<H, P> implements WxCpService, RequestHttp<H, P> {
protected final Logger log = LoggerFactory.getLogger(this.getClass());

private WxCpUserService userService = new WxCpUserServiceImpl(this);
private WxCpDepartmentService departmentService = new WxCpDepartmentServiceImpl(this);
private WxCpMediaService mediaService = new WxCpMediaServiceImpl(this);
private WxCpMenuService menuService = new WxCpMenuServiceImpl(this);
private WxCpOAuth2Service oauth2Service = new WxCpOAuth2ServiceImpl(this);
private WxCpTagService tagService = new WxCpTagServiceImpl(this);

/**
* 全局的是否正在刷新access token的锁
*/
protected final Object globalAccessTokenRefreshLock = new Object();

/**
* 全局的是否正在刷新jsapi_ticket的锁
*/
protected final Object globalJsapiTicketRefreshLock = new Object();

protected WxCpConfigStorage configStorage;


protected WxSessionManager sessionManager = new StandardSessionManager();
/**
* 临时文件目录
*/
protected File tmpDirFile;
private int retrySleepMillis = 1000;
private int maxRetryTimes = 5;

@Override
public boolean checkSignature(String msgSignature, String timestamp, String nonce, String data) {
try {
return SHA1.gen(this.configStorage.getToken(), timestamp, nonce, data)
.equals(msgSignature);
} catch (Exception e) {
this.log.error("Checking signature failed, and the reason is :" + e.getMessage());
return false;
}
}

@Override
public String getAccessToken() throws WxErrorException {
return getAccessToken(false);
}


@Override
public String getJsapiTicket() throws WxErrorException {
return getJsapiTicket(false);
}

@Override
public String getJsapiTicket(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireJsapiTicket();
}
if (this.configStorage.isJsapiTicketExpired()) {
synchronized (this.globalJsapiTicketRefreshLock) {
if (this.configStorage.isJsapiTicketExpired()) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket";
String responseContent = execute(SimpleGetRequestExecutor.create(this), url, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject();
String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
this.configStorage.updateJsapiTicket(jsapiTicket,
expiresInSeconds);
}
}
}
return this.configStorage.getJsapiTicket();
}

@Override
public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException {
long timestamp = System.currentTimeMillis() / 1000;
String noncestr = RandomUtils.getRandomStr();
String jsapiTicket = getJsapiTicket(false);
String signature = SHA1.genWithAmple(
"jsapi_ticket=" + jsapiTicket,
"noncestr=" + noncestr,
"timestamp=" + timestamp,
"url=" + url
);
WxJsapiSignature jsapiSignature = new WxJsapiSignature();
jsapiSignature.setTimestamp(timestamp);
jsapiSignature.setNonceStr(noncestr);
jsapiSignature.setUrl(url);
jsapiSignature.setSignature(signature);

// Fixed bug
jsapiSignature.setAppId(this.configStorage.getCorpId());

return jsapiSignature;
}

@Override
public WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send";
return WxCpMessageSendResult.fromJson(this.post(url, message.toJson()));
}

@Override
@Deprecated
public void menuCreate(WxMenu menu) throws WxErrorException {
this.getMenuService().create(menu);
}

@Override
@Deprecated
public void menuCreate(Integer agentId, WxMenu menu) throws WxErrorException {
this.getMenuService().create(agentId, menu);
}

@Override
@Deprecated
public void menuDelete() throws WxErrorException {
this.getMenuService().delete();
}

@Override
@Deprecated
public void menuDelete(Integer agentId) throws WxErrorException {
this.getMenuService().delete(agentId);
}

@Override
@Deprecated
public WxMenu menuGet() throws WxErrorException {
return this.getMenuService().get();
}

@Override
@Deprecated
public WxMenu menuGet(Integer agentId) throws WxErrorException {
return this.getMenuService().get(agentId);
}

@Override
@Deprecated
public WxMediaUploadResult mediaUpload(String mediaType, String fileType, InputStream inputStream)
throws WxErrorException, IOException {
return this.getMediaService().upload(mediaType, fileType, inputStream);
}

@Override
@Deprecated
public WxMediaUploadResult mediaUpload(String mediaType, File file) throws WxErrorException {
return this.getMediaService().upload(mediaType, file);
}

@Override
@Deprecated
public File mediaDownload(String mediaId) throws WxErrorException {
return this.getMediaService().download(mediaId);
}

@Override
@Deprecated
public void userAuthenticated(String userId) throws WxErrorException {
this.getUserService().authenticate(userId);
}

@Override
@Deprecated
public void userCreate(WxCpUser user) throws WxErrorException {
this.getUserService().create(user);
}

@Override
@Deprecated
public void userUpdate(WxCpUser user) throws WxErrorException {
this.getUserService().update(user);
}

@Override
@Deprecated
public void userDelete(String userid) throws WxErrorException {
this.getUserService().delete(userid);
}

@Override
@Deprecated
public void userDelete(String[] userids) throws WxErrorException {
this.getUserService().delete(userids);
}

@Override
@Deprecated
public WxCpUser userGet(String userid) throws WxErrorException {
return this.getUserService().getById(userid);
}

@Override
@Deprecated
public List<WxCpUser> userList(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException {
return this.getUserService().listByDepartment(departId, fetchChild, status);
}

@Override
@Deprecated
public List<WxCpUser> departGetUsers(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException {
return this.getUserService().listSimpleByDepartment(departId, fetchChild, status);
}

@Override
@Deprecated
public String tagCreate(String tagName) throws WxErrorException {
return this.getTagService().create(tagName);
}

@Override
@Deprecated
public void tagUpdate(String tagId, String tagName) throws WxErrorException {
this.getTagService().update(tagId, tagName);
}

@Override
@Deprecated
public void tagDelete(String tagId) throws WxErrorException {
this.getTagService().delete(tagId);
}

@Override
@Deprecated
public List<WxCpTag> tagGet() throws WxErrorException {
return this.getTagService().listAll();
}

@Override
@Deprecated
public List<WxCpUser> tagGetUsers(String tagId) throws WxErrorException {
return this.getTagService().listUsersByTagId(tagId);
}

@Override
@Deprecated
public void tagAddUsers(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException {
this.getTagService().addUsers2Tag(tagId, userIds, partyIds);
}

@Override
@Deprecated
public void tagRemoveUsers(String tagId, List<String> userIds) throws WxErrorException {
this.getTagService().removeUsersFromTag(tagId, userIds);
}

@Override
@Deprecated
public String oauth2buildAuthorizationUrl(String state) {
return this.getOauth2Service().buildAuthorizationUrl(state);
}

@Override
@Deprecated
public String oauth2buildAuthorizationUrl(String redirectUri, String state) {
return this.getOauth2Service().buildAuthorizationUrl(redirectUri, state);
}

@Override
@Deprecated
public String[] oauth2getUserInfo(String code) throws WxErrorException {
return this.getOauth2Service().getUserInfo(code);
}

@Override
@Deprecated
public String[] oauth2getUserInfo(Integer agentId, String code) throws WxErrorException {
return this.getOauth2Service().getUserInfo(agentId, code);
}

@Override
public int invite(String userId, String inviteTips) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/invite/send";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("userid", userId);
if (StringUtils.isNotEmpty(inviteTips)) {
jsonObject.addProperty("invite_tips", inviteTips);
}
String responseContent = post(url, jsonObject.toString());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return tmpJsonElement.getAsJsonObject().get("type").getAsInt();
}

@Override
public String[] getCallbackIp() throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip";
String responseContent = get(url, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray();
String[] ips = new String[jsonArray.size()];
for (int i = 0; i < jsonArray.size(); i++) {
ips[i] = jsonArray.get(i).getAsString();
}
return ips;
}

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

/**
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
*/
@Override
public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
int retryTimes = 0;
do {
try {
return this.executeInternal(executor, uri, data);
} catch (WxErrorException e) {
if (retryTimes + 1 > this.maxRetryTimes) {
this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
//最后一次重试失败后,直接抛出异常,不再等待
throw new RuntimeException("微信服务端异常,超出重试次数");
}

WxError error = e.getError();
/*
* -1 系统繁忙, 1000ms后重试
*/
if (error.getErrorCode() == -1) {
int sleepMillis = this.retrySleepMillis * (1 << retryTimes);
try {
this.log.debug("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
Thread.sleep(sleepMillis);
} catch (InterruptedException e1) {
throw new RuntimeException(e1);
}
} else {
throw e;
}
}
} while (retryTimes++ < this.maxRetryTimes);

this.log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
throw new RuntimeException("微信服务端异常,超出重试次数");
}

protected synchronized <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
if (uri.contains("access_token=")) {
throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
}
String accessToken = getAccessToken(false);

String uriWithAccessToken = uri + (uri.contains("?") ? "&" : "?") + "access_token=" + accessToken;

try {
T result = executor.execute(uriWithAccessToken, data);
this.log.debug("\n【请求地址】: {}\n【请求参数】:{}\n【响应数据】:{}", uriWithAccessToken, 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【错误信息】:{}", uriWithAccessToken, data, error);
throw new WxErrorException(error);
}
return null;
} catch (IOException e) {
this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, data, e.getMessage());
throw new RuntimeException(e);
}
}

@Override
public void setWxCpConfigStorage(WxCpConfigStorage wxConfigProvider) {
this.configStorage = wxConfigProvider;
this.initHttp();
}

@Override
public void setRetrySleepMillis(int retrySleepMillis) {
this.retrySleepMillis = retrySleepMillis;
}


@Override
public void setMaxRetryTimes(int maxRetryTimes) {
this.maxRetryTimes = maxRetryTimes;
}

@Override
public WxSession getSession(String id) {
if (this.sessionManager == null) {
return null;
}
return this.sessionManager.getSession(id);
}

@Override
public WxSession getSession(String id, boolean create) {
if (this.sessionManager == null) {
return null;
}
return this.sessionManager.getSession(id, create);
}

@Override
public void setSessionManager(WxSessionManager sessionManager) {
this.sessionManager = sessionManager;
}

@Override
public String replaceParty(String mediaId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("media_id", mediaId);
return post(url, jsonObject.toString());
}

@Override
public String replaceUser(String mediaId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("media_id", mediaId);
return post(url, jsonObject.toString());
}

@Override
public String getTaskResult(String joinId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid=" + joinId;
return get(url, null);
}

public File getTmpDirFile() {
return this.tmpDirFile;
}

public void setTmpDirFile(File tmpDirFile) {
this.tmpDirFile = tmpDirFile;
}

@Override
public WxCpDepartmentService getDepartmentService() {
return departmentService;
}

@Override
public WxCpMediaService getMediaService() {
return mediaService;
}

@Override
public WxCpMenuService getMenuService() {
return menuService;
}

@Override
public WxCpOAuth2Service getOauth2Service() {
return oauth2Service;
}

@Override
public WxCpTagService getTagService() {
return tagService;
}

@Override
public WxCpUserService getUserService() {
return userService;
}

@Override
@Deprecated
public Integer departCreate(WxCpDepart depart) throws WxErrorException {
return this.getDepartmentService().create(depart);
}

@Override
@Deprecated
public void departUpdate(WxCpDepart depart) throws WxErrorException {
this.getDepartmentService().update(depart);
}

@Override
@Deprecated
public void departDelete(Integer departId) throws WxErrorException {
this.getDepartmentService().delete(departId);
}

@Override
@Deprecated
public List<WxCpDepart> departGet() throws WxErrorException {
return this.getDepartmentService().listAll();
}

@Override
public RequestHttp getRequestHttp() {
return this;
}
}

+ 65
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpDepartmentServiceImpl.java View File

@@ -0,0 +1,65 @@
package me.chanjar.weixin.cp.api.impl;

import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.json.GsonHelper;
import me.chanjar.weixin.cp.api.WxCpDepartmentService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpDepart;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.util.List;

/**
* <pre>
* 部门管理接口
* Created by BinaryWang on 2017/6/24.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxCpDepartmentServiceImpl implements WxCpDepartmentService {
private WxCpService mainService;

public WxCpDepartmentServiceImpl(WxCpService mainService) {
this.mainService = mainService;
}

@Override
public Integer create(WxCpDepart depart) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create";
String responseContent = this.mainService.post(url, depart.toJson());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id"));
}

@Override
public void update(WxCpDepart group) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/department/update";
this.mainService.post(url, group.toJson());
}

@Override
public void delete(Integer departId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/department/delete?id=" + departId;
this.mainService.get(url, null);
}

@Override
public List<WxCpDepart> listAll() throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/department/list";
String responseContent = this.mainService.get(url, null);
/*
* 操蛋的微信API,创建时返回的是 { group : { id : ..., name : ...} }
* 查询时返回的是 { groups : [ { id : ..., name : ..., count : ... }, ... ] }
*/
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.INSTANCE.create()
.fromJson(tmpJsonElement.getAsJsonObject().get("department"),
new TypeToken<List<WxCpDepart>>() {
}.getType()
);
}
}

+ 50
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMediaServiceImpl.java View File

@@ -0,0 +1,50 @@
package me.chanjar.weixin.cp.api.impl;

import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.fs.FileUtils;
import me.chanjar.weixin.common.util.http.MediaDownloadRequestExecutor;
import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
import me.chanjar.weixin.cp.api.WxCpMediaService;
import me.chanjar.weixin.cp.api.WxCpService;

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

/**
* <pre>
* 媒体管理接口
* Created by Binary Wang on 2017-6-25.
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* </pre>
*/
public class WxCpMediaServiceImpl implements WxCpMediaService {
private WxCpService mainService;

public WxCpMediaServiceImpl(WxCpService mainService) {
this.mainService = mainService;
}

@Override
public WxMediaUploadResult upload(String mediaType, String fileType, InputStream inputStream)
throws WxErrorException, IOException {
return this.upload(mediaType, FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType));
}

@Override
public WxMediaUploadResult upload(String mediaType, File file) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/media/upload?type=" + mediaType;
return this.mainService.execute(MediaUploadRequestExecutor.create(this.mainService.getRequestHttp()), url, file);
}

@Override
public File download(String mediaId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/media/get";
return this.mainService.execute(
MediaDownloadRequestExecutor.create(this.mainService.getRequestHttp(),
this.mainService.getWxCpConfigStorage().getTmpDirFile()),
url, "media_id=" + mediaId);
}
}

+ 64
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMenuServiceImpl.java View File

@@ -0,0 +1,64 @@
package me.chanjar.weixin.cp.api.impl;

import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpMenuService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

/**
* <pre>
* 菜单管理相关接口
* Created by Binary Wang on 2017-6-25.
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* </pre>
*/
public class WxCpMenuServiceImpl implements WxCpMenuService {
private WxCpService mainService;

public WxCpMenuServiceImpl(WxCpService mainService) {
this.mainService = mainService;
}

@Override
public void create(WxMenu menu) throws WxErrorException {
this.create(this.mainService.getWxCpConfigStorage().getAgentId(), menu);
}

@Override
public void create(Integer agentId, WxMenu menu) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/create?agentid=" + agentId;
this.mainService.post(url, menu.toJson());
}

@Override
public void delete() throws WxErrorException {
this.delete(this.mainService.getWxCpConfigStorage().getAgentId());
}

@Override
public void delete(Integer agentId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/delete?agentid=" + agentId;
this.mainService.get(url, null);
}

@Override
public WxMenu get() throws WxErrorException {
return this.get(this.mainService.getWxCpConfigStorage().getAgentId());
}

@Override
public WxMenu get(Integer agentId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/menu/get?agentid=" + agentId;
try {
String resultContent = this.mainService.get(url, null);
return WxCpGsonBuilder.create().fromJson(resultContent, WxMenu.class);
} catch (WxErrorException e) {
// 46003 不存在的菜单数据
if (e.getError().getErrorCode() == 46003) {
return null;
}
throw e;
}
}
}

+ 66
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOAuth2ServiceImpl.java View File

@@ -0,0 +1,66 @@
package me.chanjar.weixin.cp.api.impl;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.URIUtil;
import me.chanjar.weixin.common.util.json.GsonHelper;
import me.chanjar.weixin.cp.api.WxCpOAuth2Service;
import me.chanjar.weixin.cp.api.WxCpService;

/**
* <pre>
*
* Created by Binary Wang on 2017-6-25.
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* </pre>
*/
public class WxCpOAuth2ServiceImpl implements WxCpOAuth2Service {
private WxCpService mainService;

public WxCpOAuth2ServiceImpl(WxCpService mainService) {
this.mainService = mainService;
}

@Override
public String buildAuthorizationUrl(String state) {
return this.buildAuthorizationUrl(
this.mainService.getWxCpConfigStorage().getOauth2redirectUri(),
state
);
}

@Override
public String buildAuthorizationUrl(String redirectUri, String state) {
String url = "https://open.weixin.qq.com/connect/oauth2/authorize?";
url += "appid=" + this.mainService.getWxCpConfigStorage().getCorpId();
url += "&redirect_uri=" + URIUtil.encodeURIComponent(redirectUri);
url += "&response_type=code";
url += "&scope=snsapi_base";
if (state != null) {
url += "&state=" + state;
}
url += "#wechat_redirect";
return url;
}

@Override
public String[] getUserInfo(String code) throws WxErrorException {
return getUserInfo(this.mainService.getWxCpConfigStorage().getAgentId(), code);
}

@Override
public String[] getUserInfo(Integer agentId, String code) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/getuserinfo?"
+ "code=" + code
+ "&agentid=" + agentId;
String responseText = this.mainService.get(url, null);
JsonElement je = new JsonParser().parse(responseText);
JsonObject jo = je.getAsJsonObject();
return new String[]{GsonHelper.getString(jo, "UserId"),
GsonHelper.getString(jo, "DeviceId"),
GsonHelper.getString(jo, "OpenId")};
}

}

+ 105
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java View File

@@ -0,0 +1,105 @@
package me.chanjar.weixin.cp.api.impl;


import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.HttpType;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.BasicResponseHandler;
import org.apache.http.impl.client.CloseableHttpClient;

import java.io.IOException;

public class WxCpServiceApacheHttpClientImpl extends AbstractWxCpServiceImpl<CloseableHttpClient, HttpHost> {
protected CloseableHttpClient httpClient;
protected HttpHost httpProxy;

@Override
public CloseableHttpClient getRequestHttpClient() {
return httpClient;
}

@Override
public HttpHost getRequestHttpProxy() {
return httpProxy;
}

@Override
public HttpType getRequestType() {
return HttpType.APACHE_HTTP;
}

@Override
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireAccessToken();
}
if (this.configStorage.isAccessTokenExpired()) {
synchronized (this.globalAccessTokenRefreshLock) {
if (this.configStorage.isAccessTokenExpired()) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+ "&corpid=" + this.configStorage.getCorpId()
+ "&corpsecret=" + this.configStorage.getCorpSecret();
try {
HttpGet httpGet = new HttpGet(url);
if (this.httpProxy != null) {
RequestConfig config = RequestConfig.custom()
.setProxy(this.httpProxy).build();
httpGet.setConfig(config);
}
String resultContent = null;
try (CloseableHttpClient httpclient = getRequestHttpClient();
CloseableHttpResponse response = httpclient.execute(httpGet)) {
resultContent = new BasicResponseHandler().handleResponse(response);
} finally {
httpGet.releaseConnection();
}
WxError error = WxError.fromJson(resultContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateAccessToken(
accessToken.getAccessToken(), accessToken.getExpiresIn());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
return this.configStorage.getAccessToken();
}

@Override
public void initHttp() {
ApacheHttpClientBuilder apacheHttpClientBuilder = this.configStorage
.getApacheHttpClientBuilder();
if (null == apacheHttpClientBuilder) {
apacheHttpClientBuilder = DefaultApacheHttpClientBuilder.get();
}

apacheHttpClientBuilder.httpProxyHost(this.configStorage.getHttpProxyHost())
.httpProxyPort(this.configStorage.getHttpProxyPort())
.httpProxyUsername(this.configStorage.getHttpProxyUsername())
.httpProxyPassword(this.configStorage.getHttpProxyPassword());

if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
this.httpProxy = new HttpHost(this.configStorage.getHttpProxyHost(), this.configStorage.getHttpProxyPort());
}

this.httpClient = apacheHttpClientBuilder.build();
}

@Override
public WxCpConfigStorage getWxCpConfigStorage() {
return this.configStorage;
}

}

+ 11
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java View File

@@ -0,0 +1,11 @@
package me.chanjar.weixin.cp.api.impl;

/**
* <pre>
* 默认接口实现类,使用apache httpclient实现
* Created by Binary Wang on 2017-5-27.
* @author <a href="https://github.com/binarywang">binarywang(Binary Wang)</a>
* </pre>
*/
public class WxCpServiceImpl extends WxCpServiceApacheHttpClientImpl {
}

+ 76
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java View File

@@ -0,0 +1,76 @@
package me.chanjar.weixin.cp.api.impl;

import jodd.http.*;
import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.HttpType;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;

public class WxCpServiceJoddHttpImpl extends AbstractWxCpServiceImpl<HttpConnectionProvider, ProxyInfo> {
protected HttpConnectionProvider httpClient;
protected ProxyInfo httpProxy;


@Override
public HttpConnectionProvider getRequestHttpClient() {
return httpClient;
}

@Override
public ProxyInfo getRequestHttpProxy() {
return httpProxy;
}

@Override
public HttpType getRequestType() {
return HttpType.JODD_HTTP;
}

@Override
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireAccessToken();
}
if (this.configStorage.isAccessTokenExpired()) {
synchronized (this.globalAccessTokenRefreshLock) {
if (this.configStorage.isAccessTokenExpired()) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+ "&corpid=" + this.configStorage.getCorpId()
+ "&corpsecret=" + this.configStorage.getCorpSecret();

HttpRequest request = HttpRequest.get(url);
if (this.httpProxy != null) {
httpClient.useProxy(this.httpProxy);
}
request.withConnectionProvider(httpClient);
HttpResponse response = request.send();

String resultContent = response.bodyText();
WxError error = WxError.fromJson(resultContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateAccessToken(
accessToken.getAccessToken(), accessToken.getExpiresIn());
}
}
}
return this.configStorage.getAccessToken();
}

@Override
public void initHttp() {
if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
httpProxy = new ProxyInfo(ProxyInfo.ProxyType.HTTP, configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword());
}

httpClient = JoddHttp.httpConnectionProvider;
}

@Override
public WxCpConfigStorage getWxCpConfigStorage() {
return this.configStorage;
}
}

+ 104
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOkHttpImpl.java View File

@@ -0,0 +1,104 @@
package me.chanjar.weixin.cp.api.impl;

import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.util.http.HttpType;
import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import okhttp3.*;

import java.io.IOException;

public class WxCpServiceOkHttpImpl extends AbstractWxCpServiceImpl<ConnectionPool, OkHttpProxyInfo> {
protected ConnectionPool httpClient;
protected OkHttpProxyInfo httpProxy;


@Override
public ConnectionPool getRequestHttpClient() {
return httpClient;
}

@Override
public OkHttpProxyInfo getRequestHttpProxy() {
return httpProxy;
}

@Override
public HttpType getRequestType() {
return HttpType.OK_HTTP;
}

@Override
public String getAccessToken(boolean forceRefresh) throws WxErrorException {
if (forceRefresh) {
this.configStorage.expireAccessToken();
}
if (this.configStorage.isAccessTokenExpired()) {
synchronized (this.globalAccessTokenRefreshLock) {
if (this.configStorage.isAccessTokenExpired()) {
String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?"
+ "&corpid=" + this.configStorage.getCorpId()
+ "&corpsecret=" + this.configStorage.getCorpSecret();

OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder().connectionPool(httpClient);
//设置代理
if (httpProxy != null) {
clientBuilder.proxy(getRequestHttpProxy().getProxy());
}
//设置授权
clientBuilder.authenticator(new Authenticator() {
@Override
public Request authenticate(Route route, Response response) throws IOException {
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
return response.request().newBuilder()
.header("Authorization", credential)
.build();
}
});
//得到httpClient
OkHttpClient client = clientBuilder.build();
//请求的request
Request request = new Request.Builder().url(url).get().build();
Response response = null;
try {
response = client.newCall(request).execute();
} catch (IOException e) {
e.printStackTrace();
}
String resultContent = null;
try {
resultContent = response.body().string();
} catch (IOException e) {
e.printStackTrace();
}
WxError error = WxError.fromJson(resultContent);
if (error.getErrorCode() != 0) {
throw new WxErrorException(error);
}
WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
this.configStorage.updateAccessToken(accessToken.getAccessToken(),
accessToken.getExpiresIn());
}
}
}
return this.configStorage.getAccessToken();
}

@Override
public void initHttp() {
WxCpConfigStorage configStorage = this.configStorage;

if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
httpProxy = new OkHttpProxyInfo(OkHttpProxyInfo.ProxyType.SOCKS5, configStorage.getHttpProxyHost(), configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword());
}

httpClient = new ConnectionPool();
}

@Override
public WxCpConfigStorage getWxCpConfigStorage() {
return this.configStorage;
}
}

+ 116
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTagServiceImpl.java View File

@@ -0,0 +1,116 @@
package me.chanjar.weixin.cp.api.impl;

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.WxCpTagService;
import me.chanjar.weixin.cp.bean.WxCpTag;
import me.chanjar.weixin.cp.bean.WxCpTagAddOrRemoveUsersResult;
import me.chanjar.weixin.cp.bean.WxCpUser;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.util.List;

/**
* <pre>
* 标签管理接口
* Created by Binary Wang on 2017-6-25.
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* </pre>
*/
public class WxCpTagServiceImpl implements WxCpTagService {
private WxCpService mainService;

public WxCpTagServiceImpl(WxCpService mainService) {
this.mainService = mainService;
}

@Override
public String create(String tagName) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/create";
JsonObject o = new JsonObject();
o.addProperty("tagname", tagName);
String responseContent = this.mainService.post(url, o.toString());
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return tmpJsonElement.getAsJsonObject().get("tagid").getAsString();
}

@Override
public void update(String tagId, String tagName) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/update";
JsonObject o = new JsonObject();
o.addProperty("tagid", tagId);
o.addProperty("tagname", tagName);
this.mainService.post(url, o.toString());
}

@Override
public void delete(String tagId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/delete?tagid=" + tagId;
this.mainService.get(url, null);
}

@Override
public List<WxCpTag> listAll() throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/list";
String responseContent = this.mainService.get(url, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.INSTANCE.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("taglist"),
new TypeToken<List<WxCpTag>>() {
}.getType()
);
}

@Override
public List<WxCpUser> listUsersByTagId(String tagId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/get?tagid=" + tagId;
String responseContent = this.mainService.get(url, null);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.INSTANCE.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("userlist"),
new TypeToken<List<WxCpUser>>() {
}.getType()
);
}

@Override
public WxCpTagAddOrRemoveUsersResult addUsers2Tag(String tagId, List<String> userIds, List<String> partyIds) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/addtagusers";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("tagid", tagId);
if (userIds != null) {
JsonArray jsonArray = new JsonArray();
for (String userId : userIds) {
jsonArray.add(new JsonPrimitive(userId));
}
jsonObject.add("userlist", jsonArray);
}
if (partyIds != null) {
JsonArray jsonArray = new JsonArray();
for (String userId : partyIds) {
jsonArray.add(new JsonPrimitive(userId));
}
jsonObject.add("partylist", jsonArray);
}

return WxCpTagAddOrRemoveUsersResult.fromJson(this.mainService.post(url, jsonObject.toString()));
}

@Override
public WxCpTagAddOrRemoveUsersResult removeUsersFromTag(String tagId, List<String> userIds) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/tag/deltagusers";
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("tagid", tagId);
JsonArray jsonArray = new JsonArray();
for (String userId : userIds) {
jsonArray.add(new JsonPrimitive(userId));
}
jsonObject.add("userlist", jsonArray);

return WxCpTagAddOrRemoveUsersResult.fromJson(this.mainService.post(url, jsonObject.toString()));
}
}

+ 118
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java View File

@@ -0,0 +1,118 @@
package me.chanjar.weixin.cp.api.impl;

import com.google.gson.*;
import com.google.gson.reflect.TypeToken;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.WxCpUserService;
import me.chanjar.weixin.cp.bean.WxCpUser;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.util.List;

/**
* <pre>
* Created by BinaryWang on 2017/6/24.
* </pre>
*
* @author <a href="https://github.com/binarywang">Binary Wang</a>
*/
public class WxCpUserServiceImpl implements WxCpUserService {
private WxCpService mainService;

public WxCpUserServiceImpl(WxCpService mainService) {
this.mainService = mainService;
}

@Override
public void authenticate(String userId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/authsucc?userid=" + userId;
this.mainService.get(url, null);
}

@Override
public void create(WxCpUser user) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/create";
this.mainService.post(url, user.toJson());
}

@Override
public void update(WxCpUser user) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/update";
this.mainService.post(url, user.toJson());
}

public void deleteOne(String userId) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/delete?userid=" + userId;
this.mainService.get(url, null);
}

@Override
public void delete(String... userIds) throws WxErrorException {
if (userIds.length == 1) {
this.deleteOne(userIds[0]);
}

String url = "https://qyapi.weixin.qq.com/cgi-bin/user/batchdelete";
JsonObject jsonObject = new JsonObject();
JsonArray jsonArray = new JsonArray();
for (String userid : userIds) {
jsonArray.add(new JsonPrimitive(userid));
}
jsonObject.add("useridlist", jsonArray);
this.mainService.post(url, jsonObject.toString());
}

@Override
public WxCpUser getById(String userid) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/get?userid=" + userid;
String responseContent = this.mainService.get(url, null);
return WxCpUser.fromJson(responseContent);
}

@Override
public List<WxCpUser> listByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/list?department_id=" + departId;
String params = "";
if (fetchChild != null) {
params += "&fetch_child=" + (fetchChild ? "1" : "0");
}
if (status != null) {
params += "&status=" + status;
} else {
params += "&status=0";
}

String responseContent = this.mainService.get(url, params);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.INSTANCE.create()
.fromJson(tmpJsonElement.getAsJsonObject().get("userlist"),
new TypeToken<List<WxCpUser>>() {
}.getType()
);
}

@Override
public List<WxCpUser> listSimpleByDepartment(Integer departId, Boolean fetchChild, Integer status) throws WxErrorException {
String url = "https://qyapi.weixin.qq.com/cgi-bin/user/simplelist?department_id=" + departId;
String params = "";
if (fetchChild != null) {
params += "&fetch_child=" + (fetchChild ? "1" : "0");
}
if (status != null) {
params += "&status=" + status;
} else {
params += "&status=0";
}

String responseContent = this.mainService.get(url, params);
JsonElement tmpJsonElement = new JsonParser().parse(responseContent);
return WxCpGsonBuilder.INSTANCE.create()
.fromJson(
tmpJsonElement.getAsJsonObject().get("userlist"),
new TypeToken<List<WxCpUser>>() {
}.getType()
);
}

}

+ 7
- 7
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java View File

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

import java.io.Serializable;

import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.io.Serializable;

/**
* 微信部门
*
@@ -60,10 +60,10 @@ public class WxCpDepart implements Serializable {
@Override
public String toString() {
return "WxCpDepart{" +
"id=" + this.id +
", name='" + this.name + '\'' +
", parentId=" + this.parentId +
", order=" + this.order +
'}';
"id=" + this.id +
", name='" + this.name + '\'' +
", parentId=" + this.parentId +
", order=" + this.order +
'}';
}
}

+ 8
- 8
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java View File

@@ -33,14 +33,6 @@ public class WxCpMessage implements Serializable {
private List<NewArticle> articles = new ArrayList<>();
private List<MpnewsArticle> mpnewsArticles = new ArrayList<>();

public List<MpnewsArticle> getMpnewsArticles() {
return mpnewsArticles;
}

public void setMpnewsArticles(List<MpnewsArticle> mpnewsArticles) {
this.mpnewsArticles = mpnewsArticles;
}

/**
* 获得文本消息builder
*/
@@ -90,6 +82,14 @@ public class WxCpMessage implements Serializable {
return new FileBuilder();
}

public List<MpnewsArticle> getMpnewsArticles() {
return mpnewsArticles;
}

public void setMpnewsArticles(List<MpnewsArticle> mpnewsArticles) {
this.mpnewsArticles = mpnewsArticles;
}

public String getToUser() {
return this.toUser;
}


+ 103
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessageSendResult.java View File

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

import com.google.common.base.Splitter;
import com.google.gson.annotations.SerializedName;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;

import java.util.Collections;
import java.util.List;

/**
* <pre>
* 消息发送结果对象类
* Created by Binary Wang on 2017-6-22.
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* </pre>
*/
public class WxCpMessageSendResult {
@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
}

public static WxCpMessageSendResult fromJson(String json) {
return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpMessageSendResult.class);
}

@SerializedName("errcode")
private Integer errCode;

@SerializedName("errmsg")
private String errMsg;

@SerializedName("invaliduser")
private String invalidUser;

@SerializedName("invalidparty")
private String invalidParty;

@SerializedName("invalidtag")
private String invalidTag;

public Integer getErrCode() {
return this.errCode;
}

public void setErrCode(Integer errCode) {
this.errCode = errCode;
}

public String getErrMsg() {
return this.errMsg;
}

public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}

public String getInvalidUser() {
return this.invalidUser;
}

public void setInvalidUser(String invalidUser) {
this.invalidUser = invalidUser;
}

public String getInvalidParty() {
return this.invalidParty;
}

public void setInvalidParty(String invalidParty) {
this.invalidParty = invalidParty;
}

public String getInvalidTag() {
return this.invalidTag;
}

public void setInvalidTag(String invalidTag) {
this.invalidTag = invalidTag;
}

public List<String> getInvalidUserList() {
return this.content2List(this.invalidUser);
}

private List<String> content2List(String content) {
if(StringUtils.isBlank(content)){
return Collections.emptyList();
}

return Splitter.on("|").splitToList(content);
}

public List<String> getInvalidPartyList() {
return this.content2List(this.invalidParty);
}

public List<String> getInvalidTagList() {
return this.content2List(this.invalidTag);
}
}

+ 2
- 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTag.java View File

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

import java.io.Serializable;

import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.io.Serializable;

/**
* Created by Daniel Qian
*/


+ 85
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTagAddOrRemoveUsersResult.java View File

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

import com.google.common.base.Splitter;
import com.google.gson.annotations.SerializedName;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
import org.apache.commons.lang3.StringUtils;

import java.util.Collections;
import java.util.List;

/**
* <pre>
* 为标签添加或移除用户结果对象类
* Created by Binary Wang on 2017-6-22.
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* </pre>
*/
public class WxCpTagAddOrRemoveUsersResult {
@Override
public String toString() {
return ToStringUtils.toSimpleString(this);
}

public static WxCpTagAddOrRemoveUsersResult fromJson(String json) {
return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpTagAddOrRemoveUsersResult.class);
}

@SerializedName("errcode")
private Integer errCode;

@SerializedName("errmsg")
private String errMsg;

@SerializedName("invalidlist")
private String invalidUsers;

@SerializedName("invalidparty")
private String[] invalidParty;

public Integer getErrCode() {
return this.errCode;
}

public void setErrCode(Integer errCode) {
this.errCode = errCode;
}

public String getErrMsg() {
return this.errMsg;
}

public void setErrMsg(String errMsg) {
this.errMsg = errMsg;
}

public String getInvalidUser() {
return this.invalidUsers;
}

public void setInvalidUser(String invalidUser) {
this.invalidUsers = invalidUser;
}

public String[] getInvalidParty() {
return this.invalidParty;
}

public void setInvalidParty(String[] invalidParty) {
this.invalidParty = invalidParty;
}

public List<String> getInvalidUserList() {
return this.content2List(this.invalidUsers);
}

private List<String> content2List(String content) {
if(StringUtils.isBlank(content)){
return Collections.emptyList();
}

return Splitter.on("|").splitToList(content);
}

}

+ 70
- 21
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java View File

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

import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;

/**
* 微信用户信息
*
@@ -13,20 +13,54 @@ import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
*/
public class WxCpUser implements Serializable {

public enum Gender {
MALE("男", "1"),
FEMAIL("女", "2");

private String genderName;
private String code;

Gender(String genderName, String code) {
this.genderName = genderName;
this.code = code;
}

public String getGenderName() {
return this.genderName;
}

public String getCode() {
return this.code;
}

public static Gender fromCode(String code) {
if ("1".equals(code)) {
return Gender.MALE;
}
if ("2".equals(code)) {
return Gender.FEMAIL;
}

return null;
}
}

private static final long serialVersionUID = -5696099236344075582L;
private final List<Attr> extAttrs = new ArrayList<>();
private String userId;
private String name;
private Integer[] departIds;
private String position;
private String mobile;
private String gender;
private String tel;
private Gender gender;
private String email;
private String weiXinId;
private String avatar;
private Integer status;
private Integer enable;
private Integer isLeader;
private final List<Attr> extAttrs = new ArrayList<>();
private Integer hideMobile;
private String englishName;
private String telephone;

public static WxCpUser fromJson(String json) {
return WxCpGsonBuilder.INSTANCE.create().fromJson(json, WxCpUser.class);
@@ -56,11 +90,11 @@ public class WxCpUser implements Serializable {
this.departIds = departIds;
}

public String getGender() {
public Gender getGender() {
return this.gender;
}

public void setGender(String gender) {
public void setGender(Gender gender) {
this.gender = gender;
}

@@ -80,12 +114,12 @@ public class WxCpUser implements Serializable {
this.mobile = mobile;
}

public String getTel() {
return this.tel;
public String getTelephone() {
return this.telephone;
}

public void setTel(String tel) {
this.tel = tel;
public void setTelephone(String telephone) {
this.telephone = telephone;
}

public String getEmail() {
@@ -96,14 +130,6 @@ public class WxCpUser implements Serializable {
this.email = email;
}

public String getWeiXinId() {
return this.weiXinId;
}

public void setWeiXinId(String weiXinId) {
this.weiXinId = weiXinId;
}

public String getAvatar() {
return this.avatar;
}
@@ -136,12 +162,35 @@ public class WxCpUser implements Serializable {
return this.extAttrs;
}

public Integer getIsLeader() {
return isLeader;
}

public void setIsLeader(Integer isLeader) {
this.isLeader = isLeader;
}

public Integer getHideMobile() {
return hideMobile;
}

public void setHideMobile(Integer hideMobile) {
this.hideMobile = hideMobile;
}

public String getEnglishName() {
return englishName;
}

public void setEnglishName(String englishName) {
this.englishName = englishName;
}

public String toJson() {
return WxCpGsonBuilder.INSTANCE.create().toJson(this);
}

public static class Attr {

private String name;
private String value;



+ 48
- 50
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java View File

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

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.IOUtils;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;

import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
import me.chanjar.weixin.cp.api.WxCpConfigStorage;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
import me.chanjar.weixin.cp.util.xml.XStreamTransformer;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
* <pre>
@@ -183,18 +181,18 @@ public class WxCpXmlMessage implements Serializable {
* @param msgSignature
*/
public static WxCpXmlMessage fromEncryptedXml(
String encryptedXml,
WxCpConfigStorage wxCpConfigStorage,
String timestamp, String nonce, String msgSignature) {
String encryptedXml,
WxCpConfigStorage wxCpConfigStorage,
String timestamp, String nonce, String msgSignature) {
WxCpCryptUtil cryptUtil = new WxCpCryptUtil(wxCpConfigStorage);
String plainText = cryptUtil.decrypt(msgSignature, timestamp, nonce, encryptedXml);
return fromXml(plainText);
}

public static WxCpXmlMessage fromEncryptedXml(
InputStream is,
WxCpConfigStorage wxCpConfigStorage,
String timestamp, String nonce, String msgSignature) {
InputStream is,
WxCpConfigStorage wxCpConfigStorage,
String timestamp, String nonce, String msgSignature) {
try {
return fromEncryptedXml(IOUtils.toString(is, "UTF-8"), wxCpConfigStorage, timestamp, nonce, msgSignature);
} catch (IOException e) {
@@ -493,40 +491,40 @@ public class WxCpXmlMessage implements Serializable {
@Override
public String toString() {
return "WxCpXmlMessage{" +
"agentId=" + this.agentId +
", toUserName='" + this.toUserName + '\'' +
", fromUserName='" + this.fromUserName + '\'' +
", createTime=" + this.createTime +
", msgType='" + this.msgType + '\'' +
", content='" + this.content + '\'' +
", msgId=" + this.msgId +
", picUrl='" + this.picUrl + '\'' +
", mediaId='" + this.mediaId + '\'' +
", format='" + this.format + '\'' +
", thumbMediaId='" + this.thumbMediaId + '\'' +
", locationX=" + this.locationX +
", locationY=" + this.locationY +
", scale=" + this.scale +
", label='" + this.label + '\'' +
", title='" + this.title + '\'' +
", description='" + this.description + '\'' +
", url='" + this.url + '\'' +
", event='" + this.event + '\'' +
", eventKey='" + this.eventKey + '\'' +
", ticket='" + this.ticket + '\'' +
", latitude=" + this.latitude +
", longitude=" + this.longitude +
", precision=" + this.precision +
", recognition='" + this.recognition + '\'' +
", status='" + this.status + '\'' +
", totalCount=" + this.totalCount +
", filterCount=" + this.filterCount +
", sentCount=" + this.sentCount +
", errorCount=" + this.errorCount +
", scanCodeInfo=" + this.scanCodeInfo +
", sendPicsInfo=" + this.sendPicsInfo +
", sendLocationInfo=" + this.sendLocationInfo +
'}';
"agentId=" + this.agentId +
", toUserName='" + this.toUserName + '\'' +
", fromUserName='" + this.fromUserName + '\'' +
", createTime=" + this.createTime +
", msgType='" + this.msgType + '\'' +
", content='" + this.content + '\'' +
", msgId=" + this.msgId +
", picUrl='" + this.picUrl + '\'' +
", mediaId='" + this.mediaId + '\'' +
", format='" + this.format + '\'' +
", thumbMediaId='" + this.thumbMediaId + '\'' +
", locationX=" + this.locationX +
", locationY=" + this.locationY +
", scale=" + this.scale +
", label='" + this.label + '\'' +
", title='" + this.title + '\'' +
", description='" + this.description + '\'' +
", url='" + this.url + '\'' +
", event='" + this.event + '\'' +
", eventKey='" + this.eventKey + '\'' +
", ticket='" + this.ticket + '\'' +
", latitude=" + this.latitude +
", longitude=" + this.longitude +
", precision=" + this.precision +
", recognition='" + this.recognition + '\'' +
", status='" + this.status + '\'' +
", totalCount=" + this.totalCount +
", filterCount=" + this.filterCount +
", sentCount=" + this.sentCount +
", errorCount=" + this.errorCount +
", scanCodeInfo=" + this.scanCodeInfo +
", sendPicsInfo=" + this.sendPicsInfo +
", sendLocationInfo=" + this.sendLocationInfo +
'}';
}

@XStreamAlias("ScanCodeInfo")


+ 0
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java View File

@@ -2,7 +2,6 @@ package me.chanjar.weixin.cp.bean;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;

import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter;



+ 1
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java View File

@@ -3,7 +3,7 @@ package me.chanjar.weixin.cp.bean;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
import me.chanjar.weixin.cp.api.WxCpConfigStorage;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import me.chanjar.weixin.cp.bean.outxmlbuilder.*;
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil;
import me.chanjar.weixin.cp.util.xml.XStreamTransformer;


+ 3
- 4
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessage.java View File

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

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

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;

import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;

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

@XStreamAlias("xml")
public class WxCpXmlOutNewsMessage extends WxCpXmlOutMessage {
private static final long serialVersionUID = -5796178637883178826L;


+ 0
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java View File

@@ -2,7 +2,6 @@ package me.chanjar.weixin.cp.bean;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;

import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;



+ 0
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java View File

@@ -2,7 +2,6 @@ package me.chanjar.weixin.cp.bean;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;

import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;



+ 0
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java View File

@@ -2,7 +2,6 @@ package me.chanjar.weixin.cp.bean;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamConverter;

import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.util.xml.XStreamMediaIdConverter;



weixin-java-common/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java View File

@@ -4,6 +4,7 @@ package me.chanjar.weixin.cp.bean.article;
* <pre>
* Created by BinaryWang on 2017/3/27.
* </pre>
*
* @author Binary Wang
*/
public class MpnewsArticle {

weixin-java-common/src/main/java/me/chanjar/weixin/cp/bean/article/NewArticle.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/NewArticle.java View File


+ 1
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java View File

@@ -44,7 +44,7 @@ public class BaseBuilder<T> {
m.setToParty(this.toParty);
m.setToTag(this.toTag);
m.setSafe(
(this.safe == null || "".equals(this.safe)) ? WxConsts.CUSTOM_MSG_SAFE_NO : this.safe);
(this.safe == null || "".equals(this.safe)) ? WxConsts.CUSTOM_MSG_SAFE_NO : this.safe);
return m;
}



weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpConfigStorage.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java View File

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

import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;

import java.io.File;


weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpInMemoryConfigStorage.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpInMemoryConfigStorage.java View File

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

import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.ToStringUtils;
import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;

import java.io.File;


weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpJedisConfigStorage.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpJedisConfigStorage.java View File

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

import me.chanjar.weixin.common.bean.WxAccessToken;
import me.chanjar.weixin.common.util.http.ApacheHttpClientBuilder;
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.io.File;

@@ -38,6 +39,15 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage {
this.jedisPool = new JedisPool(host, port);
}


public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port) {
this.jedisPool = new JedisPool(poolConfig, host, port);
}

public WxCpJedisConfigStorage(JedisPoolConfig poolConfig, String host, int port, int timeout, final String password) {
this.jedisPool = new JedisPool(poolConfig, host, port, timeout, password);
}

/**
* This method will be destroy jedis pool
*/

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageHandler.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageHandler.java View File

@@ -1,7 +1,8 @@
package me.chanjar.weixin.cp.api;
package me.chanjar.weixin.cp.message;

import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;


weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageInterceptor.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageInterceptor.java View File

@@ -1,7 +1,8 @@
package me.chanjar.weixin.cp.api;
package me.chanjar.weixin.cp.message;

import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;

import java.util.Map;

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageMatcher.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageMatcher.java View File

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

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


weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageRouter.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java View File

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

import me.chanjar.weixin.common.api.WxErrorExceptionHandler;
import me.chanjar.weixin.common.api.WxMessageDuplicateChecker;
@@ -8,13 +8,16 @@ import me.chanjar.weixin.common.session.InternalSessionManager;
import me.chanjar.weixin.common.session.StandardSessionManager;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.common.util.LogExceptionHandler;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -135,8 +138,9 @@ public class WxCpMessageRouter {
* 处理微信消息
*
* @param wxMessage
* @param context
*/
public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) {
public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage, final Map<String, Object> context) {
if (isDuplicateMessage(wxMessage)) {
// 如果是重复消息,那么就不做处理
return null;
@@ -166,12 +170,12 @@ public class WxCpMessageRouter {
this.executorService.submit(new Runnable() {
@Override
public void run() {
rule.service(wxMessage, WxCpMessageRouter.this.wxCpService, WxCpMessageRouter.this.sessionManager, WxCpMessageRouter.this.exceptionHandler);
rule.service(wxMessage, context, WxCpMessageRouter.this.wxCpService, WxCpMessageRouter.this.sessionManager, WxCpMessageRouter.this.exceptionHandler);
}
})
);
} else {
res = rule.service(wxMessage, this.wxCpService, this.sessionManager, this.exceptionHandler);
res = rule.service(wxMessage, context, this.wxCpService, this.sessionManager, this.exceptionHandler);
// 在同步操作结束,session访问结束
this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUserName());
sessionEndAccess(wxMessage);
@@ -200,6 +204,16 @@ public class WxCpMessageRouter {
return res;
}


/**
* 处理微信消息
*
* @param wxMessage
*/
public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) {
return this.route(wxMessage, new HashMap<String, Object>());
}

protected boolean isDuplicateMessage(WxCpXmlMessage wxMessage) {

String messageId = "";

weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageRouterRule.java → weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouterRule.java View File

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

import me.chanjar.weixin.common.api.WxErrorExceptionHandler;
import me.chanjar.weixin.common.exception.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;

@@ -229,13 +230,16 @@ public class WxCpMessageRouterRule {
* @return true 代表继续执行别的router,false 代表停止执行别的router
*/
protected WxCpXmlOutMessage service(WxCpXmlMessage wxMessage,
Map<String, Object> context,
WxCpService wxCpService,
WxSessionManager sessionManager,
WxErrorExceptionHandler exceptionHandler) {

try {
if (context == null) {
context = new HashMap<>();
}

Map<String, Object> context = new HashMap<>();
try {
// 如果拦截器不通过
for (WxCpMessageInterceptor interceptor : this.interceptors) {
if (!interceptor.intercept(wxMessage, context, wxCpService, sessionManager)) {

+ 51
- 47
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpCryptUtil.java View File

@@ -1,47 +1,51 @@
/**
* 对公众平台发送给公众账号的消息加解密示例代码.
*
* @copyright Copyright (c) 1998-2014 Tencent Inc.
* <p>
* 针对org.apache.commons.codec.binary.Base64,
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
*/
// ------------------------------------------------------------------------
/**
* 针对org.apache.commons.codec.binary.Base64,
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
*/
package me.chanjar.weixin.cp.util.crypto;
import me.chanjar.weixin.common.util.crypto.WxCryptUtil;
import me.chanjar.weixin.cp.api.WxCpConfigStorage;
import org.apache.commons.codec.binary.Base64;
public class WxCpCryptUtil extends WxCryptUtil {
/**
* 构造函数
*
* @param wxCpConfigStorage
*/
public WxCpCryptUtil(WxCpConfigStorage wxCpConfigStorage) {
/*
* @param token 公众平台上,开发者设置的token
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
* @param appidOrCorpid 公众平台appid
*/
String encodingAesKey = wxCpConfigStorage.getAesKey();
String token = wxCpConfigStorage.getToken();
String corpId = wxCpConfigStorage.getCorpId();
this.token = token;
this.appidOrCorpid = corpId;
this.aesKey = Base64.decodeBase64(encodingAesKey + "=");
}
}
/**
* 对公众平台发送给公众账号的消息加解密示例代码.
*
* @copyright Copyright (c) 1998-2014 Tencent Inc.
* <p>
* 针对org.apache.commons.codec.binary.Base64,
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
* <p>
* 针对org.apache.commons.codec.binary.Base64,
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
*/

// ------------------------------------------------------------------------

/**
* 针对org.apache.commons.codec.binary.Base64,
* 需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
* 官方下载地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi
*/
package me.chanjar.weixin.cp.util.crypto;

import me.chanjar.weixin.common.util.crypto.WxCryptUtil;
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
import org.apache.commons.codec.binary.Base64;

public class WxCpCryptUtil extends WxCryptUtil {

/**
* 构造函数
*
* @param wxCpConfigStorage
*/
public WxCpCryptUtil(WxCpConfigStorage wxCpConfigStorage) {
/*
* @param token 公众平台上,开发者设置的token
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
* @param appidOrCorpid 公众平台appid
*/
String encodingAesKey = wxCpConfigStorage.getAesKey();
String token = wxCpConfigStorage.getToken();
String corpId = wxCpConfigStorage.getCorpId();

this.token = token;
this.appidOrCorpid = corpId;
this.aesKey = Base64.decodeBase64(encodingAesKey + "=");
}


}

+ 1
- 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpDepartGsonAdapter.java View File

@@ -39,7 +39,7 @@ public class WxCpDepartGsonAdapter implements JsonSerializer<WxCpDepart>, JsonDe

@Override
public WxCpDepart deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
throws JsonParseException {
throws JsonParseException {
WxCpDepart depart = new WxCpDepart();
JsonObject departJson = json.getAsJsonObject();
if (departJson.get("id") != null && !departJson.get("id").isJsonNull()) {


+ 2
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpGsonBuilder.java View File

@@ -2,6 +2,7 @@ package me.chanjar.weixin.cp.util.json;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.bean.result.WxError;
import me.chanjar.weixin.common.util.json.WxErrorAdapter;
import me.chanjar.weixin.cp.bean.WxCpDepart;
@@ -19,6 +20,7 @@ public class WxCpGsonBuilder {
INSTANCE.registerTypeAdapter(WxCpDepart.class, new WxCpDepartGsonAdapter());
INSTANCE.registerTypeAdapter(WxCpUser.class, new WxCpUserGsonAdapter());
INSTANCE.registerTypeAdapter(WxError.class, new WxErrorAdapter());
INSTANCE.registerTypeAdapter(WxMenu.class, new WxCpMenuGsonAdapter());
INSTANCE.registerTypeAdapter(WxCpTag.class, new WxCpTagGsonAdapter());
}



+ 24
- 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpMenuGsonAdapter.java View File

@@ -0,0 +1,24 @@
package me.chanjar.weixin.cp.util.json;

import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import me.chanjar.weixin.common.bean.menu.WxMenu;
import me.chanjar.weixin.common.util.json.WxMenuGsonAdapter;

import java.lang.reflect.Type;

/**
* <pre>
* 企业号菜单json转换适配器
* Created by Binary Wang on 2017-6-25.
* @author <a href="https://github.com/binarywang">Binary Wang</a>
* </pre>
*/
public class WxCpMenuGsonAdapter extends WxMenuGsonAdapter {

@Override
public WxMenu deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
return this.buildMenuFromJson(json.getAsJsonObject().get("button").getAsJsonArray());
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save