Browse Source

Merge pull request #1419 from Wechat-Group/develop

合并Develop分支代码,发布3.7.0正式版
Binary Wang 5 years ago
parent
commit
7bd4726e9c
100 changed files with 3474 additions and 142 deletions
  1. 17 2
      pom.xml
  2. 1 1
      spring-boot-starters/pom.xml
  3. 1 1
      spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml
  4. 6 1
      spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml
  5. 4 0
      spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java
  6. 6 1
      spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml
  7. 40 8
      spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenStorageAutoConfiguration.java
  8. 11 1
      spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java
  9. 1 1
      spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml
  10. 40 0
      weixin-graal/pom.xml
  11. 167 0
      weixin-graal/src/main/java/cn/binarywang/wx/graal/GraalProcessor.java
  12. 1 0
      weixin-graal/src/main/resources/META-INF/services/javax.annotation.processing.Processor
  13. 36 4
      weixin-java-common/pom.xml
  14. 13 0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java
  15. 2 3
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java
  16. 1 0
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java
  17. 1 1
      weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java
  18. 33 1
      weixin-java-cp/pom.xml
  19. 37 7
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java
  20. 80 9
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java
  21. 4 2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java
  22. 3 1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java
  23. 7 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java
  24. 74 12
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java
  25. 10 4
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java
  26. 21 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalApplyData.java
  27. 23 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalApplyer.java
  28. 48 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalComment.java
  29. 2 1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpApprovalDataResult.java
  30. 78 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetail.java
  31. 27 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetailResult.java
  32. 29 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalInfo.java
  33. 61 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalInfoQueryFilter.java
  34. 26 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalRecord.java
  35. 48 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalRecordDetail.java
  36. 28 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApproverAttr.java
  37. 5 1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinData.java
  38. 1 1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinOption.java
  39. 1 1
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDialRecord.java
  40. 24 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOperator.java
  41. 41 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpRecordSpStatus.java
  42. 54 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpSpStatus.java
  43. 35 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpTemplateResult.java
  44. 24 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/Content.java
  45. 18 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentTitle.java
  46. 103 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java
  47. 37 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateConfig.java
  48. 17 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateContent.java
  49. 19 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateControls.java
  50. 19 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateDateRange.java
  51. 16 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateOptions.java
  52. 41 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateProperty.java
  53. 18 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTitle.java
  54. 18 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateVacationItem.java
  55. 25 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateAttendance.java
  56. 22 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateContact.java
  57. 18 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateDate.java
  58. 20 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateSelector.java
  59. 23 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateTable.java
  60. 17 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateVacation.java
  61. 6 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java
  62. 6 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
  63. 5 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
  64. 5 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java
  65. 5 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
  66. 11 3
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
  67. 14 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java
  68. 8 0
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
  69. 9 0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java
  70. 50 7
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImplTest.java
  71. 2 0
      weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java
  72. 38 7
      weixin-java-miniapp/pom.xml
  73. 317 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCloudService.java
  74. 203 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressService.java
  75. 48 7
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaMsgService.java
  76. 8 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaSecCheckService.java
  77. 22 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
  78. 153 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaSubscribeService.java
  79. 4 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaTemplateService.java
  80. 178 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCloudServiceImpl.java
  81. 98 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImpl.java
  82. 1 1
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaJsapiServiceImpl.java
  83. 13 9
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java
  84. 19 3
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSecCheckServiceImpl.java
  85. 24 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java
  86. 73 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java
  87. 6 1
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMediaAsyncCheckResult.java
  88. 34 7
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
  89. 0 30
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeData.java
  90. 25 3
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeMessage.java
  91. 76 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUpdatableMsg.java
  92. 44 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudBatchDeleteFileResult.java
  93. 50 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudBatchDownloadFileResult.java
  94. 47 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudCloudDatabaseMigrateQueryInfoResult.java
  95. 86 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudDatabaseCollectionGetResult.java
  96. 59 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudDatabaseCreateIndexRequest.java
  97. 51 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudDatabaseQueryResult.java
  98. 32 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudDatabaseUpdateResult.java
  99. 41 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudGetQcloudTokenResult.java
  100. 0 0
      weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudUploadFileResult.java

+ 17 - 2
pom.xml

@@ -6,7 +6,7 @@
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.github.binarywang</groupId>
   <artifactId>wx-java</artifactId>
-  <version>3.6.0</version>
+  <version>3.7.0</version>
   <packaging>pom</packaging>
   <name>WxJava - Weixin/Wechat Java SDK</name>
   <description>微信开发Java SDK</description>
@@ -99,6 +99,7 @@
   </scm>
 
   <modules>
+    <module>weixin-graal</module>
     <module>weixin-java-common</module>
     <module>weixin-java-cp</module>
     <module>weixin-java-mp</module>
@@ -172,7 +173,7 @@
       <dependency>
         <groupId>com.thoughtworks.xstream</groupId>
         <artifactId>xstream</artifactId>
-        <version>1.4.11</version>
+        <version>1.4.11.1</version>
       </dependency>
       <!-- 由于guava较新的21.0版本需要jdk8,故而此处采用较低版本 -->
       <dependency>
@@ -243,6 +244,12 @@
         <scope>provided</scope>
       </dependency>
       <dependency>
+        <groupId>org.redisson</groupId>
+        <artifactId>redisson</artifactId>
+        <version>3.12.0</version>
+        <scope>provided</scope>
+      </dependency>
+      <dependency>
         <groupId>org.projectlombok</groupId>
         <artifactId>lombok</artifactId>
         <version>1.18.8</version>
@@ -325,6 +332,14 @@
         </plugins>
       </build>
     </profile>
+
+    <profile>
+      <id>native-image</id>
+      <activation>
+        <activeByDefault>false</activeByDefault>
+      </activation>
+    </profile>
+
   </profiles>
 
   <build>

+ 1 - 1
spring-boot-starters/pom.xml

@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.github.binarywang</groupId>
     <artifactId>wx-java</artifactId>
-    <version>3.6.0</version>
+    <version>3.7.0</version>
   </parent>
   <packaging>pom</packaging>
   <artifactId>wx-java-spring-boot-starters</artifactId>

+ 1 - 1
spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml

@@ -5,7 +5,7 @@
   <parent>
     <artifactId>wx-java-spring-boot-starters</artifactId>
     <groupId>com.github.binarywang</groupId>
-    <version>3.6.0</version>
+    <version>3.7.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 

+ 6 - 1
spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml

@@ -5,7 +5,7 @@
   <parent>
     <artifactId>wx-java-spring-boot-starters</artifactId>
     <groupId>com.github.binarywang</groupId>
-    <version>3.6.0</version>
+    <version>3.7.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
@@ -24,6 +24,11 @@
       <artifactId>jedis</artifactId>
       <scope>compile</scope>
     </dependency>
+    <dependency>
+      <groupId>org.redisson</groupId>
+      <artifactId>redisson</artifactId>
+      <scope>compile</scope>
+    </dependency>
   </dependencies>
 
   <build>

+ 4 - 0
spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java

@@ -6,6 +6,7 @@ import lombok.RequiredArgsConstructor;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
 import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
+import org.redisson.api.RedissonClient;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
@@ -26,6 +27,9 @@ public class WxMpStorageAutoConfiguration {
   @Autowired(required = false)
   private JedisPool jedisPool;
 
+  @Autowired(required = false)
+  private RedissonClient redissonClient;
+
   @Bean
   @ConditionalOnMissingBean(WxMpConfigStorage.class)
   public WxMpConfigStorage wxMpInMemoryConfigStorage() {

+ 6 - 1
spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml

@@ -5,7 +5,7 @@
   <parent>
     <artifactId>wx-java-spring-boot-starters</artifactId>
     <groupId>com.github.binarywang</groupId>
-    <version>3.6.0</version>
+    <version>3.7.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 
@@ -24,6 +24,11 @@
       <artifactId>jedis</artifactId>
       <scope>compile</scope>
     </dependency>
+    <dependency>
+      <groupId>org.redisson</groupId>
+      <artifactId>redisson</artifactId>
+      <scope>compile</scope>
+    </dependency>
   </dependencies>
 
   <build>

+ 40 - 8
spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenStorageAutoConfiguration.java

@@ -6,7 +6,12 @@ import lombok.RequiredArgsConstructor;
 import me.chanjar.weixin.open.api.WxOpenConfigStorage;
 import me.chanjar.weixin.open.api.impl.WxOpenInMemoryConfigStorage;
 import me.chanjar.weixin.open.api.impl.WxOpenInRedisConfigStorage;
+import me.chanjar.weixin.open.api.impl.WxOpenInRedissonConfigStorage;
 import org.apache.commons.lang3.StringUtils;
+import org.redisson.Redisson;
+import org.redisson.api.RedissonClient;
+import org.redisson.config.Config;
+import org.redisson.config.TransportMode;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -28,6 +33,9 @@ public class WxOpenStorageAutoConfiguration {
   @Autowired(required = false)
   private JedisPool jedisPool;
 
+  @Autowired(required = false)
+  private RedissonClient redissonClient;
+
   @Value("${wx.open.config-storage.redis.host:}")
   private String redisHost;
 
@@ -40,12 +48,20 @@ public class WxOpenStorageAutoConfiguration {
     if (type == WxOpenProperties.StorageType.redis) {
       return getWxOpenInRedisConfigStorage();
     }
+
+    if (type == WxOpenProperties.StorageType.jedis) {
+      return getWxOpenInRedisConfigStorage();
+    }
+
+    if (type == WxOpenProperties.StorageType.redisson) {
+      return getWxOpenInRedissonConfigStorage();
+    }
     return getWxOpenInMemoryConfigStorage();
   }
 
   private WxOpenInMemoryConfigStorage getWxOpenInMemoryConfigStorage() {
     WxOpenInMemoryConfigStorage config = new WxOpenInMemoryConfigStorage();
-    setWxOpenInfo(config);
+    config.setWxOpenInfo(properties.getAppId(), properties.getSecret(), properties.getToken(), properties.getAesKey());
     return config;
   }
 
@@ -54,18 +70,22 @@ public class WxOpenStorageAutoConfiguration {
     if (jedisPool == null || StringUtils.isNotEmpty(redisHost)) {
       poolToUse = getJedisPool();
     }
-    WxOpenInRedisConfigStorage config = new WxOpenInRedisConfigStorage(poolToUse);
-    setWxOpenInfo(config);
+    WxOpenInRedisConfigStorage config = new WxOpenInRedisConfigStorage(poolToUse, properties.getConfigStorage().getKeyPrefix());
+    config.setWxOpenInfo(properties.getAppId(), properties.getSecret(), properties.getToken(), properties.getAesKey());
     return config;
   }
 
-  private void setWxOpenInfo(WxOpenConfigStorage config) {
-    config.setComponentAppId(properties.getAppId());
-    config.setComponentAppSecret(properties.getSecret());
-    config.setComponentToken(properties.getToken());
-    config.setComponentAesKey(properties.getAesKey());
+  private WxOpenInRedissonConfigStorage getWxOpenInRedissonConfigStorage() {
+    RedissonClient redissonClientToUse = this.redissonClient;
+    if (redissonClient == null) {
+      redissonClientToUse = getRedissonClient();
+    }
+    WxOpenInRedissonConfigStorage config = new WxOpenInRedissonConfigStorage(redissonClientToUse, properties.getConfigStorage().getKeyPrefix());
+    config.setWxOpenInfo(properties.getAppId(), properties.getSecret(), properties.getToken(), properties.getAesKey());
+    return config;
   }
 
+
   private JedisPool getJedisPool() {
     WxOpenProperties.ConfigStorage storage = properties.getConfigStorage();
     RedisProperties redis = storage.getRedis();
@@ -90,4 +110,16 @@ public class WxOpenStorageAutoConfiguration {
       redis.getTimeout(), redis.getPassword(), redis.getDatabase());
     return pool;
   }
+
+  private RedissonClient getRedissonClient() {
+    WxOpenProperties.ConfigStorage storage = properties.getConfigStorage();
+    RedisProperties redis = storage.getRedis();
+
+    Config config = new Config();
+    config.useSingleServer()
+      .setAddress("redis://" + redis.getHost() + ":" + redis.getPort())
+      .setPassword(redis.getPassword());
+    config.setTransportMode(TransportMode.NIO);
+    return Redisson.create(config);
+  }
 }

+ 11 - 1
spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java

@@ -53,6 +53,8 @@ public class WxOpenProperties {
 
     private RedisProperties redis = new RedisProperties();
 
+    private String keyPrefix;
+
   }
 
   public enum StorageType {
@@ -63,6 +65,14 @@ public class WxOpenProperties {
     /**
      * redis.
      */
-    redis
+    redis,
+    /**
+     * jedis.
+     */
+    jedis,
+    /**
+     * redisson.
+     */
+    redisson
   }
 }

+ 1 - 1
spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml

@@ -5,7 +5,7 @@
   <parent>
     <artifactId>wx-java-spring-boot-starters</artifactId>
     <groupId>com.github.binarywang</groupId>
-    <version>3.6.0</version>
+    <version>3.7.0</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>
 

+ 40 - 0
weixin-graal/pom.xml

@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+         xmlns="http://maven.apache.org/POM/4.0.0">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>com.github.binarywang</groupId>
+    <artifactId>wx-java</artifactId>
+    <version>3.7.0</version>
+  </parent>
+
+  <artifactId>weixin-graal</artifactId>
+  <name>WxJava - Graal</name>
+  <description>微信开发Java内部配合graal以产生native-image配置的辅助工具, 可以通过项目的 native-image Profile 来启用: mvn -P native-image ...
+  </description>
+
+  <dependencies>
+
+    <dependency>
+      <groupId>org.projectlombok</groupId>
+      <artifactId>lombok</artifactId>
+      <scope>compile</scope>
+    </dependency>
+
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>3.5.1</version>
+        <configuration>
+          <proc>none</proc>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+</project>

+ 167 - 0
weixin-graal/src/main/java/cn/binarywang/wx/graal/GraalProcessor.java

@@ -0,0 +1,167 @@
+package cn.binarywang.wx.graal;
+
+import lombok.Data;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import javax.lang.model.util.ElementFilter;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+// 目前仅仅处理@Data,且必须在lombok自己的processor之前执行,千万注意!!!!!
+@SupportedAnnotationTypes("lombok.Data")
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+public class GraalProcessor extends AbstractProcessor {
+
+  private static final String REFLECTION_CONFIG_JSON = "reflection-config.json";
+  private static final String NATIVE_IMAGE_PROPERTIES = "native-image.properties";
+
+  private SortedSet<String> classSet = new TreeSet<>();
+  private String shortestPackageName = null;
+
+  @Override
+  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
+    for (TypeElement annotatedClass : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Data.class))) {
+
+      registerClass(annotatedClass.getQualifiedName().toString());
+      handleSuperClass(annotatedClass);
+    }
+
+    //只有最后一轮才可以写文件,否则文件会被重复打开,报错!
+    if (!roundEnv.processingOver()) return false;
+
+    // 如果没有文件要写,跳过
+    if (classSet.isEmpty()) return false;
+
+    writeFiles();
+
+    //必须返回false,以便让lombok能继续处理。
+    return false;
+  }
+
+  /**
+   * 设置当前最短的package名称
+   *
+   * @param packageName 包名
+   */
+  private void setShortestPackageName(String packageName) {
+    if (shortestPackageName == null) {
+      shortestPackageName = packageName;
+    } else if (packageName.length() < shortestPackageName.length()) {
+      shortestPackageName = packageName;
+    }
+  }
+
+  /**
+   * 更加完整的类名来获取package名称
+   *
+   * @param fullClassName 完整的类名
+   * @return package name
+   */
+  private String getPackageName(String fullClassName) {
+    int last = fullClassName.lastIndexOf('.');
+    if (last == -1) return fullClassName;
+    return fullClassName.substring(0, last);
+  }
+
+  /**
+   * 保存文件
+   * META-INF/native-image/.../reflection-config.json
+   * META-INF/native-image/.../native-image.properties
+   */
+  private void writeFiles() {
+    String basePackage = shortestPackageName;
+
+    String module;
+    if (basePackage.contains(".")) {
+      final int i = basePackage.lastIndexOf('.');
+      module = basePackage.substring(i + 1);
+      basePackage = basePackage.substring(0, i);
+    } else {
+      module = basePackage;
+    }
+
+    String path = "META-INF/native-image/" + basePackage + "/" + module + "/";
+    String reflectFile = path + REFLECTION_CONFIG_JSON;
+    String propsFile = path + NATIVE_IMAGE_PROPERTIES;
+    try {
+      FileObject fileObject = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", propsFile);
+      Writer writer = fileObject.openWriter();
+      writer.append("Args = -H:ReflectionConfigurationResources=${.}/" + REFLECTION_CONFIG_JSON);
+      writer.close();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+    try {
+      FileObject fileObject = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", reflectFile);
+      Writer writer = fileObject.openWriter();
+      writer.write("[\n");
+      boolean first = true;
+      for (String name : classSet) {
+        if (first) {
+          first = false;
+        } else {
+          writer.write(",");
+        }
+        writer.write(assetGraalJsonElement(name));
+        writer.append('\n');
+      }
+      writer.write("]");
+      writer.close();
+    } catch (IOException e) {
+      e.printStackTrace();
+    }
+
+  }
+
+  private String assetGraalJsonElement(String className) {
+    return "{\n" +
+      "  \"name\" : \"" + className + "\",\n" +
+      "  \"allDeclaredFields\":true,\n" +
+      "  \"allDeclaredMethods\":true,\n" +
+      "  \"allDeclaredConstructors\":true,\n" +
+      "  \"allPublicMethods\" : true\n" +
+      "}";
+  }
+
+  /**
+   * 登记一个class
+   *
+   * @param className 完整的类名
+   */
+  private void registerClass(String className) {
+    classSet.add(className);
+    setShortestPackageName(getPackageName(className));
+  }
+
+  /**
+   * 获取一个类型的所有的父类,并登记
+   *
+   * @param typeElement 类型元素
+   */
+  private void handleSuperClass(TypeElement typeElement) {
+    TypeMirror superclass = typeElement.getSuperclass();
+    if (superclass.getKind() == TypeKind.DECLARED) {
+      TypeElement s = (TypeElement) ((DeclaredType) superclass).asElement();
+      String sName = s.toString();
+      // ignore java.**/javax.**
+      if (sName.startsWith("java.") || sName.startsWith("javax.")) return;
+      registerClass(sName);
+      handleSuperClass(s);
+    }
+  }
+
+}

+ 1 - 0
weixin-graal/src/main/resources/META-INF/services/javax.annotation.processing.Processor

@@ -0,0 +1 @@
+cn.binarywang.wx.graal.GraalProcessor

+ 36 - 4
weixin-java-common/pom.xml

@@ -1,12 +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">
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+         xmlns="http://maven.apache.org/POM/4.0.0">
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <groupId>com.github.binarywang</groupId>
     <artifactId>wx-java</artifactId>
-    <version>3.6.0</version>
+    <version>3.7.0</version>
   </parent>
 
   <artifactId>weixin-java-common</artifactId>
@@ -134,4 +134,36 @@
     </plugins>
   </build>
 
+  <profiles>
+    <profile>
+      <id>native-image</id>
+      <activation>
+        <activeByDefault>false</activeByDefault>
+      </activation>
+
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <version>3.5.1</version>
+            <configuration>
+              <annotationProcessors>
+                cn.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor
+              </annotationProcessors>
+              <annotationProcessorPaths>
+                <path>
+                  <groupId>com.github.binarywang</groupId>
+                  <artifactId>weixin-graal</artifactId>
+                  <version>${project.version}</version>
+                </path>
+              </annotationProcessorPaths>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+
+    </profile>
+  </profiles>
+
 </project>

+ 13 - 0
weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java

@@ -452,6 +452,19 @@ public enum WxMaErrorMsgEnum {
   CODE_43101(43101, "用户拒绝接受消息,如果用户之前曾经订阅过,则表示用户取消了订阅关系"),
 
   CODE_47003(47003, "模板参数不准确,可能为空或者不满足规则,errmsg会提示具体是哪个字段出错"),
+
+  /**
+   * 小程序绑定体验者
+   */
+  CODE_85001(85001, "微信号不存在或微信号设置为不可搜索"),
+
+  CODE_85002(85002, "小程序绑定的体验者数量达到上限"),
+
+  CODE_85003(85003, "微信号绑定的小程序体验者达到上限"),
+
+  CODE_85004(85004, "微信号已经绑定"),
+
+//  CODE_504002(-504002, "云函数未找到 Function not found"),
   ;
 
   private int code;

+ 2 - 3
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java

@@ -1,15 +1,14 @@
 package me.chanjar.weixin.common.util.http;
 
-import java.io.IOException;
-
 import me.chanjar.weixin.common.WxType;
 import me.chanjar.weixin.common.error.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;
 
+import java.io.IOException;
+
 /**
- * 用装饰模式实现
  * 简单的POST请求执行器,请求的参数是String, 返回的结果也是String
  *
  * @author Daniel Qian

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

@@ -36,6 +36,7 @@ public class ApacheSimplePostRequestExecutor extends SimplePostRequestExecutor<C
 
     if (postEntity != null) {
       StringEntity entity = new StringEntity(postEntity, Consts.UTF_8);
+      entity.setContentType("application/json; charset=utf-8");
       httpPost.setEntity(entity);
     }
 

+ 1 - 1
weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/Utf8ResponseHandler.java

@@ -25,7 +25,7 @@ public class Utf8ResponseHandler implements ResponseHandler<String> {
     final HttpEntity entity = response.getEntity();
     if (statusLine.getStatusCode() >= 300) {
       EntityUtils.consume(entity);
-      throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase());
+      throw new HttpResponseException(statusLine.getStatusCode(), statusLine.toString());
     }
     return entity == null ? null : EntityUtils.toString(entity, Consts.UTF_8);
   }

+ 33 - 1
weixin-java-cp/pom.xml

@@ -7,7 +7,7 @@
   <parent>
     <groupId>com.github.binarywang</groupId>
     <artifactId>wx-java</artifactId>
-    <version>3.6.0</version>
+    <version>3.7.0</version>
   </parent>
 
   <artifactId>weixin-java-cp</artifactId>
@@ -95,4 +95,36 @@
     </plugins>
   </build>
 
+  <profiles>
+    <profile>
+      <id>native-image</id>
+      <activation>
+        <activeByDefault>false</activeByDefault>
+      </activation>
+
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <version>3.5.1</version>
+            <configuration>
+              <annotationProcessors>
+                cn.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor
+              </annotationProcessors>
+              <annotationProcessorPaths>
+                <path>
+                  <groupId>com.github.binarywang</groupId>
+                  <artifactId>weixin-graal</artifactId>
+                  <version>${project.version}</version>
+                </path>
+              </annotationProcessorPaths>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+
+    </profile>
+  </profiles>
+
 </project>

+ 37 - 7
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java

@@ -24,25 +24,54 @@ public interface WxCpExternalContactService {
    * </pre>
    *
    * @param userId 外部联系人的userid
+   * @return .
+   * @deprecated 建议使用 {@link #getContactDetail(String)}
    */
+  @Deprecated
   WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException;
 
   /**
-   * 获取外部联系人列表.
+   * 获取客户详情.
    * <pre>
-   *   企业可通过此接口获取指定成员添加的客户列表。
-   *   客户是指配置了客户联系功能的成员所添加的外部联系人。
-   *   没有配置客户联系功能的成员,所添加的外部联系人将不会作为客户返回。
+   *
+   * 企业可通过此接口,根据外部联系人的userid(如何获取?),拉取客户详情。
+   *
+   * 请求方式:GET(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/get?access_token=ACCESS_TOKEN&external_userid=EXTERNAL_USERID
+   *
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?);
+   * 第三方/自建应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
+   * </pre>
+   *
+   * @param userId 外部联系人的userid,注意不是企业成员的帐号
+   * @return .
+   * @throws WxErrorException .
+   */
+  WxCpUserExternalContactInfo getContactDetail(String userId) throws WxErrorException;
+
+  /**
+   * 获取客户列表.
+   * <pre>
+   *   企业可通过此接口获取指定成员添加的客户列表。客户是指配置了客户联系功能的成员所添加的外部联系人。没有配置客户联系功能的成员,所添加的外部联系人将不会作为客户返回。
+   *
+   * 请求方式:GET(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/list?access_token=ACCESS_TOKEN&userid=USERID
+   *
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?);
    * 第三方应用需拥有“企业客户”权限。
-   * 第三方应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
+   * 第三方/自建应用只能获取到可见范围内的配置了客户联系功能的成员。
    * </pre>
    *
-   * @param userId 外部联系人的userid
+   * @param userId 企业成员的userid
    * @return List of External wx id
+   * @throws WxErrorException .
    */
   List<String> listExternalContacts(String userId) throws WxErrorException;
 
-
   /**
    * 企业和第三方服务商可通过此接口获取配置了客户联系功能的成员(Customer Contact)列表。
    * <pre>
@@ -52,6 +81,7 @@ public interface WxCpExternalContactService {
    * </pre>
    *
    * @return List of CpUser id
+   * @throws WxErrorException .
    */
   List<String> listFollowUser() throws WxErrorException;
 

+ 80 - 9
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaService.java

@@ -1,10 +1,8 @@
 package me.chanjar.weixin.cp.api;
 
+import lombok.NonNull;
 import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.cp.bean.WxCpApprovalDataResult;
-import me.chanjar.weixin.cp.bean.WxCpCheckinData;
-import me.chanjar.weixin.cp.bean.WxCpCheckinOption;
-import me.chanjar.weixin.cp.bean.WxCpDialRecord;
+import me.chanjar.weixin.cp.bean.oa.*;
 
 import java.util.Date;
 import java.util.List;
@@ -30,7 +28,8 @@ public interface WxCpOaService {
    * @return 打卡数据列表
    * @throws WxErrorException 异常
    */
-  List<WxCpCheckinData> getCheckinData(Integer openCheckinDataType, Date startTime, Date endTime, List<String> userIdList) throws WxErrorException;
+  List<WxCpCheckinData> getCheckinData(Integer openCheckinDataType, Date startTime, Date endTime,
+                                       List<String> userIdList) throws WxErrorException;
 
   /**
    * <pre>
@@ -41,13 +40,63 @@ public interface WxCpOaService {
    * @param datetime   需要获取规则的当天日期
    * @param userIdList 需要获取打卡规则的用户列表
    * @return 打卡规则列表
-   * @throws WxErrorException 异常
+   * @throws WxErrorException
    */
   List<WxCpCheckinOption> getCheckinOption(Date datetime, List<String> userIdList) throws WxErrorException;
 
   /**
    * <pre>
-   *   获取审批数据
+   *
+   * 批量获取审批单号
+   *
+   * 审批应用及有权限的自建应用,可通过Secret调用本接口,以获取企业一段时间内企业微信“审批应用”单据的审批编号,支持按模板类型、申请人、部门、申请单审批状态等条件筛选。
+   * 自建应用调用此接口,需在“管理后台-应用管理-审批-API-审批数据权限”中,授权应用允许提交审批单据。
+   *
+   * 一次拉取调用最多拉取100个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。
+   *
+   * API doc : https://work.weixin.qq.com/api/doc/90000/90135/91816
+   * </pre>
+   *
+   * @param startTime 开始时间
+   * @param endTime   结束时间
+   * @param cursor    分页查询游标,默认为0,后续使用返回的next_cursor进行分页拉取
+   * @param size      一次请求拉取审批单数量,默认值为100,上限值为100
+   * @param filters   筛选条件,可对批量拉取的审批申请设置约束条件,支持设置多个条件,nullable
+   * @return WxCpApprovalInfo
+   * @throws WxErrorException
+   */
+  WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime, Integer cursor, Integer size,
+                                   List<WxCpApprovalInfoQueryFilter> filters) throws WxErrorException;
+
+  /**
+   * short method
+   *
+   * @param startTime 开始时间
+   * @param endTime   结束时间
+   * @return WxCpApprovalInfo
+   * @throws WxErrorException
+   * @see me.chanjar.weixin.cp.api.WxCpOaService#getApprovalInfo
+   */
+  WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime) throws WxErrorException;
+
+  /**
+   * <pre>
+   *   获取审批申请详情
+   *
+   *   企业可通过审批应用或自建应用Secret调用本接口,根据审批单号查询企业微信“审批应用”的审批申请详情。
+   *
+   *   API Doc : https://work.weixin.qq.com/api/doc/90000/90135/91983
+   * </pre>
+   *
+   * @param spNo 审批单编号。
+   * @return WxCpApprovaldetail
+   * @throws WxErrorException
+   */
+  WxCpApprovalDetailResult getApprovalDetail(@NonNull String spNo) throws WxErrorException;
+
+  /**
+   * <pre>
+   *   获取审批数据 (已过期, 请使用"批量获取审批单号" && "获取审批申请详情")
    *   通过本接口来获取公司一段时间内的审批记录。一次拉取调用最多拉取10000个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。
    *   API doc : https://work.weixin.qq.com/api/doc#90000/90135/91530
    * </pre>
@@ -55,10 +104,32 @@ public interface WxCpOaService {
    * @param startTime 获取审批记录的开始时间
    * @param endTime   获取审批记录的结束时间
    * @param nextSpnum 第一个拉取的审批单号,不填从该时间段的第一个审批单拉取
-   * @throws WxErrorException 异常
+   * @throws WxErrorException
+   * @see me.chanjar.weixin.cp.api.WxCpOaService#getApprovalInfo
+   * @see me.chanjar.weixin.cp.api.WxCpOaService#getApprovalDetail
    */
+  @Deprecated
   WxCpApprovalDataResult getApprovalData(Date startTime, Date endTime, Long nextSpnum) throws WxErrorException;
 
-  List<WxCpDialRecord> getDialRecord(Date startTime, Date endTime, Integer offset, Integer limit) throws WxErrorException;
+  /**
+   * 获取公费电话拨打记录
+   *
+   * @param startTime 查询的起始时间戳
+   * @param endTime   查询的结束时间戳
+   * @param offset    分页查询的偏移量
+   * @param limit     分页查询的每页大小,默认为100条,如该参数大于100则按100处理
+   * @return
+   * @throws WxErrorException
+   */
+  List<WxCpDialRecord> getDialRecord(Date startTime, Date endTime, Integer offset,
+                                     Integer limit) throws WxErrorException;
+
+  /**
+   * 获取审批模板详情
+   * @param templateId 模板ID
+   * @return
+   * @throws WxErrorException
+   */
+  WxCpTemplateResult getTemplateDetail(@NonNull String templateId)throws WxErrorException;
 
 }

+ 4 - 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java

@@ -205,7 +205,7 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
     JsonObject jsonObject = new JsonObject();
     jsonObject.addProperty("corpid", corpId);
     jsonObject.addProperty("provider_secret", providerSecret);
-    return WxCpProviderToken.fromJson(this.post(this.configStorage.getApiUrl(GET_PROVIDER_TOKEN), jsonObject.toString()));
+    return WxCpProviderToken.fromJson(this.post(this.configStorage.getApiUrl(Tp.GET_PROVIDER_TOKEN), jsonObject.toString()));
   }
 
   @Override
@@ -281,7 +281,9 @@ public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestH
       if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001 || error.getErrorCode() == 40014) {
         // 强制设置wxCpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token
         this.configStorage.expireAccessToken();
-        return execute(executor, uri, data);
+        if (this.getWxCpConfigStorage().autoRefreshToken()) {
+          return this.execute(executor, uri, data);
+        }
       }
 
       if (error.getErrorCode() != 0) {

+ 3 - 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java

@@ -194,7 +194,9 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
       if (error.getErrorCode() == 42009) {
         // 强制设置wxCpTpConfigStorage它的suite access token过期了,这样在下一次请求里就会刷新suite access token
         this.configStorage.expireSuiteAccessToken();
-        return execute(executor, uri, data);
+        if (this.getWxCpTpConfigStorage().autoRefreshToken()) {
+          return this.execute(executor, uri, data);
+        }
       }
 
       if (error.getErrorCode() != 0) {

+ 7 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java

@@ -26,6 +26,13 @@ public class WxCpExternalContactServiceImpl implements WxCpExternalContactServic
   }
 
   @Override
+  public WxCpUserExternalContactInfo getContactDetail(String userId) throws WxErrorException {
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CONTACT_DETAIL + userId);
+    String responseContent = this.mainService.get(url, null);
+    return WxCpUserExternalContactInfo.fromJson(responseContent);
+  }
+
+  @Override
   public List<String> listExternalContacts(String userId) throws WxErrorException {
     final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_EXTERNAL_CONTACT + userId);
     String responseContent = this.mainService.get(url, null);

+ 74 - 12
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java

@@ -5,15 +5,12 @@ import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import com.google.gson.reflect.TypeToken;
+import lombok.NonNull;
 import lombok.RequiredArgsConstructor;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.cp.api.WxCpOaService;
 import me.chanjar.weixin.cp.api.WxCpService;
-import me.chanjar.weixin.cp.bean.WxCpApprovalDataResult;
-import me.chanjar.weixin.cp.bean.WxCpCheckinData;
-import me.chanjar.weixin.cp.bean.WxCpCheckinOption;
-import me.chanjar.weixin.cp.bean.WxCpDialRecord;
-import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
+import me.chanjar.weixin.cp.bean.oa.*;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
 import java.util.Date;
@@ -22,7 +19,7 @@ import java.util.List;
 import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*;
 
 /**
- * .
+ * 企业微信 OA 接口实现
  *
  * @author Element
  * @date 2019-04-06 11:20
@@ -31,6 +28,9 @@ import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*;
 public class WxCpOaServiceImpl implements WxCpOaService {
   private final WxCpService mainService;
 
+  private static final int MONTH_SECONDS = 30 * 24 * 60 * 60;
+  private static final int USER_IDS_LIMIT = 100;
+
   @Override
   public List<WxCpCheckinData> getCheckinData(Integer openCheckinDataType, Date startTime, Date endTime,
                                               List<String> userIdList) throws WxErrorException {
@@ -38,14 +38,14 @@ public class WxCpOaServiceImpl implements WxCpOaService {
       throw new RuntimeException("starttime and endtime can't be null");
     }
 
-    if (userIdList == null || userIdList.size() > 100) {
-      throw new RuntimeException("用户列表不能为空,不超过100个,若用户超过100个,请分批获取");
+    if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) {
+      throw new RuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
     }
 
     long endtimestamp = endTime.getTime() / 1000L;
     long starttimestamp = startTime.getTime() / 1000L;
 
-    if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= 30 * 24 * 60 * 60) {
+    if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= MONTH_SECONDS) {
       throw new RuntimeException("获取记录时间跨度不超过一个月");
     }
 
@@ -79,8 +79,8 @@ public class WxCpOaServiceImpl implements WxCpOaService {
       throw new RuntimeException("datetime can't be null");
     }
 
-    if (userIdList == null || userIdList.size() > 100) {
-      throw new RuntimeException("用户列表不能为空,不超过100个,若用户超过100个,请分批获取");
+    if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) {
+      throw new RuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取");
     }
 
     JsonArray jsonArray = new JsonArray();
@@ -105,6 +105,59 @@ public class WxCpOaServiceImpl implements WxCpOaService {
   }
 
   @Override
+  public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime,
+                                          Integer cursor, Integer size, List<WxCpApprovalInfoQueryFilter> filters) throws WxErrorException {
+
+    if (cursor == null) {
+      cursor = 0;
+    }
+
+    if (size == null) {
+      size = 100;
+    }
+
+    if (size < 0 || size > 100) {
+      throw new IllegalArgumentException("size参数错误,请使用[1-100]填充,默认100");
+    }
+
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("starttime", startTime.getTime() / 1000L);
+    jsonObject.addProperty("endtime", endTime.getTime() / 1000L);
+    jsonObject.addProperty("size", size);
+    jsonObject.addProperty("cursor", cursor);
+
+    if (filters != null && !filters.isEmpty()) {
+      JsonArray filterJsonArray = new JsonArray();
+      for (WxCpApprovalInfoQueryFilter filter : filters) {
+        filterJsonArray.add(new JsonParser().parse(filter.toJson()));
+      }
+      jsonObject.add("filters", filterJsonArray);
+    }
+
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_APPROVAL_INFO);
+    String responseContent = this.mainService.post(url, jsonObject.toString());
+
+    return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalInfo.class);
+  }
+
+  @Override
+  public WxCpApprovalInfo getApprovalInfo(@NonNull Date startTime, @NonNull Date endTime) throws WxErrorException {
+    return this.getApprovalInfo(startTime, endTime, null, null, null);
+  }
+
+  @Override
+  public WxCpApprovalDetailResult getApprovalDetail(@NonNull String spNo) throws WxErrorException {
+
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("sp_no", spNo);
+
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_APPROVAL_DETAIL);
+    String responseContent = this.mainService.post(url, jsonObject.toString());
+
+    return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalDetailResult.class);
+  }
+
+  @Override
   public WxCpApprovalDataResult getApprovalData(Date startTime, Date endTime, Long nextSpnum) throws WxErrorException {
     JsonObject jsonObject = new JsonObject();
     jsonObject.addProperty("starttime", startTime.getTime() / 1000L);
@@ -139,7 +192,7 @@ public class WxCpOaServiceImpl implements WxCpOaService {
       long endtimestamp = endTime.getTime() / 1000L;
       long starttimestamp = startTime.getTime() / 1000L;
 
-      if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= 30 * 24 * 60 * 60) {
+      if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= MONTH_SECONDS) {
         throw new RuntimeException("受限于网络传输,起止时间的最大跨度为30天,如超过30天,则以结束时间为基准向前取30天进行查询");
       }
 
@@ -156,4 +209,13 @@ public class WxCpOaServiceImpl implements WxCpOaService {
       }.getType()
     );
   }
+
+  @Override
+  public WxCpTemplateResult getTemplateDetail(@NonNull String templateId) throws WxErrorException {
+    JsonObject jsonObject = new JsonObject();
+    jsonObject.addProperty("template_id",templateId);
+    final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_TEMPLATE_DETAIL);
+    String responseContent = this.mainService.post(url, jsonObject.toString());
+    return WxCpGsonBuilder.create().fromJson(responseContent,WxCpTemplateResult.class);
+  }
 }

+ 10 - 4
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java

@@ -1,15 +1,15 @@
 package me.chanjar.weixin.cp.bean;
 
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.List;
-
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * 微信用户信息.
  *
@@ -27,6 +27,8 @@ public class WxCpUser implements Serializable {
   private Gender gender;
   private String email;
   private String avatar;
+  private String thumbAvatar;
+
   /**
    * 地址。长度最大128个字符
    */
@@ -34,6 +36,10 @@ public class WxCpUser implements Serializable {
   private String avatarMediaId;
   private Integer status;
   private Integer enable;
+  /**
+   * 别名;第三方仅通讯录应用可获取
+   */
+  private String alias;
   private Integer isLeader;
   /**
    * is_leader_in_dept.

+ 21 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalApplyData.java

@@ -0,0 +1,21 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import lombok.Data;
+import me.chanjar.weixin.cp.bean.oa.applydata.Content;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 审批申请数据
+ *
+ * @author element
+ */
+@Data
+public class WxCpApprovalApplyData implements Serializable {
+
+  private static final long serialVersionUID = 4061352949894274704L;
+
+  private List<Content> contents;
+
+}

+ 23 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalApplyer.java

@@ -0,0 +1,23 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 申请人信息
+ * @author element
+ */
+@Data
+public class WxCpApprovalApplyer extends WxCpOperator implements Serializable {
+
+  private static final long serialVersionUID = -8974662568286821271L;
+
+  /**
+   * 申请人所在部门id
+   */
+  @SerializedName("partyid")
+  private String partyId;
+
+}

+ 48 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalComment.java

@@ -0,0 +1,48 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 审批申请备注信息
+ *
+ * @author element
+ */
+@Data
+public class WxCpApprovalComment implements Serializable {
+
+  private static final long serialVersionUID = -5430367411926856292L;
+
+  /**
+   * 备注人信息
+   */
+  private WxCpOperator commentUserInfo;
+
+  /**
+   * 备注提交时间戳,Unix时间戳
+   */
+  @SerializedName("commenttime")
+  private Long commentTime;
+
+  /**
+   * 备注id
+   */
+  @SerializedName("commentid")
+  private String commentId;
+
+  /**
+   * 备注文本内容
+   */
+  @SerializedName("commentcontent")
+  private String commentContent;
+
+  /**
+   * 备注附件id,可能有多个
+   */
+  @SerializedName("media_id")
+  private List<String> mediaIds;
+
+}

+ 2 - 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpApprovalDataResult.java

@@ -1,4 +1,4 @@
-package me.chanjar.weixin.cp.bean;
+package me.chanjar.weixin.cp.bean.oa;
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
@@ -12,6 +12,7 @@ import java.util.Map;
  * @author Element
  * @date 2019-04-06 14:36
  */
+@Deprecated
 @Data
 public class WxCpApprovalDataResult implements Serializable {
   private static final long serialVersionUID = -1046940445840716590L;

+ 78 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetail.java

@@ -0,0 +1,78 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 审批申请详情
+ *
+ * @author element
+ */
+@Data
+public class WxCpApprovalDetail implements Serializable {
+
+  private static final long serialVersionUID = 1353393306564207170L;
+
+  /**
+   * 审批编号
+   */
+  @SerializedName("sp_no")
+  private String spNo;
+
+  /**
+   * 审批申请类型名称(审批模板名称)
+   */
+  @SerializedName("sp_name")
+  private String spName;
+
+  /**
+   * 申请单状态:1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付
+   */
+  @SerializedName("sp_status")
+  private WxCpSpStatus spStatus;
+
+  /**
+   * 审批模板id。可在“获取审批申请详情”、“审批状态变化回调通知”中获得,也可在审批模板的模板编辑页面链接中获得。
+   */
+  @SerializedName("template_id")
+  private String templateId;
+
+  /**
+   * 审批申请提交时间,Unix时间戳
+   */
+  @SerializedName("apply_time")
+  private Long applyTime;
+
+  /**
+   * 申请人信息
+   */
+  private WxCpApprovalApplyer applyer;
+
+  /**
+   * 审批流程信息,可能有多个审批节点
+   */
+  @SerializedName("sp_record")
+  private WxCpApprovalRecord spRecord;
+
+  /**
+   * 抄送信息,可能有多个抄送节点
+   */
+  @SerializedName("notifyer")
+  private WxCpOperator notifyer;
+
+  /**
+   * 审批申请数据
+   */
+  @SerializedName("apply_data")
+  private WxCpApprovalApplyData applyData;
+
+  /**
+   * 审批申请备注信息,可能有多个备注节点
+   */
+  @SerializedName("comments")
+  private List<WxCpApprovalComment> comments;
+
+}

+ 27 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetailResult.java

@@ -0,0 +1,27 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 审批申请详情响应结果
+ *
+ * @author element
+ */
+@Data
+public class WxCpApprovalDetailResult implements Serializable {
+
+  private static final long serialVersionUID = 3909779949756252918L;
+
+  @SerializedName("errcode")
+  private Integer errCode;
+
+  @SerializedName("errmsg")
+  private String errMsg;
+
+  @SerializedName("info")
+  private WxCpApprovalDetail info;
+
+}

+ 29 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalInfo.java

@@ -0,0 +1,29 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+/**
+ * @author element
+ */
+@Data
+public class WxCpApprovalInfo implements Serializable {
+
+  private static final long serialVersionUID = 7387181805254287167L;
+
+  @SerializedName("errcode")
+  private Integer errCode;
+
+  @SerializedName("errmsg")
+  private String errMsg;
+
+  @SerializedName("sp_no_list")
+  private List<String> spNoList;
+
+  @SerializedName("next_cursor")
+  private Integer nextCursor;
+
+}

+ 61 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalInfoQueryFilter.java

@@ -0,0 +1,61 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * <pre>
+ *  批量获取审批单号的筛选条件,可对批量拉取的审批申请设置约束条件,支持设置多个条件
+ *  注意:
+ *  仅“部门”支持同时配置多个筛选条件。
+ *  不同类型的筛选条件之间为“与”的关系,同类型筛选条件之间为“或”的关系
+ * </pre>
+ *
+ * @author element
+ */
+@Data
+public class WxCpApprovalInfoQueryFilter implements Serializable {
+
+  private static final long serialVersionUID = 3318064927980231802L;
+
+  private WxCpApprovalInfoQueryFilter.KEY key;
+
+  private Object value;
+
+  public String toJson() {
+    return WxGsonBuilder.create().toJson(this);
+  }
+
+  public static enum KEY {
+
+    /**
+     * template_id - 模板类型/模板id;
+     */
+    @SerializedName("template_id")
+    TEMPLATE_ID("template_id"),
+    /**
+     * creator - 申请人;
+     */
+    @SerializedName("creator")
+    CREATOR("creator"),
+    /**
+     * department - 审批单提单者所在部门;
+     */
+    @SerializedName("department")
+    DEPARTMENT("department"),
+    /**
+     * sp_status - 审批状态。
+     */
+    @SerializedName("sp_status")
+    SP_STATUS("sp_status");
+
+    private String value;
+
+    private KEY(String value) {
+      this.value = value;
+    }
+  }
+}

+ 26 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalRecord.java

@@ -0,0 +1,26 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 审批流程信息
+ * @author element
+ */
+@Data
+public class WxCpApprovalRecord implements Serializable {
+
+  private static final long serialVersionUID = -327230786004105887L;
+
+  @SerializedName("sp_status")
+  private WxCpRecordSpStatus status;
+
+  @SerializedName("approverattr")
+  private WxCpApproverAttr approverAttr;
+
+  private List<WxCpApprovalRecordDetail> details;
+
+}

+ 48 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalRecordDetail.java

@@ -0,0 +1,48 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 审批节点详情
+ * @author element
+ */
+@Data
+public class WxCpApprovalRecordDetail implements Serializable {
+
+  private static final long serialVersionUID = -9142079764088495301L;
+
+  /**
+   * 分支审批人
+   */
+  @SerializedName("approver")
+  private WxCpOperator approver;
+
+  /**
+   * 审批意见
+   */
+  @SerializedName("speech")
+  private String speech;
+
+  /**
+   * 分支审批人审批状态
+   */
+  @SerializedName("sp_status")
+  private WxCpRecordSpStatus spStatus;
+
+  /**
+   * 节点分支审批人审批操作时间戳,0表示未操作
+   */
+  @SerializedName("sptime")
+  private Long spTime;
+
+  /**
+   * 节点分支审批人审批意见附件
+   */
+  @SerializedName("media_id")
+  private List<String> mediaIds;
+
+}

+ 28 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApproverAttr.java

@@ -0,0 +1,28 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * 审批方式
+ *
+ * @author element
+ */
+public enum WxCpApproverAttr {
+  /**
+   * 或签
+   */
+  @SerializedName("1")
+  ONE_SIGN(1),
+  /**
+   * 会签
+   */
+  @SerializedName("2")
+  ALL_SIGN(2);
+
+  private Integer attr;
+
+  private WxCpApproverAttr(Integer attr) {
+    this.attr = attr;
+  }
+
+}

+ 5 - 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinData.java

@@ -1,4 +1,4 @@
-package me.chanjar.weixin.cp.bean;
+package me.chanjar.weixin.cp.bean.oa;
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
@@ -47,4 +47,8 @@ public class WxCpCheckinData implements Serializable {
 
   @SerializedName("mediaids")
   private List<String> mediaIds;
+
+  private Integer lat;
+
+  private Integer lng;
 }

+ 1 - 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpCheckinOption.java

@@ -1,4 +1,4 @@
-package me.chanjar.weixin.cp.bean;
+package me.chanjar.weixin.cp.bean.oa;
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;

+ 1 - 1
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDialRecord.java

@@ -1,4 +1,4 @@
-package me.chanjar.weixin.cp.bean;
+package me.chanjar.weixin.cp.bean.oa;
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;

+ 24 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpOperator.java

@@ -0,0 +1,24 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+
+/**
+ * 企业微信操作人
+ *
+ * @author element
+ */
+@Data
+public class WxCpOperator implements Serializable {
+
+  private static final long serialVersionUID = 5797144853574346736L;
+
+  /**
+   * 企业微信userid
+   */
+  @SerializedName("userid")
+  private String userId;
+}

+ 41 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpRecordSpStatus.java

@@ -0,0 +1,41 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * 审批记录(节点)分支审批状态
+ *
+ * 1-审批中;2-已同意;3-已驳回;4-已转审
+ *
+ * @author element
+ */
+public enum WxCpRecordSpStatus {
+
+  /**
+   * 审批中
+   */
+  @SerializedName("1")
+  AUDITING(1),
+  /**
+   * 已同意
+   */
+  @SerializedName("2")
+  PASSED(2),
+  /**
+   * 已驳回
+   */
+  @SerializedName("3")
+  REJECTED(3),
+  /**
+   * 已转审
+   */
+  @SerializedName("4")
+  TURNED(4);
+
+  private Integer status;
+
+  private WxCpRecordSpStatus(Integer status) {
+    this.status = status;
+  }
+
+}

+ 54 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpSpStatus.java

@@ -0,0 +1,54 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * 审批单状态
+ * (1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付)
+ *
+ * @author element
+ */
+public enum WxCpSpStatus {
+
+  /**
+   * 审批中
+   */
+  @SerializedName("1")
+  AUDITING(1),
+  /**
+   * 已通过
+   */
+  @SerializedName("2")
+  PASSED(2),
+  /**
+   * 已驳回
+   */
+  @SerializedName("3")
+  REJECTED(3),
+  /**
+   * 已撤销
+   */
+  @SerializedName("4")
+  UNDONE(4),
+  /**
+   * 通过后撤销
+   */
+  @SerializedName("6")
+  PASS_UNDONE(6),
+  /**
+   * 已删除
+   */
+  @SerializedName("7")
+  DELETED(7),
+  /**
+   * 已支付
+   */
+  @SerializedName("10")
+  ALREADY_PAY(10);
+
+  private Integer status;
+
+  private WxCpSpStatus(Integer status) {
+    this.status = status;
+  }
+}

+ 35 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpTemplateResult.java

@@ -0,0 +1,35 @@
+package me.chanjar.weixin.cp.bean.oa;
+
+import com.google.gson.JsonObject;
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateContent;
+import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateControls;
+import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateTitle;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 审批模板详情
+ *
+ * @author gyv12345@163.com
+ */
+@Data
+public class WxCpTemplateResult implements Serializable {
+  private static final long serialVersionUID = 6690547131189343887L;
+
+  @SerializedName("errcode")
+  private Integer errCode;
+
+  @SerializedName("errmsg")
+  private String errMsg;
+
+  @SerializedName("template_names")
+  private List<TemplateTitle> templateNames;
+
+  @SerializedName("template_content")
+  private TemplateContent templateContent;
+
+}

+ 24 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/Content.java

@@ -0,0 +1,24 @@
+package me.chanjar.weixin.cp.bean.oa.applydata;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author element
+ */
+@Data
+public class Content implements Serializable {
+  private static final long serialVersionUID = 8456821731930526935L;
+
+  private String control;
+
+  private String id;
+
+  @SerializedName("title")
+  private List<ContentTitle> titles;
+
+  private ContentValue value;
+}

+ 18 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentTitle.java

@@ -0,0 +1,18 @@
+package me.chanjar.weixin.cp.bean.oa.applydata;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author element
+ */
+@Data
+public class ContentTitle implements Serializable {
+
+  private static final long serialVersionUID = -4501999157383517007L;
+
+  private String text;
+  private String lang;
+
+}

+ 103 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java

@@ -0,0 +1,103 @@
+package me.chanjar.weixin.cp.bean.oa.applydata;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author element
+ */
+@Data
+public class ContentValue implements Serializable {
+
+  private static final long serialVersionUID = -5607678965965065261L;
+
+  private String text;
+
+  @SerializedName("new_number")
+  private Integer newNumber;
+
+  @SerializedName("new_money")
+  private Integer newMoney;
+
+  private ContentValue.Date date;
+
+  private ContentValue.Selector selector;
+
+  private List<ContentValue.Member> members;
+
+  private List<ContentValue.Department> departments;
+
+  private List<ContentValue.File> files;
+
+  private List<ContentValue.Child> children;
+
+  @Data
+  public static class Date implements Serializable {
+
+    private static final long serialVersionUID = -6181554080062231138L;
+    private String type;
+
+    @SerializedName("s_timestamp")
+    private Long timestamp;
+  }
+
+  @Data
+  public static class Selector implements Serializable {
+
+    private static final long serialVersionUID = 7305458759126951773L;
+    private String type;
+    private List<Option> options;
+
+    @Data
+    public static class Option implements Serializable {
+
+      private static final long serialVersionUID = -3471071106328280252L;
+      private String key;
+
+      @SerializedName("value")
+      private List<ContentTitle> values;
+    }
+
+  }
+
+  @Data
+  public static class Member implements Serializable {
+
+    private static final long serialVersionUID = 1316551341955496067L;
+    @SerializedName("userid")
+    private String userId;
+    private String name;
+  }
+
+  @Data
+  public static class Department implements Serializable {
+
+    private static final long serialVersionUID = -2513762192924826234L;
+
+    @SerializedName("openapi_id")
+
+    private String openApiId;
+    private String name;
+  }
+
+  @Data
+  public static class File implements Serializable {
+
+    private static final long serialVersionUID = 3890971381800855142L;
+    @SerializedName("file_id")
+    private String fileId;
+
+
+  }
+
+  @Data
+  public static class Child implements Serializable {
+
+    private static final long serialVersionUID = -3500102073821161558L;
+    private List<Content> list;
+  }
+
+}

+ 37 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateConfig.java

@@ -0,0 +1,37 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.bean.oa.templatedata.control.*;
+
+import java.io.Serializable;
+
+/**
+ * 模板控件配置,包含了部分控件类型的附加类型、属性,详见附录说明。
+ * 目前有配置信息的控件类型有:
+ * Date-日期/日期+时间;
+ * Selector-单选/多选;
+ * Contact-成员/部门;
+ * Table-明细;
+ * Attendance-假勤组件(请假、外出、出差、加班)
+ * @author gyv12345@163.com
+ */
+@Data
+public class TemplateConfig implements Serializable {
+
+  private static final long serialVersionUID = 6993937809371277669L;
+
+  private TemplateDate date;
+
+  private TemplateSelector selector;
+
+  private TemplateContact contact;
+
+  private TemplateTable table;
+
+  private TemplateAttendance attendance;
+
+  @SerializedName("vacation_list")
+  private TemplateVacation vacationList;
+
+}

+ 17 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateContent.java

@@ -0,0 +1,17 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author gyv12345@163.com
+ */
+@Data
+public class TemplateContent implements Serializable {
+
+  private static final long serialVersionUID = -5640250983775840865L;
+
+  private List<TemplateControls> controls;
+}

+ 19 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateControls.java

@@ -0,0 +1,19 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author Administrator
+ */
+@Data
+public class TemplateControls implements Serializable {
+
+  private static final long serialVersionUID = -7496794407355510374L;
+
+  private TemplateProperty property;
+
+  private TemplateConfig config;
+}

+ 19 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateDateRange.java

@@ -0,0 +1,19 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author gyv12345@163.com
+ */
+@Data
+public class TemplateDateRange implements Serializable {
+
+  private static final long serialVersionUID = -9209035461466543180L;
+
+  /**
+   * 时间刻度:hour-精确到分钟, halfday—上午/下午
+   */
+  private String type;
+}

+ 16 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateOptions.java

@@ -0,0 +1,16 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author gyv123@163.com
+ */
+public class TemplateOptions implements Serializable {
+
+  private static final long serialVersionUID = -7883792668568772078L;
+
+  private String key;
+
+  private List<TemplateTitle> value;
+}

+ 41 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateProperty.java

@@ -0,0 +1,41 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import com.google.gson.JsonObject;
+import com.google.gson.annotations.SerializedName;
+import me.chanjar.weixin.cp.bean.oa.WxCpTemplateResult;
+import me.chanjar.weixin.cp.bean.oa.templatedata.control.TemplateContact;
+
+import java.io.Serializable;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author gyv12345@163.com
+ */
+public class TemplateProperty implements Serializable {
+
+  private static final long serialVersionUID = -3429251158540167453L;
+
+  private String control;
+
+  private String id;
+
+  private List<TemplateTitle> title;
+
+  /**
+   * 控件说明,向申请者展示的控件填写说明,若配置了多语言则会包含中英文的控件说明,默认为zh_CN中文
+   */
+  private List<TemplateTitle> placeholder;
+
+  /**
+   * 是否必填:1-必填;0-非必填
+   */
+  private Integer require;
+  /**
+   * 是否参与打印:1-不参与打印;0-参与打印
+   */
+  @SerializedName("un_print")
+  private Integer unPrint;
+
+  private TemplateConfig config;
+}

+ 18 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateTitle.java

@@ -0,0 +1,18 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author gyv12345@163.com
+ */
+@Data
+public class TemplateTitle implements Serializable {
+
+  private static final long serialVersionUID = -3229779834737051398L;
+
+  private String text;
+
+  private String lang;
+}

+ 18 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/TemplateVacationItem.java

@@ -0,0 +1,18 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author gyv12345@163.com
+ */
+@Data
+public class TemplateVacationItem implements Serializable {
+
+  private static final long serialVersionUID = 4510594801023791319L;
+
+  private Integer id;
+
+  private TemplateTitle name;
+}

+ 25 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateAttendance.java

@@ -0,0 +1,25 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata.control;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateDateRange;
+
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author gyv12345@163.com
+ */
+@Data
+public class TemplateAttendance implements Serializable {
+
+  private static final long serialVersionUID = 5800412600894589065L;
+
+  @SerializedName("date_range")
+  private TemplateDateRange dateRange;
+
+  /**
+   * 假勤控件类型:1-请假,3-出差,4-外出,5-加班
+   */
+  private Integer type;
+}

+ 22 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateContact.java

@@ -0,0 +1,22 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata.control;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author gyv12345@163.com
+ */
+@Data
+public class TemplateContact implements Serializable {
+
+  private static final long serialVersionUID = -7840088884653172851L;
+  /**
+   * 选择方式:single-单选;multi-多选
+   */
+  private String type;
+  /**
+   * 选择对象:user-成员;department-部门
+   */
+  private String mode;
+}

+ 18 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateDate.java

@@ -0,0 +1,18 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata.control;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author Administrator
+ */
+@Data
+public class TemplateDate implements Serializable {
+
+  private static final long serialVersionUID = 1300634733160349684L;
+  /**
+   * day-日期;hour-日期+时间
+   */
+  private String type;
+}

+ 20 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateSelector.java

@@ -0,0 +1,20 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata.control;
+
+import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateOptions;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author
+ */
+public class TemplateSelector implements Serializable {
+
+  private static final long serialVersionUID = 4995408101489736881L;
+  /**
+   * single-单选;multi-多选
+   */
+  private String type;
+
+  private List<TemplateOptions> options;
+}

+ 23 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateTable.java

@@ -0,0 +1,23 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata.control;
+
+import lombok.Data;
+import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateControls;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ *
+ * @author gyv12345@163.com
+ */
+@Data
+public class TemplateTable implements Serializable {
+
+
+  private static final long serialVersionUID = -8181588935694605858L;
+
+  private List<TemplateControls> children;
+
+  private String[] statField;
+
+}

+ 17 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/templatedata/control/TemplateVacation.java

@@ -0,0 +1,17 @@
+package me.chanjar.weixin.cp.bean.oa.templatedata.control;
+
+import lombok.Data;
+import me.chanjar.weixin.cp.bean.oa.templatedata.TemplateVacationItem;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author gyv12345@163.com
+ */
+@Data
+public class TemplateVacation implements Serializable {
+
+  private List<TemplateVacationItem> item;
+
+}

+ 6 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpConfigStorage.java

@@ -97,4 +97,10 @@ public interface WxCpConfigStorage {
    * @return ApacheHttpClientBuilder
    */
   ApacheHttpClientBuilder getApacheHttpClientBuilder();
+
+  /**
+   * 是否自动刷新token
+   * @return .
+   */
+  boolean autoRefreshToken();
 }

+ 6 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java

@@ -83,4 +83,10 @@ public interface WxCpTpConfigStorage {
    * @return ApacheHttpClientBuilder
    */
   ApacheHttpClientBuilder getApacheHttpClientBuilder();
+
+  /**
+   * 是否自动刷新token
+   * @return .
+   */
+  boolean autoRefreshToken();
 }

+ 5 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java

@@ -262,6 +262,11 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
     return this.apacheHttpClientBuilder;
   }
 
+  @Override
+  public boolean autoRefreshToken() {
+    return true;
+  }
+
   public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) {
     this.apacheHttpClientBuilder = apacheHttpClientBuilder;
   }

+ 5 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedisConfigImpl.java

@@ -320,6 +320,11 @@ public class WxCpRedisConfigImpl implements WxCpConfigStorage {
     return this.apacheHttpClientBuilder;
   }
 
+  @Override
+  public boolean autoRefreshToken() {
+    return true;
+  }
+
   public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) {
     this.apacheHttpClientBuilder = apacheHttpClientBuilder;
   }

+ 5 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java

@@ -242,6 +242,11 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
     return this.apacheHttpClientBuilder;
   }
 
+  @Override
+  public boolean autoRefreshToken() {
+    return true;
+  }
+
   public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) {
     this.apacheHttpClientBuilder = apacheHttpClientBuilder;
   }

+ 11 - 3
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java

@@ -21,7 +21,6 @@ public final class WxCpApiPathConsts {
   public static final String BATCH_GET_RESULT = "/cgi-bin/batch/getresult?jobid=";
   public static final String JSCODE_TO_SESSION = "/cgi-bin/miniprogram/jscode2session";
   public static final String GET_TOKEN = "/cgi-bin/gettoken?corpid=%s&corpsecret=%s";
-  public static final String GET_PROVIDER_TOKEN = "/cgi-bin/service/get_provider_token";
 
   public static class Agent {
     public static final String AGENT_GET = "/cgi-bin/agent/get?agentid=%d";
@@ -65,8 +64,13 @@ public final class WxCpApiPathConsts {
   public static class Oa {
     public static final String GET_CHECKIN_DATA = "/cgi-bin/checkin/getcheckindata";
     public static final String GET_CHECKIN_OPTION = "/cgi-bin/checkin/getcheckinoption";
-    public static final String GET_APPROVAL_DATA = "/cgi-bin/corp/getapprovaldata";
+    public static final String GET_APPROVAL_INFO = "/cgi-bin/oa/getapprovalinfo";
+    public static final String GET_APPROVAL_DETAIL = "/cgi-bin/oa/getapprovaldetail";
     public static final String GET_DIAL_RECORD = "/cgi-bin/dial/get_dial_record";
+    @Deprecated
+    public static final String GET_APPROVAL_DATA = "/cgi-bin/corp/getapprovaldata";
+    public static final String GET_TEMPLATE_DETAIL = "/cgi-bin/oa/gettemplatedetail";
+    public static final String APPLY_EVENT="/cgi-bin/oa/applyevent";
   }
 
   public static class Tag {
@@ -88,6 +92,7 @@ public final class WxCpApiPathConsts {
     public static final String GET_CORP_TOKEN = "/cgi-bin/service/get_corp_token";
     public static final String GET_PERMANENT_CODE = "/cgi-bin/service/get_permanent_code";
     public static final String GET_SUITE_TOKEN = "/cgi-bin/service/get_suite_token";
+    public static final String GET_PROVIDER_TOKEN = "/cgi-bin/service/get_provider_token";
   }
 
   public static class User {
@@ -107,8 +112,11 @@ public final class WxCpApiPathConsts {
   }
 
   public static class ExternalContact {
+    @Deprecated
     public static final String GET_EXTERNAL_CONTACT = "/cgi-bin/crm/get_external_contact?external_userid=";
-    public static final String LIST_EXTERNAL_CONTACT = "/cgi-bin/externalcontact/list?userid=";
+
     public static final String GET_FOLLOW_USER_LIST = "/cgi-bin/externalcontact/get_follow_user_list";
+    public static final String GET_CONTACT_DETAIL = "/cgi-bin/externalcontact/get?external_userid=";
+    public static final String LIST_EXTERNAL_CONTACT = "/cgi-bin/externalcontact/list?userid=";
   }
 }

+ 14 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java

@@ -94,6 +94,11 @@ public class WxCpConsts {
      */
     public static final String CHANGE_EXTERNAL_CONTACT = "change_external_contact";
 
+    /**
+     * 企业微信审批事件推送
+     */
+    public static final String OPEN_APPROVAL_CHANGE = "open_approval_change";
+
 
   }
 
@@ -109,6 +114,15 @@ public class WxCpConsts {
      * 删除外部联系人
      */
     public static final String DEL_EXTERNAL_CONTACT = "del_external_contact";
+
+    /**
+     * 外部联系人免验证添加成员事件
+     */
+    public static final String ADD_HALF_EXTERNAL_CONTACT = "add_half_external_contact";
+    /**
+     * 删除跟进成员事件
+     */
+    public static final String DEL_FOLLOW_USER = "del_follow_user";
   }
 
   /**

+ 8 - 0
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java

@@ -66,10 +66,12 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
     user.setGender(Gender.fromCode(GsonHelper.getString(o, "gender")));
     user.setEmail(GsonHelper.getString(o, "email"));
     user.setAvatar(GsonHelper.getString(o, "avatar"));
+    user.setThumbAvatar(GsonHelper.getString(o, "thumb_avatar"));
     user.setAddress(GsonHelper.getString(o, "address"));
     user.setAvatarMediaId(GsonHelper.getString(o, "avatar_mediaid"));
     user.setStatus(GsonHelper.getInteger(o, "status"));
     user.setEnable(GsonHelper.getInteger(o, "enable"));
+    user.setAlias(GsonHelper.getString(o, "alias"));
     user.setIsLeader(GsonHelper.getInteger(o, "isleader"));
     user.setIsLeaderInDept(GsonHelper.getIntArray(o, "is_leader_in_dept"));
     user.setHideMobile(GsonHelper.getInteger(o, "hide_mobile"));
@@ -187,6 +189,9 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
     if (user.getAvatar() != null) {
       o.addProperty("avatar", user.getAvatar());
     }
+    if (user.getThumbAvatar() != null) {
+      o.addProperty("thumb_avatar", user.getThumbAvatar());
+    }
     if (user.getAddress() != null) {
       o.addProperty("address", user.getAddress());
     }
@@ -199,6 +204,9 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri
     if (user.getEnable() != null) {
       o.addProperty("enable", user.getEnable());
     }
+    if (user.getAlias() != null) {
+      o.addProperty("alias", user.getAlias());
+    }
     if (user.getIsLeader() != null) {
       o.addProperty("isleader", user.getIsLeader());
     }

+ 9 - 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java

@@ -42,4 +42,13 @@ public class WxCpExternalContactServiceImplTest {
     System.out.println(ret);
     assertNotNull(ret);
   }
+
+  @Test
+  public void testGetContactDetail() throws WxErrorException {
+    String externalUserId = this.configStorage.getExternalUserId();
+    WxCpUserExternalContactInfo result = this.wxCpService.getExternalContactService().getContactDetail(externalUserId);
+    System.out.println(result);
+    assertNotNull(result);
+  }
+
 }

+ 50 - 7
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImplTest.java

@@ -1,33 +1,37 @@
 package me.chanjar.weixin.cp.api.impl;
 
-import com.google.common.collect.Lists;
 import com.google.gson.Gson;
 import com.google.inject.Inject;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.cp.api.ApiTestModule;
 import me.chanjar.weixin.cp.api.WxCpService;
-import me.chanjar.weixin.cp.bean.WxCpCheckinData;
-import me.chanjar.weixin.cp.bean.WxCpCheckinOption;
+import me.chanjar.weixin.cp.bean.oa.*;
 import org.apache.commons.lang3.time.DateFormatUtils;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
+import org.testng.collections.Lists;
 
 import java.text.ParseException;
-import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
 /**
+ * 企业微信 OA数据接口 测试用例
+ *
  * @author Element
- * @date 2019-04-20 13:46
  */
+
 @Guice(modules = ApiTestModule.class)
 public class WxCpOaServiceImplTest {
+
   @Inject
   protected WxCpService wxService;
 
+  @Inject
+  protected Gson gson;
+
   @Test
   public void testGetCheckinData() throws ParseException, WxErrorException {
     Date startTime = DateFormatUtils.ISO_8601_EXTENDED_DATE_FORMAT.parse("2019-04-11");
@@ -37,13 +41,52 @@ public class WxCpOaServiceImplTest {
       .getCheckinData(1, startTime, endTime, Lists.newArrayList("binary"));
 
     assertThat(results).isNotNull();
+
+    System.out.println("results ");
+    System.out.println(gson.toJson(results));
+
   }
 
   @Test
   public void testGetCheckinOption() throws WxErrorException {
+
     Date now = new Date();
-    List<WxCpCheckinOption> results = wxService.getOAService()
-      .getCheckinOption(now, Lists.newArrayList("binary"));
+    List<WxCpCheckinOption> results = wxService.getOAService().getCheckinOption(now, Lists.newArrayList("binary"));
     assertThat(results).isNotNull();
+    System.out.println("results ");
+    System.out.println(gson.toJson(results));
   }
+
+  @Test
+  public void testGetApprovalInfo() throws WxErrorException, ParseException {
+    Date startTime = DateFormatUtils.ISO_8601_EXTENDED_DATE_FORMAT.parse("2019-12-01");
+    Date endTime = DateFormatUtils.ISO_8601_EXTENDED_DATE_FORMAT.parse("2019-12-31");
+    WxCpApprovalInfo result = wxService.getOAService().getApprovalInfo(startTime, endTime);
+
+    assertThat(result).isNotNull();
+
+    System.out.println("result ");
+    System.out.println(gson.toJson(result));
+  }
+
+  @Test
+  public void testGetApprovalDetail() throws WxErrorException {
+    String spNo = "201912020001";
+    WxCpApprovalDetailResult result = wxService.getOAService().getApprovalDetail(spNo);
+
+    assertThat(result).isNotNull();
+
+    System.out.println("result ");
+    System.out.println(gson.toJson(result));
+  }
+
+  @Test
+  public void testGetTemplateDetail() throws WxErrorException{
+    String templateId="3TkZjxugodbqpEMk9j7X6h6zKqYkc7MxQrrFmT7H";
+    WxCpTemplateResult result=wxService.getOAService().getTemplateDetail(templateId);
+    assertThat(result).isNotNull();
+    System.out.println("result ");
+    System.out.println(gson.toJson(result));
+  }
+
 }

+ 2 - 0
weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java

@@ -83,6 +83,7 @@ public class WxCpUserGsonAdapterTest {
     assertThat(user.getOrders()[1]).isEqualTo(2);
 
     assertThat(user.getAddress()).isEqualTo("广州市海珠区新港中路");
+    assertThat(user.getAlias()).isEqualTo("jackzhang");
     assertThat(user.getExternalAttrs()).isNotEmpty();
 
     final WxCpUser.ExternalAttribute externalAttr1 = user.getExternalAttrs().get(0);
@@ -102,6 +103,7 @@ public class WxCpUserGsonAdapterTest {
     assertThat(externalAttr3.getAppid()).isEqualTo("wx8bd80126147df384");
     assertThat(externalAttr3.getPagePath()).isEqualTo("/index");
     assertThat(externalAttr3.getTitle()).isEqualTo("my miniprogram");
+
   }
 
   @Test

+ 38 - 7
weixin-java-miniapp/pom.xml

@@ -7,7 +7,7 @@
   <parent>
     <groupId>com.github.binarywang</groupId>
     <artifactId>wx-java</artifactId>
-    <version>3.6.0</version>
+    <version>3.7.0</version>
   </parent>
 
   <artifactId>weixin-java-miniapp</artifactId>
@@ -80,12 +80,12 @@
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
     </dependency>
- 	<dependency>
-	  <groupId>com.github.jedis-lock</groupId>
-	  <artifactId>jedis-lock</artifactId>
-	  <version>1.0.0</version>
-	  <optional>true</optional>
-	</dependency>
+    <dependency>
+      <groupId>com.github.jedis-lock</groupId>
+      <artifactId>jedis-lock</artifactId>
+      <version>1.0.0</version>
+      <optional>true</optional>
+    </dependency>
   </dependencies>
 
   <build>
@@ -102,4 +102,35 @@
     </plugins>
   </build>
 
+  <profiles>
+    <profile>
+      <id>native-image</id>
+      <activation>
+        <activeByDefault>false</activeByDefault>
+      </activation>
+
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-compiler-plugin</artifactId>
+            <version>3.5.1</version>
+            <configuration>
+              <annotationProcessors>
+                cn.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor
+              </annotationProcessors>
+              <annotationProcessorPaths>
+                <path>
+                  <groupId>com.github.binarywang</groupId>
+                  <artifactId>weixin-graal</artifactId>
+                  <version>${project.version}</version>
+                </path>
+              </annotationProcessorPaths>
+            </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
 </project>

+ 317 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaCloudService.java

@@ -0,0 +1,317 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.cloud.*;
+import com.google.gson.JsonArray;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+import java.util.List;
+
+/**
+ * 云开发相关接口.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-01-22
+ */
+public interface WxMaCloudService {
+  String INVOKE_CLOUD_FUNCTION_URL = "https://api.weixin.qq.com/tcb/invokecloudfunction?env=%s&name=%s";
+  String DATABASE_COLLECTION_GET_URL = "https://api.weixin.qq.com/tcb/databasecollectionget";
+  String DATABASE_COLLECTION_DELETE_URL = "https://api.weixin.qq.com/tcb/databasecollectiondelete";
+  String DATABASE_COLLECTION_ADD_URL = "https://api.weixin.qq.com/tcb/databasecollectionadd";
+  String GET_QCLOUD_TOKEN_URL = "https://api.weixin.qq.com/tcb/getqcloudtoken";
+  String BATCH_DELETE_FILE_URL = "https://api.weixin.qq.com/tcb/batchdeletefile";
+  String UPLOAD_FILE_URL = "https://api.weixin.qq.com/tcb/uploadfile";
+  String DATABASE_MIGRATE_QUERY_INFO_URL = "https://api.weixin.qq.com/tcb/databasemigratequeryinfo";
+  String DATABASE_MIGRATE_EXPORT_URL = "https://api.weixin.qq.com/tcb/databasemigrateexport";
+  String DATABASE_MIGRATE_IMPORT_URL = "https://api.weixin.qq.com/tcb/databasemigrateimport";
+  String UPDATE_INDEX_URL = "https://api.weixin.qq.com/tcb/updateindex";
+  String DATABASE_COUNT_URL = "https://api.weixin.qq.com/tcb/databasecount";
+  String DATABASE_AGGREGATE_URL = "https://api.weixin.qq.com/tcb/databaseaggregate";
+  String DATABASE_QUERY_URL = "https://api.weixin.qq.com/tcb/databasequery";
+  String DATABASE_UPDATE_URL = "https://api.weixin.qq.com/tcb/databaseupdate";
+  String DATABASE_DELETE_URL = "https://api.weixin.qq.com/tcb/databasedelete";
+  String DATABASE_ADD_URL = "https://api.weixin.qq.com/tcb/databaseadd";
+
+  /**
+   * <pre>
+   * 触发云函数。注意:HTTP API 途径触发云函数不包含用户信息。
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/functions/invokeCloudFunction.html
+   *
+   * 请求地址
+   * POST https://api.weixin.qq.com/tcb/invokecloudfunction?access_token=ACCESS_TOKEN&env=ENV&name=FUNCTION_NAME
+   *
+   * </pre>
+   *
+   * @param env  string		是	云开发环境ID
+   * @param name string		是	云函数名称
+   * @param body string		是	云函数的传入参数,具体结构由开发者定义。
+   * @return resp_data  string	云函数返回的buffer
+   * @throws WxErrorException .
+   */
+  String invokeCloudFunction(String env, String name, String body) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 数据库插入记录
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseAdd.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/databaseadd?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env   云环境ID
+   * @param query 数据库操作语句
+   * @return 插入成功的数据集合主键_id
+   * @throws WxErrorException .
+   */
+  JsonArray databaseAdd(String env, String query) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 数据库删除记录
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseDelete.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/databasedelete?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env   云环境ID
+   * @param query 数据库操作语句
+   * @return 删除记录数量
+   * @throws WxErrorException .
+   */
+  int databaseDelete(String env, String query) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 数据库更新记录
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseUpdate.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/databaseupdate?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env   云环境ID
+   * @param query 数据库操作语句
+   * @return .
+   * @throws WxErrorException .
+   */
+  WxCloudDatabaseUpdateResult databaseUpdate(String env, String query) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 数据库查询记录
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseQuery.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/databasequery?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env   云环境ID
+   * @param query 数据库操作语句
+   * @return .
+   * @throws WxErrorException .
+   */
+  WxCloudDatabaseQueryResult databaseQuery(String env, String query) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 数据库聚合记录
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseAggregate.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/databaseaggregate?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env   云环境ID
+   * @param query 数据库操作语句
+   * @return .
+   * @throws WxErrorException .
+   */
+  JsonArray databaseAggregate(String env, String query) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 统计集合记录数或统计查询语句对应的结果记录数
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCount.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/databasecount?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env   云环境ID
+   * @param query 数据库操作语句
+   * @return 记录数量
+   * @throws WxErrorException .
+   */
+  Long databaseCount(String env, String query) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 变更数据库索引
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/updateIndex.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/updateindex?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env            云环境ID
+   * @param collectionName 集合名称
+   * @param createIndexes  新增索引对象
+   * @param dropIndexNames 要删除的索引的名字
+   * @throws WxErrorException .
+   */
+  void updateIndex(String env, String collectionName, List<WxCloudDatabaseCreateIndexRequest> createIndexes,
+                   List<String> dropIndexNames) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 数据库导入
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateImport.html
+   * 请求地址: POST https://api.weixin.qq.com/tcb/databasemigrateimport?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env            云环境ID
+   * @param collectionName 导入collection名
+   * @param filePath       导入文件路径(导入文件需先上传到同环境的存储中,可使用开发者工具或 HTTP API的上传文件 API上传)
+   * @param fileType       导入文件类型, 1	JSON, 2	CSV
+   * @param stopOnError    是否在遇到错误时停止导入
+   * @param conflictMode   冲突处理模式 : 1	INSERT	,    2	UPSERT
+   * @return jobId
+   * @throws WxErrorException .
+   */
+  Long databaseMigrateImport(String env, String collectionName, String filePath, int fileType, boolean stopOnError,
+                             int conflictMode) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 数据库导出
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateExport.html
+   * 请求地址: POST https://api.weixin.qq.com/tcb/databasemigrateexport?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env      云环境ID
+   * @param filePath 导出文件路径(文件会导出到同环境的云存储中,可使用获取下载链接 API 获取下载链接)
+   * @param fileType 导出文件类型, 1	JSON, 2	CSV
+   * @param query    导出条件
+   * @return jobId
+   * @throws WxErrorException .
+   */
+  Long databaseMigrateExport(String env, String filePath, int fileType, String query) throws WxErrorException;
+
+  /**
+   * <pre>
+   *   数据库迁移状态查询
+   *
+   *  文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseMigrateQueryInfo.html
+   *  请求地址:POST https://api.weixin.qq.com/tcb/databasemigratequeryinfo?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env   云环境ID
+   * @param jobId 迁移任务ID
+   * @return .
+   * @throws WxErrorException .
+   */
+  WxCloudCloudDatabaseMigrateQueryInfoResult databaseMigrateQueryInfo(String env, Long jobId) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 获取文件上传链接
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/uploadFile.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/uploadfile?access_token=ACCESS_TOKEN
+   *
+   * </pre>
+   *
+   * @param env  云环境ID
+   * @param path 上传路径
+   * @return 上传结果
+   * @throws WxErrorException .
+   */
+  WxCloudUploadFileResult uploadFile(String env, String path) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 获取文件下载链接
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/batchDownloadFile.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/batchdownloadfile?access_token=ACCESS_TOKEN
+   *
+   * </pre>
+   *
+   * @param env     云环境ID
+   * @param fileIds 文件ID列表
+   * @param maxAges 下载链接有效期列表,对应文件id列表
+   * @return 下载链接信息
+   * @throws WxErrorException .
+   */
+  WxCloudBatchDownloadFileResult batchDownloadFile(String env, String[] fileIds, long[] maxAges) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 删除文件
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/storage/batchDeleteFile.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/batchdeletefile?access_token=ACCESS_TOKEN
+   *
+   * </pre>
+   *
+   * @param env     云环境ID
+   * @param fileIds 文件ID列表
+   * @return 下载链接信息
+   * @throws WxErrorException .
+   */
+  WxCloudBatchDeleteFileResult batchDeleteFile(String env, String[] fileIds) throws WxErrorException;
+
+  /**
+   * <pre>
+   *  获取腾讯云API调用凭证
+   *
+   *  文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/utils/getQcloudToken.html
+   *  请求地址:POST https://api.weixin.qq.com/tcb/getqcloudtoken?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param lifeSpan 有效期(单位为秒,最大7200)
+   * @return .
+   * @throws WxErrorException .
+   */
+  WxCloudGetQcloudTokenResult getQcloudToken(long lifeSpan) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 新增集合
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionAdd.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/databasecollectionadd?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env            云环境ID
+   * @param collectionName 集合名称
+   * @throws WxErrorException .
+   */
+  void databaseCollectionAdd(String env, String collectionName) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 删除集合
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionDelete.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/databasecollectionadd?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env            云环境ID
+   * @param collectionName 集合名称
+   * @throws WxErrorException .
+   */
+  void databaseCollectionDelete(String env, String collectionName) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 获取特定云环境下集合信息
+   *
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/wxcloud/reference-http-api/database/databaseCollectionGet.html
+   * 请求地址:POST https://api.weixin.qq.com/tcb/databasecollectionget?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param env            云环境ID
+   * @param limit          获取数量限制,默认值:10
+   * @param offset         偏移量,默认值:0
+   * @return .
+   * @throws WxErrorException .
+   */
+  WxCloudDatabaseCollectionGetResult databaseCollectionGet(String env, Long limit, Long offset) throws WxErrorException;
+}

+ 203 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaExpressService.java

@@ -0,0 +1,203 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.express.WxMaExpressAccount;
+import cn.binarywang.wx.miniapp.bean.express.WxMaExpressDelivery;
+import cn.binarywang.wx.miniapp.bean.express.WxMaExpressPath;
+import cn.binarywang.wx.miniapp.bean.express.WxMaExpressPrinter;
+import cn.binarywang.wx.miniapp.bean.express.request.*;
+import cn.binarywang.wx.miniapp.bean.express.result.WxMaExpressOrderInfoResult;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+import java.util.List;
+
+/**
+ * 小程序物流助手
+ * @author <a href="https://github.com/mr-xiaoyu">xiaoyu</a>
+ * @since 2019-11-26
+ */
+public interface WxMaExpressService {
+  /**
+   * 获取支持的快递公司列表
+   */
+  String ALL_DELIVERY_URL = "https://api.weixin.qq.com/cgi-bin/express/business/delivery/getall";
+
+  /**
+   * 获取所有绑定的物流账号
+   */
+  String ALL_ACCOUNT_URL = "https://api.weixin.qq.com/cgi-bin/express/business/account/getall";
+
+  /**
+   * 绑定、解绑物流账号
+   */
+  String BIND_ACCOUNT_URL = "https://api.weixin.qq.com/cgi-bin/express/business/account/bind";
+
+  /**
+   * 获取电子面单余额
+   */
+  String GET_QUOTA_URL = "https://api.weixin.qq.com/cgi-bin/express/business/quota/get";
+
+  /**
+   * 配置面单打印员
+   */
+  String UPDATE_PRINTER_URL = "https://api.weixin.qq.com/cgi-bin/express/business/printer/update";
+
+  /**
+   * 获取打印员
+   */
+  String GET_PRINTER_URL = "https://api.weixin.qq.com/cgi-bin/express/business/printer/getall";
+
+  /**
+   * 生成运单
+   */
+  String ADD_ORDER_URL = "https://api.weixin.qq.com/cgi-bin/express/business/order/add";
+
+  /**
+   * 批量获取运单数据
+   */
+  String BATCH_GET_ORDER_URL = "https://api.weixin.qq.com/cgi-bin/express/business/order/batchget";
+
+  /**
+   * 取消运单
+   */
+  String CANCEL_ORDER_URL = "https://api.weixin.qq.com/cgi-bin/express/business/order/cancel";
+
+  /**
+   * 获取运单数据
+   */
+  String GET_ORDER_URL = "https://api.weixin.qq.com/cgi-bin/express/business/order/get";
+
+  /**
+   * 查询运单轨迹
+   */
+  String GET_PATH_URL = "https://api.weixin.qq.com/cgi-bin/express/business/path/get";
+
+  /**
+   * 模拟快递公司更新订单状态
+   */
+  String TEST_UPDATE_ORDER_URL = "https://api.weixin.qq.com/cgi-bin/express/business/test_update_order";
+
+  /**
+   * 获取支持的快递公司列表
+   * @return  快递公司列表
+   * @throws WxErrorException 获取失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.getAllDelivery.html">查看文档</a>
+   * </pre>
+   */
+  List<WxMaExpressDelivery> getAllDelivery() throws WxErrorException;
+
+  /**
+   * 获取所有绑定的物流账号
+   * @return 物流账号list
+   * @throws WxErrorException 获取失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.getAllAccount.html">查看文档</a>
+   * </pre>
+   */
+  List<WxMaExpressAccount> getAllAccount() throws WxErrorException;
+
+  /**
+   * 绑定、解绑物流账号
+   * @param wxMaExpressBindAccountRequest 物流账号对象
+   * @throws WxErrorException 请求失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.bindAccount.html">查看文档</a>
+   * </pre>
+   */
+  void bindAccount(WxMaExpressBindAccountRequest wxMaExpressBindAccountRequest) throws WxErrorException;
+
+  /**
+   * 获取电子面单余额。仅在使用加盟类快递公司时,才可以调用。
+   * @param wxMaExpressBindAccountRequest 物流账号对象
+   * @return 电子面单余额
+   * @throws WxErrorException 获取失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.getQuota.html">查看文档</a>
+   * </pre>
+   */
+  Integer getQuota(WxMaExpressBindAccountRequest wxMaExpressBindAccountRequest) throws WxErrorException;
+
+  /**
+   * 配置面单打印员,可以设置多个,若需要使用微信打单 PC 软件,才需要调用。
+   * @param wxMaExpressPrinterUpdateRequest  面单打印员对象
+   * @throws WxErrorException 请求失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.updatePrinter.html">查看文档</a>
+   * </pre>
+   */
+  void updatePrinter(WxMaExpressPrinterUpdateRequest wxMaExpressPrinterUpdateRequest) throws WxErrorException;
+
+  /**
+   * 获取打印员。若需要使用微信打单 PC 软件,才需要调用
+   * @return 打印员
+   * @throws WxErrorException 获取失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.getPrinter.html">查看文档</a>
+   * </pre>
+   */
+  WxMaExpressPrinter getPrinter() throws WxErrorException;
+
+  /**
+   * 生成运单
+   * @param wxMaExpressAddOrderRequest 生成运单请求对象
+   * @return 生成运单结果
+   * @throws WxErrorException 请求失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.addOrder.html">查看文档</a>
+   * </pre>
+   */
+  WxMaExpressOrderInfoResult addOrder(WxMaExpressAddOrderRequest wxMaExpressAddOrderRequest) throws WxErrorException;
+
+  /**
+   * 批量获取运单数据
+   * @param requests 获取运单请求对象集合,最多不能超过1000个
+   * @return 运单信息集合
+   * @throws WxErrorException 获取失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.batchGetOrder.html">查看文档</a>
+   * </pre>
+   */
+  List<WxMaExpressOrderInfoResult> batchGetOrder(List<WxMaExpressGetOrderRequest> requests) throws WxErrorException;
+
+  /**
+   * 取消运单
+   * @param wxMaExpressGetOrderRequest 运单信息请求对象
+   * @throws WxErrorException 取消失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.cancelOrder.html">查看文档</a>
+   * </pre>
+   */
+  void cancelOrder(WxMaExpressGetOrderRequest wxMaExpressGetOrderRequest) throws WxErrorException;
+
+  /**
+   * 获取运单数据
+   * @param wxMaExpressGetOrderRequest 运单信息请求对象
+   * @return 运单信息
+   * @throws WxErrorException 获取失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.getOrder.html">查看文档</a>
+   * </pre>
+   */
+  WxMaExpressOrderInfoResult getOrder(WxMaExpressGetOrderRequest wxMaExpressGetOrderRequest) throws WxErrorException;
+
+  /**
+   * 查询运单轨迹
+   * @param wxMaExpressGetOrderRequest 运单信息请求对象
+   * @return 运单轨迹对象
+   * @throws WxErrorException 查询失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.getPath.html">查看文档</a>
+   * </pre>
+   */
+  WxMaExpressPath getPath(WxMaExpressGetOrderRequest wxMaExpressGetOrderRequest) throws WxErrorException;
+
+  /**
+   * 模拟快递公司更新订单状态, 该接口只能用户测试
+   * @param wxMaExpressTestUpdateOrderRequest  模拟快递公司更新订单状态请求对象
+   * @throws WxErrorException 模拟更新订单状态失败时返回
+   * <pre>
+   *   <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/express/by-business/logistics.testUpdateOrder.html">查看文档</a>
+   * </pre>
+   */
+  void testUpdateOrder(WxMaExpressTestUpdateOrderRequest wxMaExpressTestUpdateOrderRequest) throws WxErrorException;
+}

+ 48 - 7
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaMsgService.java

@@ -1,9 +1,7 @@
 package cn.binarywang.wx.miniapp.api;
 
-import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
-import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
-import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
-import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage;
+import cn.binarywang.wx.miniapp.bean.*;
+import com.google.gson.JsonObject;
 import me.chanjar.weixin.common.error.WxErrorException;
 
 /**
@@ -18,6 +16,8 @@ public interface WxMaMsgService {
   String TEMPLATE_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send";
   String SUBSCRIBE_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send";
   String UNIFORM_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send";
+  String ACTIVITY_ID_CREATE_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/activityid/create";
+  String UPDATABLE_MSG_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/wxopen/updatablemsg/send";
 
   /**
    * <pre>
@@ -25,6 +25,10 @@ public interface WxMaMsgService {
    * 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/customerServiceMessage.send.html">发送客服消息</a>
    * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN
    * </pre>
+   *
+   * @param message 客服消息
+   * @return .
+   * @throws WxErrorException .
    */
   boolean sendKefuMsg(WxMaKefuMessage message) throws WxErrorException;
 
@@ -33,26 +37,63 @@ public interface WxMaMsgService {
    * 发送模板消息
    * 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/templateMessage.send.html">发送模板消息</a>
    * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN
+   * 小程序模板消息接口将于2020年1月10日下线,开发者可使用订阅消息功能
    * </pre>
+   *
+   * @param templateMessage 模版消息
+   * @throws WxErrorException .
    */
+  @Deprecated
   void sendTemplateMsg(WxMaTemplateMessage templateMessage) throws WxErrorException;
 
-
   /**
    * <pre>
+   * 发送订阅消息
    * https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
    * </pre>
-   * 发送订阅消息
+   *
+   * @param subscribeMessage 订阅消息
+   * @throws WxErrorException .
    */
   void sendSubscribeMsg(WxMaSubscribeMessage subscribeMessage) throws WxErrorException;
 
-
   /**
    * <pre>
    * 下发小程序和公众号统一的服务消息
    * 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api/open-api/uniform-message/sendUniformMessage.html">下发小程序和公众号统一的服务消息</a>
    * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/wxopen/template/uniform_send?access_token=ACCESS_TOKEN
    * </pre>
+   *
+   * @param uniformMessage 消息
+   * @throws WxErrorException .
    */
   void sendUniformMsg(WxMaUniformMessage uniformMessage) throws WxErrorException;
+
+  /**
+   * <pre>
+   *  创建被分享动态消息的 activity_id.
+   *  动态消息: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share/updatable-message.html
+   *
+   *  文档地址:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/updatable-message/updatableMessage.createActivityId.html
+   *  接口地址:GET https://api.weixin.qq.com/cgi-bin/message/wxopen/activityid/create?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @return .
+   * @throws WxErrorException .
+   */
+  JsonObject createUpdatableMessageActivityId() throws WxErrorException;
+
+  /**
+   * <pre>
+   *  修改被分享的动态消息.
+   *  动态消息: https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/share/updatable-message.html
+   *
+   *  文档地址:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/updatable-message/updatableMessage.setUpdatableMsg.html
+   *  接口地址:POST https://api.weixin.qq.com/cgi-bin/message/wxopen/activityid/create?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param msg 动态消息
+   * @throws WxErrorException .
+   */
+  void setUpdatableMsg(WxMaUpdatableMsg msg) throws WxErrorException;
 }

+ 8 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaSecCheckService.java

@@ -33,6 +33,14 @@ public interface WxMaSecCheckService {
   boolean checkImage(File file) throws WxErrorException;
 
   /**
+   * 校验一张图片是否含有违法违规内容
+   * @param fileUrl 文件网络地址
+   * @return 是否违规
+   * @throws WxErrorException .
+   */
+  boolean checkImage(String fileUrl) throws WxErrorException;
+
+  /**
    * <pre>
    * 检查一段文本是否含有违法违规内容。
    * 应用场景举例:

+ 22 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java

@@ -87,6 +87,11 @@ public interface WxMaService {
   String post(String url, String postData) throws WxErrorException;
 
   /**
+   * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求.
+   */
+  String post(String url, Object obj) throws WxErrorException;
+
+  /**
    * <pre>
    * Service没有实现某个API的时候,可以用这个,
    * 比{@link #get}和{@link #post}方法更灵活,可以自己构造RequestExecutor用来处理不同的参数和不同的返回类型。
@@ -159,6 +164,13 @@ public interface WxMaService {
   WxMaTemplateService getTemplateService();
 
   /**
+   * 返回订阅消息配置相关接口方法的实现类对象, 以方便调用其各个接口.
+   *
+   * @return WxMaSubscribeService
+   */
+  WxMaSubscribeService getSubscribeService();
+
+  /**
    * 数据分析相关查询服务.
    *
    * @return WxMaAnalysisService
@@ -224,5 +236,15 @@ public interface WxMaService {
    */
   RequestHttp getRequestHttp();
 
+  /**
+   * 获取物流助手接口服务对象
+   *
+   * @return
+   */
+  WxMaExpressService getExpressService();
 
+  /**
+   * 获取云开发接口服务对象
+   */
+  WxMaCloudService getCloudService();
 }

+ 153 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaSubscribeService.java

@@ -0,0 +1,153 @@
+package cn.binarywang.wx.miniapp.api;
+
+import cn.binarywang.wx.miniapp.bean.template.WxMaPubTemplateTitleListResult;
+import lombok.Data;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+import java.util.List;
+
+/**
+ * 订阅消息类
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2019-12-15
+ */
+public interface WxMaSubscribeService {
+  /**
+   * 获取模板标题下的关键词列表.
+   */
+  String GET_PUB_TEMPLATE_TITLE_LIST_URL = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatetitles";
+
+  /**
+   * 获取模板标题下的关键词列表.
+   */
+  String GET_PUB_TEMPLATE_KEY_WORDS_BY_ID_URL = "https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatekeywords";
+
+  /**
+   * 组合模板并添加至帐号下的个人模板库.
+   */
+  String TEMPLATE_ADD_URL = "https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate";
+
+  /**
+   * 获取当前帐号下的个人模板列表.
+   */
+  String TEMPLATE_LIST_URL = "https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate";
+
+  /**
+   * 删除帐号下的某个模板.
+   */
+  String TEMPLATE_DEL_URL = "https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate";
+
+  /**
+   * 获取小程序账号的类目
+   */
+  String GET_CATEGORY_URL = "https://api.weixin.qq.com/wxaapi/newtmpl/getcategory";
+
+  /**
+   * <pre>
+   * 获取帐号所属类目下的公共模板标题
+   *
+   * 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getPubTemplateTitleList.html">获取帐号所属类目下的公共模板标题</a>
+   * 接口url格式: https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatetitles?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param ids   类目 id,多个用逗号隔开
+   * @param limit 用于分页,表示拉取 limit 条记录。最大为 30。
+   * @param start 用于分页,表示从 start 开始。从 0 开始计数。
+   * @return .
+   * @throws WxErrorException .
+   */
+  WxMaPubTemplateTitleListResult getPubTemplateTitleList(String[] ids, int start, int limit) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 获取模板库某个模板标题下关键词库
+   *
+   * 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getPubTemplateKeyWordsById.html">获取模板标题下的关键词列表</a>
+   * 接口url格式: GET https://api.weixin.qq.com/wxaapi/newtmpl/getpubtemplatekeywords?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param id 模板标题 id,可通过接口获取
+   * @return .
+   * @throws WxErrorException .
+   */
+  List<PubTemplateKeyword> getPubTemplateKeyWordsById(String id) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 组合模板并添加至帐号下的个人模板库
+   *
+   * 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.addTemplate.html">获取小程序模板库标题列表</a>
+   * 接口url格式: POST https://api.weixin.qq.com/wxaapi/newtmpl/addtemplate?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param id            模板标题 id,可通过接口获取,也可登录小程序后台查看获取
+   * @param keywordIdList 模板关键词列表
+   * @param sceneDesc     服务场景描述,15个字以内
+   * @return 添加至帐号下的模板id,发送小程序订阅消息时所需
+   * @throws WxErrorException .
+   */
+  String addTemplate(String id, List<Integer> keywordIdList, String sceneDesc) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 获取当前帐号下的个人模板列表
+   *
+   * 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getTemplateList.html">获取当前帐号下的个人模板列表</a>
+   * 接口url格式: GET https://api.weixin.qq.com/wxaapi/newtmpl/gettemplate?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @return .
+   * @throws WxErrorException .
+   */
+  List<TemplateInfo> getTemplateList() throws WxErrorException;
+
+  /**
+   * <pre>
+   * 删除帐号下的某个模板
+   *
+   * 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.deleteTemplate.html">删除帐号下的个人模板</a>
+   * 接口url格式: POST https://api.weixin.qq.com/wxaapi/newtmpl/deltemplate?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @param templateId 要删除的模板id
+   * @return 删除是否成功
+   * @throws WxErrorException .
+   */
+  boolean delTemplate(String templateId) throws WxErrorException;
+
+  /**
+   * <pre>
+   * 获取小程序账号的类目
+   * https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.getCategory.html
+   * GET https://api.weixin.qq.com/wxaapi/newtmpl/getcategory?access_token=ACCESS_TOKEN
+   * </pre>
+   *
+   * @return .
+   * @throws WxErrorException .
+   */
+  List<CategoryData> getCategory() throws WxErrorException;
+
+  @Data
+  class CategoryData {
+    int id;
+    String name;
+  }
+
+  @Data
+  class TemplateInfo {
+    private String priTmplId;
+    private String title;
+    private String content;
+    private String example;
+    private int type;
+  }
+
+  @Data
+  class PubTemplateKeyword {
+    private int kid;
+    private String name;
+    private String example;
+    private String rule;
+  }
+}

+ 4 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaTemplateService.java

@@ -8,6 +8,10 @@ import me.chanjar.weixin.common.error.WxErrorException;
 
 import java.util.List;
 
+/**
+ * @author IOMan(lewis.lynn1006@gmail.com)
+ */
+@Deprecated
 public interface WxMaTemplateService {
   /**
    * 获取小程序模板库标题列表.

+ 178 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCloudServiceImpl.java

@@ -0,0 +1,178 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaCloudService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.cloud.*;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Lists;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 云开发相关接口实现类.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-01-22
+ */
+@Slf4j
+@RequiredArgsConstructor
+public class WxMaCloudServiceImpl implements WxMaCloudService {
+  private static final JsonParser JSON_PARSER = new JsonParser();
+  private final WxMaService wxMaService;
+
+  @Override
+  public String invokeCloudFunction(String env, String name, String body) throws WxErrorException {
+    final String response = this.wxMaService.post(String.format(INVOKE_CLOUD_FUNCTION_URL, env, name), body);
+    return JSON_PARSER.parse(response).getAsJsonObject().get("resp_data").getAsString();
+  }
+
+  @Override
+  public JsonArray databaseAdd(String env, String query) throws WxErrorException {
+    String response = this.wxMaService.post(DATABASE_ADD_URL, ImmutableMap.of("env", env, "query", query));
+    return JSON_PARSER.parse(response).getAsJsonObject().get("id_list").getAsJsonArray();
+  }
+
+  @Override
+  public int databaseDelete(String env, String query) throws WxErrorException {
+    String response = this.wxMaService.post(DATABASE_DELETE_URL, ImmutableMap.of("env", env, "query", query));
+    return JSON_PARSER.parse(response).getAsJsonObject().get("deleted").getAsInt();
+  }
+
+  @Override
+  public WxCloudDatabaseUpdateResult databaseUpdate(String env, String query) throws WxErrorException {
+    String response = this.wxMaService.post(DATABASE_UPDATE_URL, ImmutableMap.of("env", env, "query", query));
+    return WxGsonBuilder.create().fromJson(response, WxCloudDatabaseUpdateResult.class);
+  }
+
+  @Override
+  public WxCloudDatabaseQueryResult databaseQuery(String env, String query) throws WxErrorException {
+    String response = this.wxMaService.post(DATABASE_QUERY_URL, ImmutableMap.of("env", env, "query", query));
+    return WxGsonBuilder.create().fromJson(response, WxCloudDatabaseQueryResult.class);
+  }
+
+  @Override
+  public JsonArray databaseAggregate(String env, String query) throws WxErrorException {
+    String response = this.wxMaService.post(DATABASE_AGGREGATE_URL, ImmutableMap.of("env", env, "query", query));
+    return JSON_PARSER.parse(response).getAsJsonObject().get("data").getAsJsonArray();
+  }
+
+  @Override
+  public Long databaseCount(String env, String query) throws WxErrorException {
+    String response = this.wxMaService.post(DATABASE_COUNT_URL, ImmutableMap.of("env", env, "query", query));
+    return JSON_PARSER.parse(response).getAsJsonObject().get("count").getAsLong();
+  }
+
+  @Override
+  public void updateIndex(String env, String collectionName, List<WxCloudDatabaseCreateIndexRequest> createIndexes,
+                          List<String> dropIndexNames) throws WxErrorException {
+    List<Map<String, String>> dropIndexes = Lists.newArrayList();
+    if (dropIndexNames != null) {
+      for (String index : dropIndexNames) {
+        dropIndexes.add(ImmutableMap.of("name", index));
+      }
+    }
+
+    this.wxMaService.post(UPDATE_INDEX_URL, ImmutableMap.of("env", env,
+      "collection_name", collectionName, "create_indexes", createIndexes, "drop_indexes", dropIndexes));
+  }
+
+  @Override
+  public Long databaseMigrateImport(String env, String collectionName, String filePath, int fileType,
+                                    boolean stopOnError, int conflictMode) throws WxErrorException {
+    JsonObject params = new JsonObject();
+    params.addProperty("env", env);
+    params.addProperty("collection_name", collectionName);
+    params.addProperty("file_path", filePath);
+    params.addProperty("file_type", fileType);
+    params.addProperty("stop_on_error", stopOnError);
+    params.addProperty("conflict_mode", conflictMode);
+
+    String response = this.wxMaService.post(DATABASE_MIGRATE_IMPORT_URL, params.toString());
+    return JSON_PARSER.parse(response).getAsJsonObject().get("job_id").getAsLong();
+  }
+
+  @Override
+  public Long databaseMigrateExport(String env, String filePath, int fileType, String query) throws WxErrorException {
+    JsonObject params = new JsonObject();
+    params.addProperty("env", env);
+    params.addProperty("file_path", filePath);
+    params.addProperty("file_type", fileType);
+    params.addProperty("query", query);
+
+    String response = this.wxMaService.post(DATABASE_MIGRATE_EXPORT_URL, params.toString());
+    return JSON_PARSER.parse(response).getAsJsonObject().get("job_id").getAsLong();
+  }
+
+  @Override
+  public WxCloudCloudDatabaseMigrateQueryInfoResult databaseMigrateQueryInfo(String env, Long jobId) throws WxErrorException {
+    String response = this.wxMaService.post(DATABASE_MIGRATE_QUERY_INFO_URL, ImmutableMap.of("env", env, "job_id", jobId));
+    return WxGsonBuilder.create().fromJson(response, WxCloudCloudDatabaseMigrateQueryInfoResult.class);
+  }
+
+  @Override
+  public WxCloudUploadFileResult uploadFile(String env, String path) throws WxErrorException {
+    String response = this.wxMaService.post(UPLOAD_FILE_URL, ImmutableMap.of("env", env, "path", path));
+    return WxGsonBuilder.create().fromJson(response, WxCloudUploadFileResult.class);
+  }
+
+  @Override
+  public WxCloudBatchDownloadFileResult batchDownloadFile(String env, String[] fileIds, long[] maxAges) throws WxErrorException {
+    List<Map<String, Serializable>> fileList = Lists.newArrayList();
+    int i = 0;
+    for (String fileId : fileIds) {
+      fileList.add(ImmutableMap.of("fileid", fileId, "max_age", (Serializable) maxAges[i++]));
+    }
+
+    String response = this.wxMaService.post(GET_QCLOUD_TOKEN_URL, ImmutableMap.of("env", env, "file_list", fileList));
+    return WxGsonBuilder.create().fromJson(response, WxCloudBatchDownloadFileResult.class);
+  }
+
+  @Override
+  public WxCloudBatchDeleteFileResult batchDeleteFile(String env, String[] fileIds) throws WxErrorException {
+    String response = this.wxMaService.post(BATCH_DELETE_FILE_URL, ImmutableMap.of("env", env, "fileid_list", fileIds));
+    return WxGsonBuilder.create().fromJson(response, WxCloudBatchDeleteFileResult.class);
+  }
+
+  @Override
+  public WxCloudGetQcloudTokenResult getQcloudToken(long lifeSpan) throws WxErrorException {
+    String response = this.wxMaService.post(GET_QCLOUD_TOKEN_URL, ImmutableMap.of("lifespan", lifeSpan));
+    return WxGsonBuilder.create().fromJson(response, WxCloudGetQcloudTokenResult.class);
+  }
+
+  @Override
+  public void databaseCollectionAdd(String env, String collectionName) throws WxErrorException {
+    this.wxMaService.post(DATABASE_COLLECTION_ADD_URL, ImmutableMap.of("env", env, "collection_name", collectionName));
+  }
+
+  @Override
+  public void databaseCollectionDelete(String env, String collectionName) throws WxErrorException {
+    this.wxMaService.post(DATABASE_COLLECTION_DELETE_URL, ImmutableMap.of("env", env, "collection_name", collectionName));
+  }
+
+  @Override
+  public WxCloudDatabaseCollectionGetResult databaseCollectionGet(String env, Long limit, Long offset) throws WxErrorException {
+    Map<String, Object> params = new HashMap<>(2);
+    params.put("env", env);
+    if (limit != null) {
+      params.put("limit", limit);
+    }
+
+    if (offset != null) {
+      params.put("offset", offset);
+    }
+
+    String response = this.wxMaService.post(DATABASE_COLLECTION_GET_URL, params);
+    return WxGsonBuilder.create().fromJson(response, WxCloudDatabaseCollectionGetResult.class);
+  }
+
+}

+ 98 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImpl.java

@@ -0,0 +1,98 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaExpressService;
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.bean.express.WxMaExpressAccount;
+import cn.binarywang.wx.miniapp.bean.express.WxMaExpressDelivery;
+import cn.binarywang.wx.miniapp.bean.express.WxMaExpressPath;
+import cn.binarywang.wx.miniapp.bean.express.WxMaExpressPrinter;
+import cn.binarywang.wx.miniapp.bean.express.request.*;
+import cn.binarywang.wx.miniapp.bean.express.result.WxMaExpressOrderInfoResult;
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import lombok.AllArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="https://github.com/mr-xiaoyu">xiaoyu</a>
+ * @since 2019-11-26
+ */
+@AllArgsConstructor
+public class WxMaExpressServiceImpl implements WxMaExpressService {
+
+  private WxMaService wxMaService;
+
+  @Override
+  public List<WxMaExpressDelivery> getAllDelivery() throws WxErrorException {
+    String responseContent = this.wxMaService.get(ALL_DELIVERY_URL, null);
+    return WxMaExpressDelivery.fromJson(responseContent);
+  }
+
+  @Override
+  public List<WxMaExpressAccount> getAllAccount() throws WxErrorException {
+    String responseContent = this.wxMaService.get(ALL_ACCOUNT_URL, null);
+    return WxMaExpressAccount.fromJsonList(responseContent);
+  }
+
+  @Override
+  public void bindAccount(WxMaExpressBindAccountRequest wxMaExpressBindAccountRequest) throws WxErrorException {
+    this.wxMaService.post(BIND_ACCOUNT_URL,wxMaExpressBindAccountRequest.toJson());
+  }
+
+  @Override
+  public Integer getQuota(WxMaExpressBindAccountRequest wxMaExpressBindAccountRequest) throws WxErrorException {
+    String responseContent = this.wxMaService.post(GET_QUOTA_URL,wxMaExpressBindAccountRequest.toJson());
+    WxMaExpressAccount account = WxMaExpressAccount.fromJson(responseContent);
+    return account.getQuotaNum();
+  }
+
+  @Override
+  public void updatePrinter(WxMaExpressPrinterUpdateRequest wxMaExpressPrinterUpdateRequest) throws WxErrorException {
+    this.wxMaService.post(UPDATE_PRINTER_URL,wxMaExpressPrinterUpdateRequest.toJson());
+  }
+
+  @Override
+  public WxMaExpressPrinter getPrinter() throws WxErrorException {
+    String responseContent = this.wxMaService.get(GET_PRINTER_URL, null);
+    return WxMaExpressPrinter.fromJson(responseContent);
+  }
+
+  @Override
+  public WxMaExpressOrderInfoResult addOrder(WxMaExpressAddOrderRequest wxMaExpressAddOrderRequest) throws WxErrorException {
+    String responseContent = this.wxMaService.post(ADD_ORDER_URL,wxMaExpressAddOrderRequest.toJson());
+    return WxMaExpressOrderInfoResult.fromJson(responseContent);
+  }
+
+  @Override
+  public List<WxMaExpressOrderInfoResult> batchGetOrder(List<WxMaExpressGetOrderRequest> requests) throws WxErrorException {
+    Map<String, Object> param = new HashMap<>(1);
+    param.put("order_list", requests);
+    String responseContent = this.wxMaService.post(BATCH_GET_ORDER_URL, WxMaGsonBuilder.create().toJson(param));
+    return WxMaExpressOrderInfoResult.toList(responseContent);
+  }
+
+  @Override
+  public void cancelOrder(WxMaExpressGetOrderRequest wxMaExpressGetOrderRequest) throws WxErrorException {
+    this.wxMaService.post(CANCEL_ORDER_URL,wxMaExpressGetOrderRequest.toJson());
+  }
+
+  @Override
+  public WxMaExpressOrderInfoResult getOrder(WxMaExpressGetOrderRequest wxMaExpressGetOrderRequest) throws WxErrorException {
+    String responseContent = this.wxMaService.post(GET_ORDER_URL,wxMaExpressGetOrderRequest.toJson());
+    return WxMaExpressOrderInfoResult.fromJson(responseContent);
+  }
+
+  @Override
+  public WxMaExpressPath getPath(WxMaExpressGetOrderRequest wxMaExpressGetOrderRequest) throws WxErrorException {
+    String responseContent = this.wxMaService.post(GET_PATH_URL,wxMaExpressGetOrderRequest.toJson());
+    return WxMaExpressPath.fromJson(responseContent);
+  }
+
+  @Override
+  public void testUpdateOrder(WxMaExpressTestUpdateOrderRequest wxMaExpressTestUpdateOrderRequest) throws WxErrorException {
+    this.wxMaService.post(TEST_UPDATE_ORDER_URL,wxMaExpressTestUpdateOrderRequest.toJson());
+  }
+}

+ 1 - 1
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaJsapiServiceImpl.java

@@ -51,7 +51,7 @@ public class WxMaJsapiServiceImpl implements WxMaJsapiService {
     } finally {
       lock.unlock();
     }
-    return this.wxMaService.getWxMaConfig().getJsapiTicket();
+    return this.wxMaService.getWxMaConfig().getCardApiTicket();
   }
 
   @Override

+ 13 - 9
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java

@@ -2,11 +2,9 @@ package cn.binarywang.wx.miniapp.api.impl;
 
 import cn.binarywang.wx.miniapp.api.WxMaMsgService;
 import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.bean.WxMaKefuMessage;
-import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;
-import cn.binarywang.wx.miniapp.bean.WxMaTemplateMessage;
-import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage;
+import cn.binarywang.wx.miniapp.bean.*;
 import cn.binarywang.wx.miniapp.constant.WxMaConstants;
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
 import com.google.gson.JsonObject;
 import com.google.gson.JsonParser;
 import lombok.AllArgsConstructor;
@@ -28,11 +26,6 @@ public class WxMaMsgServiceImpl implements WxMaMsgService {
     return responseContent != null;
   }
 
-  /**
-   * <pre>
-   * 小程序模板消息接口将于2020年1月10日下线,开发者可使用订阅消息功能
-   * </pre>
-   */
   @Override
   public void sendTemplateMsg(WxMaTemplateMessage templateMessage) throws WxErrorException {
     String responseContent = this.wxMaService.post(TEMPLATE_MSG_SEND_URL, templateMessage.toJson());
@@ -60,4 +53,15 @@ public class WxMaMsgServiceImpl implements WxMaMsgService {
     }
   }
 
+  @Override
+  public JsonObject createUpdatableMessageActivityId() throws WxErrorException {
+    final String responseContent = this.wxMaService.get(ACTIVITY_ID_CREATE_URL, null);
+    return JSON_PARSER.parse(responseContent).getAsJsonObject();
+  }
+
+  @Override
+  public void setUpdatableMsg(WxMaUpdatableMsg msg) throws WxErrorException {
+    this.wxMaService.post(UPDATABLE_MSG_SEND_URL, WxMaGsonBuilder.create().toJson(msg));
+  }
+
 }

+ 19 - 3
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSecCheckServiceImpl.java

@@ -4,11 +4,16 @@ import cn.binarywang.wx.miniapp.api.WxMaSecCheckService;
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.bean.WxMaMediaAsyncCheckResult;
 import com.google.gson.JsonObject;
-import java.io.File;
 import lombok.AllArgsConstructor;
 import me.chanjar.weixin.common.bean.result.WxMediaUploadResult;
+import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
+import org.apache.commons.io.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
 
 /**
  * <pre>
@@ -20,18 +25,29 @@ import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
  */
 @AllArgsConstructor
 public class WxMaSecCheckServiceImpl implements WxMaSecCheckService {
-
   private WxMaService service;
 
   @Override
   public boolean checkImage(File file) throws WxErrorException {
-    //这里只是借用MediaUploadRequestExecutor,并不使用其返回值WxMediaUploadResult
     WxMediaUploadResult result = this.service.execute(MediaUploadRequestExecutor
       .create(this.service.getRequestHttp()), IMG_SEC_CHECK_URL, file);
     return result != null;
   }
 
   @Override
+  public boolean checkImage(String fileUrl) throws WxErrorException {
+    File file = new File(FileUtils.getTempDirectory(), System.currentTimeMillis() + ".tmp");
+    try {
+      URL url = new URL(fileUrl);
+      FileUtils.copyURLToFile(url, file);
+    } catch (IOException e) {
+      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("文件地址读取异常").build(), e);
+    }
+    
+    return this.checkImage(file);
+  }
+
+  @Override
   public boolean checkMessage(String msgString) throws WxErrorException {
     JsonObject jsonObject = new JsonObject();
     jsonObject.addProperty("content", msgString);

+ 24 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaServiceImpl.java

@@ -16,6 +16,7 @@ import me.chanjar.weixin.common.util.crypto.SHA1;
 import me.chanjar.weixin.common.util.http.*;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpHost;
 import org.apache.http.client.config.RequestConfig;
@@ -54,6 +55,9 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
   private WxMaRunService runService = new WxMaRunServiceImpl(this);
   private WxMaSecCheckService secCheckService = new WxMaSecCheckServiceImpl(this);
   private WxMaPluginService pluginService = new WxMaPluginServiceImpl(this);
+  private WxMaExpressService expressService = new WxMaExpressServiceImpl(this);
+  private WxMaSubscribeService subscribeService = new WxMaSubscribeServiceImpl(this);
+  private WxMaCloudService cloudService = new WxMaCloudServiceImpl(this);
 
   private int retrySleepMillis = 1000;
   private int maxRetryTimes = 5;
@@ -204,6 +208,11 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
     return execute(SimplePostRequestExecutor.create(this), url, postData);
   }
 
+  @Override
+  public String post(String url, Object obj) throws WxErrorException {
+    return this.execute(SimplePostRequestExecutor.create(this), url, WxGsonBuilder.create().toJson(obj));
+  }
+
   /**
    * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
    */
@@ -327,6 +336,11 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
   }
 
   @Override
+  public WxMaSubscribeService getSubscribeService() {
+    return this.subscribeService;
+  }
+
+  @Override
   public WxMaAnalysisService getAnalysisService() {
     return this.analysisService;
   }
@@ -365,4 +379,14 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl
   public WxMaPluginService getPluginService() {
     return this.pluginService;
   }
+
+  @Override
+  public WxMaExpressService getExpressService() {
+    return this.expressService;
+  }
+
+  @Override
+  public WxMaCloudService getCloudService() {
+    return this.cloudService;
+  }
 }

+ 73 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java

@@ -0,0 +1,73 @@
+package cn.binarywang.wx.miniapp.api.impl;
+
+import cn.binarywang.wx.miniapp.api.WxMaService;
+import cn.binarywang.wx.miniapp.api.WxMaSubscribeService;
+import cn.binarywang.wx.miniapp.bean.template.WxMaPubTemplateTitleListResult;
+import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.JsonParser;
+import com.google.gson.reflect.TypeToken;
+import lombok.AllArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2019-12-15
+ */
+@AllArgsConstructor
+public class WxMaSubscribeServiceImpl implements WxMaSubscribeService {
+  private WxMaService wxMaService;
+
+  @Override
+  public WxMaPubTemplateTitleListResult getPubTemplateTitleList(String[] ids, int start, int limit) throws WxErrorException {
+    ImmutableMap<String, ? extends Serializable> params = ImmutableMap.of("ids", StringUtils.join(ids, ","),
+      "start", start, "limit", limit);
+    String responseText = this.wxMaService.get(GET_PUB_TEMPLATE_TITLE_LIST_URL,
+      Joiner.on("&").withKeyValueSeparator("=").join(params));
+    return WxMaPubTemplateTitleListResult.fromJson(responseText);
+  }
+
+  @Override
+  public List<PubTemplateKeyword> getPubTemplateKeyWordsById(String id) throws WxErrorException {
+    String responseText = this.wxMaService.get(GET_PUB_TEMPLATE_KEY_WORDS_BY_ID_URL,
+      Joiner.on("&").withKeyValueSeparator("=").join(ImmutableMap.of("tid", id)));
+    return WxMaGsonBuilder.create().fromJson(new JsonParser().parse(responseText).getAsJsonObject()
+      .getAsJsonArray("data"), new TypeToken<List<PubTemplateKeyword>>() {
+    }.getType());
+  }
+
+  @Override
+  public String addTemplate(String id, List<Integer> keywordIdList, String sceneDesc) throws WxErrorException {
+    String responseText = this.wxMaService.post(TEMPLATE_ADD_URL, ImmutableMap.of("tid", id,
+      "kidList", keywordIdList.toArray(),
+      "sceneDesc", sceneDesc));
+    return new JsonParser().parse(responseText).getAsJsonObject().get("priTmplId").getAsString();
+  }
+
+  @Override
+  public List<TemplateInfo> getTemplateList() throws WxErrorException {
+    String responseText = this.wxMaService.get(TEMPLATE_LIST_URL, null);
+    return WxMaGsonBuilder.create().fromJson(new JsonParser().parse(responseText).getAsJsonObject()
+      .getAsJsonArray("data"), new TypeToken<List<TemplateInfo>>() {
+    }.getType());
+  }
+
+  @Override
+  public boolean delTemplate(String templateId) throws WxErrorException {
+    this.wxMaService.post(TEMPLATE_DEL_URL, ImmutableMap.of("priTmplId", templateId));
+    return true;
+  }
+
+  @Override
+  public List<CategoryData> getCategory() throws WxErrorException {
+    String responseText = this.wxMaService.get(GET_CATEGORY_URL, null);
+    return WxMaGsonBuilder.create().fromJson(new JsonParser().parse(responseText).getAsJsonObject()
+      .getAsJsonArray("data"), new TypeToken<List<CategoryData>>() {
+    }.getType());
+  }
+}

+ 6 - 1
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMediaAsyncCheckResult.java

@@ -2,10 +2,15 @@ package cn.binarywang.wx.miniapp.bean;
 
 import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
 import java.io.Serializable;
 
+/**
+ * @author borisbao
+ */
+@Data
 public class WxMaMediaAsyncCheckResult implements Serializable {
-
   private static final long serialVersionUID = 3928132365399916183L;
 
   /**

+ 34 - 7
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java

@@ -1,12 +1,5 @@
 package cn.binarywang.wx.miniapp.bean;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Serializable;
-import java.nio.charset.StandardCharsets;
-
-import org.apache.commons.io.IOUtils;
-
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
 import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
 import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
@@ -16,6 +9,12 @@ import com.thoughtworks.xstream.annotations.XStreamAlias;
 import com.thoughtworks.xstream.annotations.XStreamConverter;
 import lombok.Data;
 import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
+import org.apache.commons.io.IOUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
 
 /**
  * @author <a href="https://github.com/binarywang">Binary Wang</a>
@@ -108,6 +107,34 @@ public class WxMaMessage implements Serializable {
   @XStreamConverter(value = XStreamCDataConverter.class)
   private String sessionFrom;
 
+  /**
+   * 以下是异步校验图片/音频是否含有违法违规内容的异步检测结果推送报文中的参数
+   */
+  @SerializedName("isrisky")
+  @XStreamAlias("isrisky")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  private String isRisky;
+
+  @SerializedName("extra_info_json")
+  @XStreamAlias("extra_info_json")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  private String extraInfoJson;
+
+  @SerializedName("appid")
+  @XStreamAlias("appid")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  private String appid;
+
+  @SerializedName("trace_id")
+  @XStreamAlias("trace_id")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  private String traceId;
+
+  @SerializedName("status_code")
+  @XStreamAlias("status_code")
+  @XStreamConverter(value = XStreamCDataConverter.class)
+  private String statusCode;
+
   public static WxMaMessage fromXml(String xml) {
     return XStreamTransformer.fromXml(WxMaMessage.class, xml);
   }

+ 0 - 30
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeData.java

@@ -1,30 +0,0 @@
-package cn.binarywang.wx.miniapp.bean;
-
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * <pre>
- * 参考文档 https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
- * </pre>
- */
-@Data
-@NoArgsConstructor
-public class WxMaSubscribeData {
-  private String name;
-  private String value;
-  private String color;
-
-  public WxMaSubscribeData(String name, String value) {
-    this.name = name;
-    this.value = value;
-  }
-
-  public WxMaSubscribeData(String name, String value, String color) {
-    this.name = name;
-    this.value = value;
-    this.color = color;
-  }
-
-
-}

+ 25 - 3
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeMessage.java

@@ -1,5 +1,6 @@
 package cn.binarywang.wx.miniapp.bean;
 
+import cn.binarywang.wx.miniapp.constant.WxMaConstants;
 import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
 import lombok.*;
 
@@ -10,6 +11,8 @@ import java.util.List;
 /**
  * 订阅消息.
  * https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/subscribe-message/subscribeMessage.send.html
+ *
+ * @author S
  */
 @Getter
 @Setter
@@ -17,7 +20,6 @@ import java.util.List;
 @AllArgsConstructor
 @Builder
 public class WxMaSubscribeMessage implements Serializable {
-
   private static final long serialVersionUID = 6846729898251286686L;
 
   /**
@@ -58,13 +60,23 @@ public class WxMaSubscribeMessage implements Serializable {
    * 描述: 模板内容,不填则下发空模板
    * </pre>
    */
-  private List<WxMaSubscribeData> data;
+  private List<Data> data;
 
+  /**
+   * 跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
+   */
+  private String miniprogramState = WxMaConstants.MiniprogramState.FORMAL;
+
+  /**
+   * 进入小程序查看的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN
+   */
+  private String lang = WxMaConstants.MiniprogramLang.ZH_CN;
 
-  public WxMaSubscribeMessage addData(WxMaSubscribeData datum) {
+  public WxMaSubscribeMessage addData(Data datum) {
     if (this.data == null) {
       this.data = new ArrayList<>();
     }
+
     this.data.add(datum);
 
     return this;
@@ -74,4 +86,14 @@ public class WxMaSubscribeMessage implements Serializable {
     return WxMaGsonBuilder.create().toJson(this);
   }
 
+  @lombok.Data
+  @NoArgsConstructor
+  @AllArgsConstructor
+  public static class Data implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String name;
+    private String value;
+  }
+
 }

+ 76 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUpdatableMsg.java

@@ -0,0 +1,76 @@
+package cn.binarywang.wx.miniapp.bean;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 动态消息.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-02-17
+ */
+@Data
+@Accessors(chain = true)
+public class WxMaUpdatableMsg implements Serializable {
+  private static final long serialVersionUID = 6231957192034798165L;
+
+  /**
+   * 动态消息的 ID,通过 updatableMessage.createActivityId 接口获取
+   */
+  @SerializedName("activity_id")
+  private String activityId;
+
+  /**
+   * 动态消息修改后的状态
+   * 0	未开始
+   * 1	已开始
+   */
+  @SerializedName("target_state")
+  private Integer targetState;
+
+  /**
+   * 动态消息对应的模板信息
+   */
+  @SerializedName("template_info")
+  private TemplateInfo templateInfo;
+
+  @Data
+  @Accessors(chain = true)
+  public static class TemplateInfo implements Serializable {
+    private static final long serialVersionUID = -9218473401759062841L;
+
+    /**
+     * 模板中需要修改的参数
+     */
+    @SerializedName("parameter_list")
+    private List<Parameter> parameterList;
+  }
+
+  @Data
+  @Accessors(chain = true)
+  public static class Parameter implements Serializable {
+    private static final long serialVersionUID = 7444716050341038046L;
+
+    /**
+     * 要修改的参数名
+     * <pre>
+     * 合法值:
+     * member_count	target_state = 0 时必填,文字内容模板中 member_count 的值
+     * room_limit	target_state = 0 时必填,文字内容模板中 room_limit 的值
+     * path	target_state = 1 时必填,点击「进入」启动小程序时使用的路径。对于小游戏,没有页面的概念,可以用于传递查询字符串(query),如 "?foo=bar"
+     * version_type	target_state = 1 时必填,点击「进入」启动小程序时使用的版本。
+     * 有效参数值为:develop(开发版),trial(体验版),release(正式版)
+     * </pre>
+     */
+    private String name;
+
+    /**
+     * 修改后的参数值
+     */
+    private String value;
+  }
+}

+ 44 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudBatchDeleteFileResult.java

@@ -0,0 +1,44 @@
+package cn.binarywang.wx.miniapp.bean.cloud;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 文件删除结果.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-01-27
+ */
+@Data
+public class WxCloudBatchDeleteFileResult implements Serializable {
+  private static final long serialVersionUID = -1133274298839868115L;
+
+  @SerializedName("delete_list")
+  private List<FileDownloadInfo> fileList;
+
+  @Data
+  public static class FileDownloadInfo implements Serializable {
+    private static final long serialVersionUID = 5812969045277862211L;
+
+    /**
+     * fileid	string	文件ID
+     */
+    @SerializedName("fileid")
+    private String fileId;
+
+    /**
+     * status	number	状态码
+     */
+    @SerializedName("status")
+    private Integer status;
+
+    /**
+     * errmsg	string	该文件错误信息
+     */
+    @SerializedName("errmsg")
+    private String errMsg;
+  }
+}

+ 50 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudBatchDownloadFileResult.java

@@ -0,0 +1,50 @@
+package cn.binarywang.wx.miniapp.bean.cloud;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 获取文件下载链接结果.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-01-27
+ */
+@Data
+public class WxCloudBatchDownloadFileResult implements Serializable {
+  private static final long serialVersionUID = 646423661372964402L;
+
+  @SerializedName("file_list")
+  private List<FileDownloadInfo> fileList;
+
+  @Data
+  public static class FileDownloadInfo implements Serializable {
+    private static final long serialVersionUID = 5812969045277862211L;
+
+    /**
+     * fileid	string	文件ID
+     */
+    @SerializedName("fileid")
+    private String fileId;
+
+    /**
+     * download_url	string	下载链接
+     */
+    @SerializedName("download_url")
+    private String downloadUrl;
+
+    /**
+     * status	number	状态码
+     */
+    @SerializedName("status")
+    private Integer status;
+
+    /**
+     * errmsg	string	该文件错误信息
+     */
+    @SerializedName("errmsg")
+    private String errMsg;
+  }
+}

+ 47 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudCloudDatabaseMigrateQueryInfoResult.java

@@ -0,0 +1,47 @@
+package cn.binarywang.wx.miniapp.bean.cloud;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 云开发数据库迁移状态查询结果.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-01-26
+ */
+@Data
+public class WxCloudCloudDatabaseMigrateQueryInfoResult implements Serializable {
+  private static final long serialVersionUID = 2014197503355968243L;
+
+  /**
+   * status	string	导出状态
+   */
+  private String status;
+
+  /**
+   * record_success	number	导出成功记录数
+   */
+  @SerializedName("record_success")
+  private Integer recordSuccess;
+
+  /**
+   * record_fail	number	导出失败记录数
+   */
+  @SerializedName("record_fail")
+  private Integer recordFail;
+
+  /**
+   * err_msg	string	导出错误信息
+   */
+  @SerializedName("err_msg")
+  private String errMsg;
+
+  /**
+   * file_url	string	导出文件下载地址
+   */
+  @SerializedName("file_url")
+  private String fileUrl;
+
+}

+ 86 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudDatabaseCollectionGetResult.java

@@ -0,0 +1,86 @@
+package cn.binarywang.wx.miniapp.bean.cloud;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 云开发获取集合接口的结果.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-01-28
+ */
+@Data
+public class WxCloudDatabaseCollectionGetResult implements Serializable {
+  private static final long serialVersionUID = 3702855196387039823L;
+
+  /**
+   * 分页信息
+   */
+  private WxCloudDatabaseQueryResult.Pager pager;
+
+  /**
+   * 查询结果
+   */
+  private CollectionInfo[] collections;
+
+  @Data
+  public static class CollectionInfo implements Serializable {
+    private static final long serialVersionUID = -3280126948752330438L;
+
+    /**
+     * name	string	集合名
+     */
+    @SerializedName("name")
+    private String name;
+
+    /**
+     * count	number	表中文档数量
+     */
+    @SerializedName("count")
+    private Long count;
+
+    /**
+     * size	number	表的大小(即表中文档总大小),单位:字节
+     */
+    @SerializedName("size")
+    private Long size;
+
+    /**
+     * index_count	number	索引数量
+     */
+    @SerializedName("index_count")
+    private Long indexCount;
+
+    /**
+     * index_size	number	索引占用大小,单位:字节
+     */
+    @SerializedName("index_size")
+    private Long indexSize;
+  }
+
+  @Data
+  public static class Pager implements Serializable {
+    private static final long serialVersionUID = 5045727687673687839L;
+
+    /**
+     * Offset	number	偏移
+     */
+    @SerializedName("Offset")
+    private Long offset;
+
+    /**
+     * Limit	number	单次查询限制
+     */
+    @SerializedName("Limit")
+    private Long limit;
+
+    /**
+     * Total	number	符合查询条件的记录总数
+     */
+    @SerializedName("Total")
+    private Long total;
+
+  }
+}

+ 59 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudDatabaseCreateIndexRequest.java

@@ -0,0 +1,59 @@
+package cn.binarywang.wx.miniapp.bean.cloud;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 云开发新增索引的请求对象.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-01-26
+ */
+@Accessors(chain = true)
+@Data
+public class WxCloudDatabaseCreateIndexRequest implements Serializable {
+  private static final long serialVersionUID = -8308393731157121109L;
+
+  /**
+   * name	string		是	索引名
+   */
+  private String name;
+
+  /**
+   * unique	boolean		是	是否唯一
+   */
+  private boolean unique;
+
+  /**
+   * keys	Array.<Object>		是	索引字段
+   */
+  private List<IndexKey> keys;
+
+  @Data
+  @Accessors(chain = true)
+  public static class IndexKey implements Serializable {
+    private static final long serialVersionUID = -252641130547960325L;
+
+    /**
+     * name	string		是	字段名
+     */
+    private String name;
+
+    /**
+     * direction	string		是	字段排序
+     * <pre>
+     *   direction 的合法值
+     * 值	说明
+     * "1"	升序
+     * "-1"	降序
+     * "2dsphere"	地理位置
+     *
+     * </pre>
+     */
+    private String direction;
+  }
+}

+ 51 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudDatabaseQueryResult.java

@@ -0,0 +1,51 @@
+package cn.binarywang.wx.miniapp.bean.cloud;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 云开发数据库查询记录接口请求结果.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-01-26
+ */
+@Data
+public class WxCloudDatabaseQueryResult implements Serializable {
+  private static final long serialVersionUID = 8291820029137872536L;
+
+  /**
+   * 分页信息
+   */
+  private Pager pager;
+
+  /**
+   * 查询结果
+   */
+  private String[] data;
+
+  @Data
+  public static class Pager implements Serializable{
+    private static final long serialVersionUID = 8556239063823985674L;
+
+    /**
+     * Offset	number	偏移
+     */
+    @SerializedName("Offset")
+    private Long offset;
+
+    /**
+     * Limit	number	单次查询限制
+     */
+    @SerializedName("Limit")
+    private Long limit;
+
+    /**
+     * Total	number	符合查询条件的记录总数
+     */
+    @SerializedName("Total")
+    private Long total;
+
+  }
+}

+ 32 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudDatabaseUpdateResult.java

@@ -0,0 +1,32 @@
+package cn.binarywang.wx.miniapp.bean.cloud;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 云开发数据库更新记录接口请求结果.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-01-26
+ */
+@Data
+public class WxCloudDatabaseUpdateResult implements Serializable {
+  private static final long serialVersionUID = -3905429931117999004L;
+
+  /**
+   * matched	number	更新条件匹配到的结果数
+   */
+  private Long matched;
+
+  /**
+   * modified	number	修改的记录数,注意:使用set操作新插入的数据不计入修改数目
+   */
+  private Long modified;
+
+  /**
+   * id	string	新插入记录的id,注意:只有使用set操作新插入数据时这个字段会有值
+   */
+  private String id;
+
+}

+ 41 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudGetQcloudTokenResult.java

@@ -0,0 +1,41 @@
+package cn.binarywang.wx.miniapp.bean.cloud;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 获取腾讯云API调用凭证结果.
+ *
+ * @author <a href="https://github.com/binarywang">Binary Wang</a>
+ * @date 2020-01-27
+ */
+@Data
+public class WxCloudGetQcloudTokenResult implements Serializable {
+  private static final long serialVersionUID = -1505786395531138286L;
+
+  /**
+   * secretid
+   */
+  @SerializedName("secretid")
+  private String secretId;
+
+  /**
+   * secretkey
+   */
+  @SerializedName("secretkey")
+  private String secretKey;
+
+  /**
+   * token
+   */
+  @SerializedName("token")
+  private String token;
+
+  /**
+   * 过期时间戳
+   */
+  @SerializedName("expired_time")
+  private Long expiredTime;
+}

+ 0 - 0
weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/cloud/WxCloudUploadFileResult.java


Some files were not shown because too many files changed in this diff