diff --git a/pom.xml b/pom.xml index 93b5df6b87..b24ce0b77f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 4.0.0 com.github.binarywang wx-java - 3.7.0 + 3.8.0 pom WxJava - Weixin/Wechat Java SDK 微信开发Java SDK @@ -116,7 +116,7 @@ UTF-8 4.5 - 9.4.17.v20190418 + 9.4.28.v20200408 @@ -130,13 +130,13 @@ org.jodd jodd-http - 3.7.1 + 5.1.4 provided com.squareup.okhttp3 okhttp - 3.7.0 + 4.5.0 provided @@ -163,7 +163,7 @@ org.apache.commons commons-lang3 - 3.5 + 3.10 org.slf4j @@ -191,31 +191,31 @@ joda-time joda-time - 2.9.7 + 2.10.6 test ch.qos.logback logback-classic - 1.1.11 + 1.2.3 test com.google.inject guice - 3.0 + 4.2.3 test org.testng testng - 6.10 + 7.1.0 test org.mockito mockito-all - 1.9.5 + 1.10.19 test @@ -243,10 +243,24 @@ 2.9.0 provided + + com.github.jedis-lock + jedis-lock + 1.0.0 + provided + org.redisson redisson 3.12.0 + true + provided + + + org.springframework.data + spring-data-redis + 1.8.23.RELEASE + true provided diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index ecf51716dd..264c91d69d 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 3.7.0 + 3.8.0 pom wx-java-spring-boot-starters diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md index fe75ef4e3e..e58dfc8c84 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md @@ -1,26 +1,35 @@ -# 使用说明 -1. 在自己的Spring Boot项目里,引入maven依赖 -```xml +# wx-java-miniapp-spring-boot-starter +## 快速开始 +1. 引入依赖 + ```xml com.github.binarywang wx-java-miniapp-spring-boot-starter ${version} - ``` -2. 添加配置(application.yml) -```yml -wx: - miniapp: - appid: 111 - secret: 111 - token: 111 - aesKey: 111 - msgDataFormat: JSON -``` - - - - - - + ``` +2. 添加配置(application.properties) + ```properties + # 公众号配置(必填) + wx.miniapp.appid = appId + wx.miniapp.secret = @secret + wx.miniapp.token = @token + wx.miniapp.aesKey = @aesKey + wx.miniapp.msgDataFormat = @msgDataFormat # 消息格式,XML或者JSON. + # 存储配置redis(可选) + # 注意: 指定redis.host值后不会使用容器注入的redis连接(JedisPool) + wx.miniapp.config-storage.type = jedis # 配置类型: memory(默认), jedis, redistemplate + wx.miniapp.config-storage.key-prefix = wa # 相关redis前缀配置: wa(默认) + wx.miniapp.config-storage.redis.host = 127.0.0.1 + wx.miniapp.config-storage.redis.port = 6379 + # http客户端配置 + wx.miniapp.config-storage.http-client-type=httpclient # http客户端类型: httpclient(默认) + wx.miniapp.config-storage.http-proxy-host= + wx.miniapp.config-storage.http-proxy-port= + wx.miniapp.config-storage.http-proxy-username= + wx.miniapp.config-storage.http-proxy-password= + ``` +3. 自动注入的类型 +- `WxMaService` +- `WxMaConfig` diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml index 899b7bb2e0..f710eb1379 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 3.7.0 + 3.8.0 4.0.0 @@ -19,6 +19,17 @@ weixin-java-miniapp ${project.version} + + redis.clients + jedis + provided + + + org.springframework.data + spring-data-redis + ${spring.boot.version} + provided + diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java index 109b398d13..1c6d7865f3 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/config/WxMaAutoConfiguration.java @@ -4,15 +4,23 @@ import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; import cn.binarywang.wx.miniapp.config.WxMaConfig; import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; +import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; import lombok.AllArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; import org.apache.commons.lang3.StringUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolConfig; /** * 自动配置. @@ -26,7 +34,9 @@ @EnableConfigurationProperties(WxMaProperties.class) @ConditionalOnProperty(prefix = "wx.miniapp", value = "enabled", matchIfMissing = true) public class WxMaAutoConfiguration { - private WxMaProperties properties; + + private final WxMaProperties wxMaProperties; + private final ApplicationContext applicationContext; /** * 小程序service. @@ -35,16 +45,87 @@ public class WxMaAutoConfiguration { */ @Bean @ConditionalOnMissingBean(WxMaService.class) - public WxMaService service() { - WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); - config.setAppid(StringUtils.trimToNull(this.properties.getAppid())); - config.setSecret(StringUtils.trimToNull(this.properties.getSecret())); - config.setToken(StringUtils.trimToNull(this.properties.getToken())); - config.setAesKey(StringUtils.trimToNull(this.properties.getAesKey())); - config.setMsgDataFormat(StringUtils.trimToNull(this.properties.getMsgDataFormat())); - + public WxMaService service(WxMaConfig wxMaConfig) { final WxMaServiceImpl service = new WxMaServiceImpl(); - service.setWxMaConfig(config); + service.setWxMaConfig(wxMaConfig); return service; } + + @Bean + @ConditionalOnMissingBean(WxMaConfig.class) + public WxMaConfig wxMaConfig() { + WxMaProperties.StorageType type = wxMaProperties.getConfigStorage().getType(); + WxMaDefaultConfigImpl config; + if (type == WxMaProperties.StorageType.jedis) { + config = wxMaInJedisConfigStorage(); + } else if (type == WxMaProperties.StorageType.redistemplate) { + config = wxMaInRedisTemplateConfigStorage(); + } else { + config = wxMaInMemoryConfigStorage(); + } + + config.setAppid(StringUtils.trimToNull(this.wxMaProperties.getAppid())); + config.setSecret(StringUtils.trimToNull(this.wxMaProperties.getSecret())); + config.setToken(StringUtils.trimToNull(this.wxMaProperties.getToken())); + config.setAesKey(StringUtils.trimToNull(this.wxMaProperties.getAesKey())); + config.setMsgDataFormat(StringUtils.trimToNull(this.wxMaProperties.getMsgDataFormat())); + + WxMaProperties.ConfigStorage configStorageProperties = wxMaProperties.getConfigStorage(); + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } + return config; + } + + private WxMaDefaultConfigImpl wxMaInMemoryConfigStorage() { + WxMaDefaultConfigImpl config = new WxMaDefaultConfigImpl(); + return config; + } + + private WxMaDefaultConfigImpl wxMaInJedisConfigStorage() { + WxMaProperties.RedisProperties redisProperties = wxMaProperties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); + } + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + WxMaRedisBetterConfigImpl wxMaRedisConfig = new WxMaRedisBetterConfigImpl(redisOps, wxMaProperties.getConfigStorage().getKeyPrefix()); + return wxMaRedisConfig; + } + + private WxMaDefaultConfigImpl wxMaInRedisTemplateConfigStorage() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); + WxMaRedisBetterConfigImpl wxMaRedisConfig = new WxMaRedisBetterConfigImpl(redisOps, wxMaProperties.getConfigStorage().getKeyPrefix()); + return wxMaRedisConfig; + } + + private JedisPool getJedisPool() { + WxMaProperties.ConfigStorage storage = wxMaProperties.getConfigStorage(); + WxMaProperties.RedisProperties redis = storage.getRedis(); + + JedisPoolConfig config = new JedisPoolConfig(); + if (redis.getMaxActive() != null) { + config.setMaxTotal(redis.getMaxActive()); + } + if (redis.getMaxIdle() != null) { + config.setMaxIdle(redis.getMaxIdle()); + } + if (redis.getMaxWaitMillis() != null) { + config.setMaxWaitMillis(redis.getMaxWaitMillis()); + } + if (redis.getMinIdle() != null) { + config.setMinIdle(redis.getMinIdle()); + } + config.setTestOnBorrow(true); + config.setTestWhileIdle(true); + + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), + redis.getDatabase()); + } } diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java index 6477e61fa2..5af54dfe1b 100644 --- a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/WxMaProperties.java @@ -3,6 +3,8 @@ import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; +import java.io.Serializable; + /** * 属性配置类. * @@ -36,4 +38,112 @@ public class WxMaProperties { * 消息格式,XML或者JSON. */ private String msgDataFormat; + + /** + * 存储策略 + */ + private ConfigStorage configStorage = new ConfigStorage(); + + @Data + public static class ConfigStorage implements Serializable { + private static final long serialVersionUID = 4815731027000065434L; + + /** + * 存储类型. + */ + private StorageType type = StorageType.memory; + + /** + * 指定key前缀. + */ + private String keyPrefix = "wa"; + + /** + * redis连接配置. + */ + private RedisProperties redis; + + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.httpclient; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + + } + + public enum StorageType { + /** + * 内存. + */ + memory, + /** + * redis(JedisClient). + */ + jedis, + /** + * redis(RedisTemplate). + */ + redistemplate + } + + public enum HttpClientType { + /** + * HttpClient. + */ + httpclient + } + + @Data + public static class RedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; + } } diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md b/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md index ec7f343c62..65cc5bbbf1 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md @@ -1,4 +1,4 @@ -# wx-java-mp-starter +# wx-java-mp-spring-boot-starter ## 快速开始 1. 引入依赖 ```xml @@ -11,18 +11,25 @@ 2. 添加配置(application.properties) ```properties # 公众号配置(必填) - wx.mp.appId = @appId - wx.mp.secret = @secret - wx.mp.token = @token - wx.mp.aesKey = @aesKey - # 存储配置redis(可选) - wx.mp.config-storage.type = redis - wx.mp.config-storage.redis.host = 127.0.0.1 - wx.mp.config-storage.redis.port = 6379 + wx.mp.appId = appId + wx.mp.secret = @secret + wx.mp.token = @token + wx.mp.aesKey = @aesKey + # 存储配置redis(可选) + wx.mp.config-storage.type = redis # 配置类型: memory(默认), redis, jedis, redistemplate + wx.mp.config-storage.key-prefix = wx # 相关redis前缀配置: wx(默认) + wx.mp.config-storage.redis.host = 127.0.0.1 + wx.mp.config-storage.redis.port = 6379 + # http客户端配置 + wx.mp.config-storage.http-client-type=httpclient # http客户端类型: httpclient(默认), okhttp, joddhttp + wx.mp.config-storage.http-proxy-host= + wx.mp.config-storage.http-proxy-port= + wx.mp.config-storage.http-proxy-username= + wx.mp.config-storage.http-proxy-password= ``` -3. 支持自动注入的类型 - -`WxMpService`以及相关的服务类, 比如: `wxMpService.getXxxService`。 +3. 自动注入的类型 +- `WxMpService`以及~~相关的服务类, 比如: `wxMpService.getXxxService`。~~ +- `WxMpConfigStorage` diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml index 9053e72ba7..e8d0e14832 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 3.7.0 + 3.8.0 4.0.0 @@ -25,9 +25,10 @@ compile - org.redisson - redisson - compile + org.springframework.data + spring-data-redis + ${spring.boot.version} + provided diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java index 7d96226733..17d1083ed6 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpServiceAutoConfiguration.java @@ -1,7 +1,11 @@ package com.binarywang.spring.starter.wxjava.mp.config; +import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; import me.chanjar.weixin.mp.api.*; +import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; +import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl; +import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl; import me.chanjar.weixin.mp.config.WxMpConfigStorage; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; @@ -17,113 +21,161 @@ public class WxMpServiceAutoConfiguration { @Bean @ConditionalOnMissingBean - public WxMpService wxMpService(WxMpConfigStorage configStorage) { - WxMpService wxMpService = new WxMpServiceImpl(); + public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpProperties wxMpProperties) { + WxMpProperties.HttpClientType httpClientType = wxMpProperties.getConfigStorage().getHttpClientType(); + WxMpService wxMpService; + if (httpClientType == WxMpProperties.HttpClientType.okhttp) { + wxMpService = newWxMpServiceJoddHttpImpl(); + } else if (httpClientType == WxMpProperties.HttpClientType.joddhttp) { + wxMpService = newWxMpServiceOkHttpImpl(); + } else if (httpClientType == WxMpProperties.HttpClientType.httpclient) { + wxMpService = newWxMpServiceHttpClientImpl(); + } else { + wxMpService = newWxMpServiceImpl(); + } + wxMpService.setWxMpConfigStorage(configStorage); return wxMpService; } + private WxMpService newWxMpServiceImpl() { + return new WxMpServiceImpl(); + } + + private WxMpService newWxMpServiceHttpClientImpl() { + return new WxMpServiceHttpClientImpl(); + } + + private WxMpService newWxMpServiceOkHttpImpl() { + return new WxMpServiceOkHttpImpl(); + } + + private WxMpService newWxMpServiceJoddHttpImpl() { + return new WxMpServiceJoddHttpImpl(); + } + @Bean + @Deprecated public WxMpKefuService wxMpKefuService(WxMpService wxMpService) { return wxMpService.getKefuService(); } @Bean + @Deprecated public WxMpMaterialService wxMpMaterialService(WxMpService wxMpService) { return wxMpService.getMaterialService(); } @Bean + @Deprecated public WxMpMenuService wxMpMenuService(WxMpService wxMpService) { return wxMpService.getMenuService(); } @Bean + @Deprecated public WxMpUserService wxMpUserService(WxMpService wxMpService) { return wxMpService.getUserService(); } @Bean + @Deprecated public WxMpUserTagService wxMpUserTagService(WxMpService wxMpService) { return wxMpService.getUserTagService(); } @Bean + @Deprecated public WxMpQrcodeService wxMpQrcodeService(WxMpService wxMpService) { return wxMpService.getQrcodeService(); } @Bean + @Deprecated public WxMpCardService wxMpCardService(WxMpService wxMpService) { return wxMpService.getCardService(); } @Bean + @Deprecated public WxMpDataCubeService wxMpDataCubeService(WxMpService wxMpService) { return wxMpService.getDataCubeService(); } @Bean + @Deprecated public WxMpUserBlacklistService wxMpUserBlacklistService(WxMpService wxMpService) { return wxMpService.getBlackListService(); } @Bean + @Deprecated public WxMpStoreService wxMpStoreService(WxMpService wxMpService) { return wxMpService.getStoreService(); } @Bean + @Deprecated public WxMpTemplateMsgService wxMpTemplateMsgService(WxMpService wxMpService) { return wxMpService.getTemplateMsgService(); } @Bean + @Deprecated public WxMpSubscribeMsgService wxMpSubscribeMsgService(WxMpService wxMpService) { return wxMpService.getSubscribeMsgService(); } @Bean + @Deprecated public WxMpDeviceService wxMpDeviceService(WxMpService wxMpService) { return wxMpService.getDeviceService(); } @Bean + @Deprecated public WxMpShakeService wxMpShakeService(WxMpService wxMpService) { return wxMpService.getShakeService(); } @Bean + @Deprecated public WxMpMemberCardService wxMpMemberCardService(WxMpService wxMpService) { return wxMpService.getMemberCardService(); } @Bean + @Deprecated public WxMpMassMessageService wxMpMassMessageService(WxMpService wxMpService) { return wxMpService.getMassMessageService(); } @Bean + @Deprecated public WxMpAiOpenService wxMpAiOpenService(WxMpService wxMpService) { return wxMpService.getAiOpenService(); } @Bean + @Deprecated public WxMpWifiService wxMpWifiService(WxMpService wxMpService) { return wxMpService.getWifiService(); } @Bean + @Deprecated public WxMpMarketingService wxMpMarketingService(WxMpService wxMpService) { return wxMpService.getMarketingService(); } @Bean + @Deprecated public WxMpCommentService wxMpCommentService(WxMpService wxMpService) { return wxMpService.getCommentService(); } @Bean + @Deprecated public WxMpOcrService wxMpOcrService(WxMpService wxMpService) { return wxMpService.getOcrService(); } diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java index 4ddff5a72a..fc15e7605e 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/config/WxMpStorageAutoConfiguration.java @@ -1,16 +1,20 @@ package com.binarywang.spring.starter.wxjava.mp.config; -import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties; import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; 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.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; @@ -22,52 +26,78 @@ @Configuration @RequiredArgsConstructor public class WxMpStorageAutoConfiguration { - private final WxMpProperties properties; - @Autowired(required = false) - private JedisPool jedisPool; + private final ApplicationContext applicationContext; - @Autowired(required = false) - private RedissonClient redissonClient; + private final WxMpProperties wxMpProperties; + + @Value("${wx.mp.config-storage.redis.host:") + private String redisHost; + + @Value("${wx.mp.configStorage.redis.host:") + private String redisHost2; @Bean @ConditionalOnMissingBean(WxMpConfigStorage.class) - public WxMpConfigStorage wxMpInMemoryConfigStorage() { - WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); - WxMpProperties.StorageType type = storage.getType(); - - if (type == WxMpProperties.StorageType.redis) { - return getWxMpInRedisConfigStorage(); + public WxMpConfigStorage wxMpConfigStorage() { + WxMpProperties.StorageType type = wxMpProperties.getConfigStorage().getType(); + WxMpConfigStorage config; + if (type == WxMpProperties.StorageType.redis || type == WxMpProperties.StorageType.jedis) { + config = wxMpInJedisConfigStorage(); + } else if (type == WxMpProperties.StorageType.redistemplate) { + config = wxMpInRedisTemplateConfigStorage(); + } else { + config = wxMpInMemoryConfigStorage(); } - return getWxMpInMemoryConfigStorage(); + return config; } - private WxMpDefaultConfigImpl getWxMpInMemoryConfigStorage() { + private WxMpConfigStorage wxMpInMemoryConfigStorage() { WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); setWxMpInfo(config); return config; } - private WxMpRedisConfigImpl getWxMpInRedisConfigStorage() { - JedisPool poolToUse = jedisPool; - if (poolToUse == null) { - poolToUse = getJedisPool(); + private WxMpConfigStorage wxMpInJedisConfigStorage() { + JedisPool jedisPool; + if (StringUtils.isNotEmpty(redisHost) || StringUtils.isNotEmpty(redisHost2)) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); } - WxMpRedisConfigImpl config = new WxMpRedisConfigImpl(poolToUse); - setWxMpInfo(config); - return config; + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps, wxMpProperties.getConfigStorage().getKeyPrefix()); + setWxMpInfo(wxMpRedisConfig); + return wxMpRedisConfig; + } + + private WxMpConfigStorage wxMpInRedisTemplateConfigStorage() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); + WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps, wxMpProperties.getConfigStorage().getKeyPrefix()); + setWxMpInfo(wxMpRedisConfig); + return wxMpRedisConfig; } private void setWxMpInfo(WxMpDefaultConfigImpl config) { + WxMpProperties properties = wxMpProperties; + WxMpProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); config.setAppId(properties.getAppId()); config.setSecret(properties.getSecret()); config.setToken(properties.getToken()); config.setAesKey(properties.getAesKey()); + + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); + } } private JedisPool getJedisPool() { - WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); - RedisProperties redis = storage.getRedis(); + WxMpProperties.ConfigStorage storage = wxMpProperties.getConfigStorage(); + WxMpProperties.RedisProperties redis = storage.getRedis(); JedisPoolConfig config = new JedisPoolConfig(); if (redis.getMaxActive() != null) { @@ -85,8 +115,7 @@ private JedisPool getJedisPool() { config.setTestOnBorrow(true); config.setTestWhileIdle(true); - JedisPool pool = new JedisPool(config, redis.getHost(), redis.getPort(), - redis.getTimeout(), redis.getPassword(), redis.getDatabase()); - return pool; + return new JedisPool(config, redis.getHost(), redis.getPort(), redis.getTimeout(), redis.getPassword(), + redis.getDatabase()); } } diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/RedisProperties.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/RedisProperties.java deleted file mode 100644 index d95e8df984..0000000000 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/RedisProperties.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.binarywang.spring.starter.wxjava.mp.properties; - -import lombok.Data; - -import java.io.Serializable; - -/** - * Redis配置. - * - * @author someone - */ -@Data -public class RedisProperties implements Serializable { - private static final long serialVersionUID = -5924815351660074401L; - - /** - * 主机地址. - */ - private String host = "127.0.0.1"; - - /** - * 端口号. - */ - private int port = 6379; - - /** - * 密码. - */ - private String password; - - /** - * 超时. - */ - private int timeout = 2000; - - /** - * 数据库. - */ - private int database = 0; - - private Integer maxActive; - private Integer maxIdle; - private Integer maxWaitMillis; - private Integer minIdle; -} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java index 0dbca9e778..60b39d9cdc 100644 --- a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/WxMpProperties.java @@ -40,19 +40,54 @@ public class WxMpProperties { private String aesKey; /** - * 存储策略, memory, redis. + * 存储策略 */ private ConfigStorage configStorage = new ConfigStorage(); - @Data public static class ConfigStorage implements Serializable { private static final long serialVersionUID = 4815731027000065434L; + /** + * 存储类型. + */ private StorageType type = memory; + /** + * 指定key前缀. + */ + private String keyPrefix = "wx"; + + /** + * redis连接配置. + */ private RedisProperties redis = new RedisProperties(); + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.httpclient; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; + } public enum StorageType { @@ -61,8 +96,67 @@ public enum StorageType { */ memory, /** - * redis. + * jedis. + */ + redis, + /** + * redis(JedisClient). */ - redis + jedis, + /** + * redis(RedisTemplate). + */ + redistemplate } + + public enum HttpClientType { + /** + * HttpClient. + */ + httpclient, + /** + * OkHttp. + */ + okhttp, + /** + * JoddHttp. + */ + joddhttp + } + + @Data + public static class RedisProperties implements Serializable { + private static final long serialVersionUID = -5924815351660074401L; + + /** + * 主机地址. + */ + private String host = "127.0.0.1"; + + /** + * 端口号. + */ + private int port = 6379; + + /** + * 密码. + */ + private String password; + + /** + * 超时. + */ + private int timeout = 2000; + + /** + * 数据库. + */ + private int database = 0; + + private Integer maxActive; + private Integer maxIdle; + private Integer maxWaitMillis; + private Integer minIdle; + } + } diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/README.md b/spring-boot-starters/wx-java-open-spring-boot-starter/README.md index fd00f03531..44333f8e4f 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/README.md +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/README.md @@ -9,26 +9,27 @@ ``` 2. 添加配置(application.properties) - ``` - # 开放平台配置(必填) - wx.open.appId = @appId - wx.open.secret = @secret - wx.open.token = @token - wx.open.aesKey = @aesKey - # 存储配置redis(可选), 优先使用(wx.open.config-storage.redis)配置的redis, 支持自定注入的JedisPool - wx.open.config-storage.type = redis # 可选值, memory(默认), redis - wx.open.config-storage.redis.host = 127.0.0.1 - wx.open.config-storage.redis.port = 6379 + ```properties + # 公众号配置(必填) + wx.open.appId = appId + wx.open.secret = @secret + wx.open.token = @token + wx.open.aesKey = @aesKey + # 存储配置redis(可选) + # 优先注入容器的(JedisPool, RedissonClient), 当配置了wx.open.config-storage.redis.host, 不会使用容器注入redis连接配置 + wx.open.config-storage.type = redis # 配置类型: memory(默认), redis(jedis), jedis, redisson, redistemplate + wx.open.config-storage.key-prefix = wx # 相关redis前缀配置: wx(默认) + wx.open.config-storage.redis.host = 127.0.0.1 + wx.open.config-storage.redis.port = 6379 + # http客户端配置 + wx.open.config-storage.http-client-type=httpclient # http客户端类型: httpclient(默认) + wx.open.config-storage.http-proxy-host= + wx.open.config-storage.http-proxy-port= + wx.open.config-storage.http-proxy-username= + wx.open.config-storage.http-proxy-password= ``` 3. 支持自动注入的类型: `WxOpenService, WxOpenMessageRouter, WxOpenComponentService` 4. 覆盖自动配置: 自定义注入的bean会覆盖自动注入的 - WxOpenConfigStorage - WxOpenService - - - - - - - diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml index 9d77c2c76a..265cef1efd 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 3.7.0 + 3.8.0 4.0.0 @@ -22,12 +22,18 @@ redis.clients jedis - compile + provided org.redisson redisson - compile + provided + + + org.springframework.data + spring-data-redis + ${spring.boot.version} + provided diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenStorageAutoConfiguration.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenStorageAutoConfiguration.java index a92b3483b9..c97f00451d 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenStorageAutoConfiguration.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/config/WxOpenStorageAutoConfiguration.java @@ -3,20 +3,23 @@ import com.binarywang.spring.starter.wxjava.open.properties.RedisProperties; import com.binarywang.spring.starter.wxjava.open.properties.WxOpenProperties; import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.redis.JedisWxRedisOps; +import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps; +import me.chanjar.weixin.common.redis.RedissonWxRedisOps; +import me.chanjar.weixin.common.redis.WxRedisOps; 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; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.core.StringRedisTemplate; import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; @@ -29,15 +32,7 @@ @RequiredArgsConstructor public class WxOpenStorageAutoConfiguration { private final WxOpenProperties properties; - - @Autowired(required = false) - private JedisPool jedisPool; - - @Autowired(required = false) - private RedissonClient redissonClient; - - @Value("${wx.open.config-storage.redis.host:}") - private String redisHost; + private final ApplicationContext applicationContext; @Bean @ConditionalOnMissingBean(WxOpenConfigStorage.class) @@ -45,43 +40,63 @@ public WxOpenConfigStorage wxOpenConfigStorage() { WxOpenProperties.ConfigStorage storage = properties.getConfigStorage(); WxOpenProperties.StorageType type = storage.getType(); - if (type == WxOpenProperties.StorageType.redis) { - return getWxOpenInRedisConfigStorage(); + WxOpenInMemoryConfigStorage config; + if (type == WxOpenProperties.StorageType.redis || type == WxOpenProperties.StorageType.jedis) { + config = getWxOpenInRedisConfigStorage(); + } else if (type == WxOpenProperties.StorageType.redisson) { + config = getWxOpenInRedissonConfigStorage(); + } else if (type == WxOpenProperties.StorageType.redistemplate) { + config = getWxOpenInRedisTemplateConfigStorage(); + } else { + config = getWxOpenInMemoryConfigStorage(); } - if (type == WxOpenProperties.StorageType.jedis) { - return getWxOpenInRedisConfigStorage(); - } - - if (type == WxOpenProperties.StorageType.redisson) { - return getWxOpenInRedissonConfigStorage(); + WxOpenProperties.ConfigStorage configStorageProperties = properties.getConfigStorage(); + config.setWxOpenInfo(properties.getAppId(), properties.getSecret(), properties.getToken(), properties.getAesKey()); + config.setHttpProxyHost(configStorageProperties.getHttpProxyHost()); + config.setHttpProxyUsername(configStorageProperties.getHttpProxyUsername()); + config.setHttpProxyPassword(configStorageProperties.getHttpProxyPassword()); + if (configStorageProperties.getHttpProxyPort() != null) { + config.setHttpProxyPort(configStorageProperties.getHttpProxyPort()); } - return getWxOpenInMemoryConfigStorage(); + return config; } private WxOpenInMemoryConfigStorage getWxOpenInMemoryConfigStorage() { WxOpenInMemoryConfigStorage config = new WxOpenInMemoryConfigStorage(); - config.setWxOpenInfo(properties.getAppId(), properties.getSecret(), properties.getToken(), properties.getAesKey()); return config; } private WxOpenInRedisConfigStorage getWxOpenInRedisConfigStorage() { - JedisPool poolToUse = jedisPool; - if (jedisPool == null || StringUtils.isNotEmpty(redisHost)) { - poolToUse = getJedisPool(); + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + JedisPool jedisPool; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + jedisPool = getJedisPool(); + } else { + jedisPool = applicationContext.getBean(JedisPool.class); } - WxOpenInRedisConfigStorage config = new WxOpenInRedisConfigStorage(poolToUse, properties.getConfigStorage().getKeyPrefix()); - config.setWxOpenInfo(properties.getAppId(), properties.getSecret(), properties.getToken(), properties.getAesKey()); + WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); + WxOpenInRedisConfigStorage config = new WxOpenInRedisConfigStorage(redisOps, properties.getConfigStorage().getKeyPrefix()); return config; } - private WxOpenInRedissonConfigStorage getWxOpenInRedissonConfigStorage() { - RedissonClient redissonClientToUse = this.redissonClient; - if (redissonClient == null) { - redissonClientToUse = getRedissonClient(); + private WxOpenInRedisConfigStorage getWxOpenInRedissonConfigStorage() { + RedisProperties redisProperties = properties.getConfigStorage().getRedis(); + RedissonClient redissonClient; + if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) { + redissonClient = getRedissonClient(); + } else { + redissonClient = applicationContext.getBean(RedissonClient.class); } - WxOpenInRedissonConfigStorage config = new WxOpenInRedissonConfigStorage(redissonClientToUse, properties.getConfigStorage().getKeyPrefix()); - config.setWxOpenInfo(properties.getAppId(), properties.getSecret(), properties.getToken(), properties.getAesKey()); + WxRedisOps redisOps = new RedissonWxRedisOps(redissonClient); + WxOpenInRedisConfigStorage config = new WxOpenInRedisConfigStorage(redisOps, properties.getConfigStorage().getKeyPrefix()); + return config; + } + + private WxOpenInRedisConfigStorage getWxOpenInRedisTemplateConfigStorage() { + StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); + WxOpenInRedisConfigStorage config = new WxOpenInRedisConfigStorage(redisOps, properties.getConfigStorage().getKeyPrefix()); return config; } diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/RedisProperties.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/RedisProperties.java index 565afa07f5..a03d3a47f6 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/RedisProperties.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/RedisProperties.java @@ -16,7 +16,7 @@ public class RedisProperties implements Serializable { /** * 主机地址. */ - private String host = "127.0.0.1"; + private String host; /** * 端口号. diff --git a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java index 77aabad54a..9c9986bacc 100644 --- a/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java +++ b/spring-boot-starters/wx-java-open-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/open/properties/WxOpenProperties.java @@ -40,7 +40,7 @@ public class WxOpenProperties { private String aesKey; /** - * 存储策略, memory, redis. + * 存储策略. */ private ConfigStorage configStorage = new ConfigStorage(); @@ -49,11 +49,45 @@ public class WxOpenProperties { public static class ConfigStorage implements Serializable { private static final long serialVersionUID = 4815731027000065434L; + /** + * 存储类型. + */ private StorageType type = memory; + /** + * 指定key前缀. + */ + private String keyPrefix = "wx"; + + /** + * redis连接配置. + */ private RedisProperties redis = new RedisProperties(); - private String keyPrefix; + /** + * http客户端类型. + */ + private HttpClientType httpClientType = HttpClientType.httpclient; + + /** + * http代理主机. + */ + private String httpProxyHost; + + /** + * http代理端口. + */ + private Integer httpProxyPort; + + /** + * http代理用户名. + */ + private String httpProxyUsername; + + /** + * http代理密码. + */ + private String httpProxyPassword; } @@ -73,6 +107,17 @@ public enum StorageType { /** * redisson. */ - redisson + redisson, + /** + * redistemplate + */ + redistemplate + } + + public enum HttpClientType { + /** + * HttpClient. + */ + httpclient } } diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml index 870f3e7c77..f49b20bb99 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/pom.xml @@ -5,7 +5,7 @@ wx-java-spring-boot-starters com.github.binarywang - 3.7.0 + 3.8.0 4.0.0 diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java index 43b2114e6e..2dd44004a6 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/config/WxPayAutoConfiguration.java @@ -49,6 +49,13 @@ public WxPayService wxPayService() { payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId())); payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId())); payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath())); + //以下是apiv3以及支付分相关 + payConfig.setServiceId(StringUtils.trimToNull(this.properties.getServiceId())); + payConfig.setPayScoreNotifyUrl(StringUtils.trimToNull(this.properties.getPayScoreNotifyUrl())); + payConfig.setPrivateKeyPath(StringUtils.trimToNull(this.properties.getPrivateKeyPath())); + payConfig.setPrivateCertPath(StringUtils.trimToNull(this.properties.getPrivateCertPath())); + payConfig.setCertSerialNo(StringUtils.trimToNull(this.properties.getCertSerialNo())); + payConfig.setApiV3Key(StringUtils.trimToNull(this.properties.getApiv3Key())); wxPayService.setConfig(payConfig); return wxPayService; diff --git a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java index fe8a215650..940cdf5916 100644 --- a/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java +++ b/spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/pay/properties/WxPayProperties.java @@ -43,4 +43,35 @@ public class WxPayProperties { * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定. */ private String keyPath; + + /** + * 微信支付分serviceId + */ + private String serviceId; + + /** + * 证书序列号 + */ + private String certSerialNo; + + /** + * apiV3秘钥 + */ + private String apiv3Key; + + /** + * 微信支付分回调地址 + */ + private String payScoreNotifyUrl; + + /** + * apiv3 商户apiclient_key.pem + */ + private String privateKeyPath; + + /** + * apiv3 商户apiclient_cert.pem + */ + private String privateCertPath; + } diff --git a/weixin-graal/pom.xml b/weixin-graal/pom.xml index 1f351580e1..104e4ffb45 100644 --- a/weixin-graal/pom.xml +++ b/weixin-graal/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 3.7.0 + 3.8.0 weixin-graal diff --git a/weixin-graal/src/main/java/cn/binarywang/wx/graal/GraalProcessor.java b/weixin-graal/src/main/java/cn/binarywang/wx/graal/GraalProcessor.java index c09190c3c5..4eaddadf2e 100644 --- a/weixin-graal/src/main/java/cn/binarywang/wx/graal/GraalProcessor.java +++ b/weixin-graal/src/main/java/cn/binarywang/wx/graal/GraalProcessor.java @@ -1,6 +1,7 @@ package cn.binarywang.wx.graal; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.RoundEnvironment; @@ -20,11 +21,14 @@ import java.util.SortedSet; import java.util.TreeSet; -// 目前仅仅处理@Data,且必须在lombok自己的processor之前执行,千万注意!!!!! +/** + * 目前仅仅处理@Data,且必须在lombok自己的processor之前执行,千万注意!!!!! + * + * @author outersky + */ @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"; @@ -34,16 +38,19 @@ public class GraalProcessor extends AbstractProcessor { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { for (TypeElement annotatedClass : ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Data.class))) { - registerClass(annotatedClass.getQualifiedName().toString()); handleSuperClass(annotatedClass); } //只有最后一轮才可以写文件,否则文件会被重复打开,报错! - if (!roundEnv.processingOver()) return false; + if (!roundEnv.processingOver()) { + return false; + } // 如果没有文件要写,跳过 - if (classSet.isEmpty()) return false; + if (classSet.isEmpty()) { + return false; + } writeFiles(); @@ -72,7 +79,9 @@ private void setShortestPackageName(String packageName) { */ private String getPackageName(String fullClassName) { int last = fullClassName.lastIndexOf('.'); - if (last == -1) return fullClassName; + if (last == -1) { + return fullClassName; + } return fullClassName.substring(0, last); } @@ -98,29 +107,29 @@ private void writeFiles() { 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(); + try (Writer writer = fileObject.openWriter();) { + writer.append("Args = -H:ReflectionConfigurationResources=${.}/" + REFLECTION_CONFIG_JSON); + } } 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(","); + try (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(assetGraalJsonElement(name)); - writer.append('\n'); + writer.write("]"); } - writer.write("]"); - writer.close(); } catch (IOException e) { e.printStackTrace(); } @@ -158,7 +167,9 @@ private void handleSuperClass(TypeElement typeElement) { TypeElement s = (TypeElement) ((DeclaredType) superclass).asElement(); String sName = s.toString(); // ignore java.**/javax.** - if (sName.startsWith("java.") || sName.startsWith("javax.")) return; + if (sName.startsWith("java.") || sName.startsWith("javax.")) { + return; + } registerClass(sName); handleSuperClass(s); } diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index 0620e6ddfa..4d05d5c3f7 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 3.7.0 + 3.8.0 weixin-java-common @@ -118,6 +118,22 @@ dom4j 2.1.1 + + redis.clients + jedis + + + com.github.jedis-lock + jedis-lock + + + org.redisson + redisson + + + org.springframework.data + spring-data-redis + diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/TicketType.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/enums/TicketType.java similarity index 70% rename from weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/TicketType.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/enums/TicketType.java index 02de06f6d1..95bcffe857 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/TicketType.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/enums/TicketType.java @@ -1,6 +1,7 @@ -package me.chanjar.weixin.mp.enums; +package me.chanjar.weixin.common.enums; import lombok.Getter; +import lombok.RequiredArgsConstructor; /** *
@@ -11,6 +12,7 @@
  * @author Binary Wang
  */
 @Getter
+@RequiredArgsConstructor
 public enum TicketType {
   /**
    * jsapi
@@ -19,17 +21,15 @@ public enum TicketType {
   /**
    * sdk
    */
-  SDK("2"),
+  SDK("sdk"),
   /**
    * 微信卡券
    */
   WX_CARD("wx_card");
+
   /**
    * type代码
    */
-  private String code;
+  private final String code;
 
-  TicketType(String code) {
-    this.code = code;
-  }
 }
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java
index 5529d69759..4c3dbb7cf1 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxCpErrorMsgEnum.java
@@ -5,7 +5,7 @@
 /**
  * 
  * 企业微信全局错误码.
- * 参考文档:企业微信全局错误码
+ * 参考文档:企业微信全局错误码
  * Created by Binary Wang on 2018/5/13.
  * 
* @@ -609,6 +609,322 @@ public enum WxCpErrorMsgEnum { * 不符合的state参数;必须是[a-zA-Z0-9]的参数值,长度不可超过128个字节. */ CODE_84025(84025, "不符合的state参数;必须是[a-zA-Z0-9]的参数值,长度不可超过128个字节"), + /** + * 缺少caller参数. + */ + CODE_84052(84052, "缺少caller参数"), + /** + * 缺少callee参数. + */ + CODE_84053(84053, "缺少callee参数"), + /** + * 缺少auth_corpid参数. + */ + CODE_84054(84054, "缺少auth_corpid参数"), + /** + * 超过拨打公费电话频率。排查方法:同一个客服5秒内只能调用api拨打一次公费电话 + */ + CODE_84055(84055, "超过拨打公费电话频率。排查方法:同一个客服5秒内只能调用api拨打一次公费电话"), + /** + * 被拨打用户安装应用时未授权拨打公费电话权限. + */ + CODE_84056(84056, "被拨打用户安装应用时未授权拨打公费电话权限"), + /** + * 公费电话余额不足. + */ + CODE_84057(84057, "公费电话余额不足"), + /** + * caller + */ + CODE_84058(84058, "caller 呼叫号码不支持"), + /** + * 号码非法. + */ + CODE_84059(84059, "号码非法"), + /** + * callee + */ + CODE_84060(84060, "callee 呼叫号码不支持"), + /** + * 不存在外部联系人的关系. + */ + CODE_84061(84061, "不存在外部联系人的关系"), + /** + * 未开启公费电话应用. + */ + CODE_84062(84062, "未开启公费电话应用"), + /** + * caller不存在. + */ + CODE_84063(84063, "caller不存在"), + /** + * callee不存在. + */ + CODE_84064(84064, "callee不存在"), + /** + * caller跟callee电话号码一致。排查方法:不允许自己拨打给自己 + */ + CODE_84065(84065, "caller跟callee电话号码一致。排查方法:不允许自己拨打给自己"), + /** + * 服务商拨打次数超过限制。排查方法:单个企业管理员,在一天(以上午10 + */ + CODE_84066(84066, "服务商拨打次数超过限制。排查方法:单个企业管理员,在一天(以上午10:00为起始时间)内,对应单个服务商,只能被呼叫【4】次。"), + /** + * 管理员收到的服务商公费电话个数超过限制。排查方法:单个企业管理员,在一天(以上午10 + */ + CODE_84067(84067, "管理员收到的服务商公费电话个数超过限制。排查方法:单个企业管理员,在一天(以上午10:00为起始时间)内,一共只能被【3】个服务商成功呼叫。"), + /** + * 拨打方被限制拨打公费电话. + */ + CODE_84069(84069, "拨打方被限制拨打公费电话"), + /** + * 不支持的电话号码。排查方法:拨打方或者被拨打方电话号码不支持 + */ + CODE_84070(84070, "不支持的电话号码。排查方法:拨打方或者被拨打方电话号码不支持"), + /** + * 不合法的外部联系人授权码。排查方法:非法或者已经消费过 + */ + CODE_84071(84071, "不合法的外部联系人授权码。排查方法:非法或者已经消费过"), + /** + * 应用未配置客服. + */ + CODE_84072(84072, "应用未配置客服"), + /** + * 客服userid不在应用配置的客服列表中. + */ + CODE_84073(84073, "客服userid不在应用配置的客服列表中"), + /** + * 没有外部联系人权限. + */ + CODE_84074(84074, "没有外部联系人权限"), + /** + * 不合法或过期的authcode. + */ + CODE_84075(84075, "不合法或过期的authcode"), + /** + * 缺失authcode. + */ + CODE_84076(84076, "缺失authcode"), + /** + * 订单价格过高,无法受理. + */ + CODE_84077(84077, "订单价格过高,无法受理"), + /** + * 购买人数不正确. + */ + CODE_84078(84078, "购买人数不正确"), + /** + * 价格策略不存在. + */ + CODE_84079(84079, "价格策略不存在"), + /** + * 订单不存在. + */ + CODE_84080(84080, "订单不存在"), + /** + * 存在未支付订单. + */ + CODE_84081(84081, "存在未支付订单"), + /** + * 存在申请退款中的订单. + */ + CODE_84082(84082, "存在申请退款中的订单"), + /** + * 非服务人员. + */ + CODE_84083(84083, "非服务人员"), + /** + * 非跟进用户. + */ + CODE_84084(84084, "非跟进用户"), + /** + * 应用已下架. + */ + CODE_84085(84085, "应用已下架"), + /** + * 订单人数超过可购买最大人数. + */ + CODE_84086(84086, "订单人数超过可购买最大人数"), + /** + * 打开订单支付前禁止关闭订单. + */ + CODE_84087(84087, "打开订单支付前禁止关闭订单"), + /** + * 禁止关闭已支付的订单. + */ + CODE_84088(84088, "禁止关闭已支付的订单"), + /** + * 订单已支付. + */ + CODE_84089(84089, "订单已支付"), + /** + * 缺失user_ticket. + */ + CODE_84090(84090, "缺失user_ticket"), + /** + * 订单价格不可低于下限. + */ + CODE_84091(84091, "订单价格不可低于下限"), + /** + * 无法发起代下单操作. + */ + CODE_84092(84092, "无法发起代下单操作"), + /** + * 代理关系已占用,无法代下单. + */ + CODE_84093(84093, "代理关系已占用,无法代下单"), + /** + * 该应用未配置代理分润规则,请先联系应用服务商处理. + */ + CODE_84094(84094, "该应用未配置代理分润规则,请先联系应用服务商处理"), + /** + * 免费试用版,无法扩容. + */ + CODE_84095(84095, "免费试用版,无法扩容"), + /** + * 免费试用版,无法续期. + */ + CODE_84096(84096, "免费试用版,无法续期"), + /** + * 当前企业有未处理订单. + */ + CODE_84097(84097, "当前企业有未处理订单"), + /** + * 固定总量,无法扩容. + */ + CODE_84098(84098, "固定总量,无法扩容"), + /** + * 非购买状态,无法扩容. + */ + CODE_84099(84099, "非购买状态,无法扩容"), + /** + * 未购买过此应用,无法续期. + */ + CODE_84100(84100, "未购买过此应用,无法续期"), + /** + * 企业已试用付费版本,无法全新购买. + */ + CODE_84101(84101, "企业已试用付费版本,无法全新购买"), + /** + * 企业当前应用状态已过期,无法扩容. + */ + CODE_84102(84102, "企业当前应用状态已过期,无法扩容"), + /** + * 仅可修改未支付订单. + */ + CODE_84103(84103, "仅可修改未支付订单"), + /** + * 订单已支付,无法修改. + */ + CODE_84104(84104, "订单已支付,无法修改"), + /** + * 订单已被取消,无法修改. + */ + CODE_84105(84105, "订单已被取消,无法修改"), + /** + * 企业含有该应用的待支付订单,无法代下单. + */ + CODE_84106(84106, "企业含有该应用的待支付订单,无法代下单"), + /** + * 企业含有该应用的退款中订单,无法代下单. + */ + CODE_84107(84107, "企业含有该应用的退款中订单,无法代下单"), + /** + * 企业含有该应用的待生效订单,无法代下单. + */ + CODE_84108(84108, "企业含有该应用的待生效订单,无法代下单"), + /** + * 订单定价不能未0. + */ + CODE_84109(84109, "订单定价不能未0"), + /** + * 新安装应用不在试用状态,无法升级为付费版. + */ + CODE_84110(84110, "新安装应用不在试用状态,无法升级为付费版"), + /** + * 无足够可用优惠券. + */ + CODE_84111(84111, "无足够可用优惠券"), + /** + * 无法关闭未支付订单. + */ + CODE_84112(84112, "无法关闭未支付订单"), + /** + * 无付费信息. + */ + CODE_84113(84113, "无付费信息"), + /** + * 虚拟版本不支持下单. + */ + CODE_84114(84114, "虚拟版本不支持下单"), + /** + * 虚拟版本不支持扩容. + */ + CODE_84115(84115, "虚拟版本不支持扩容"), + /** + * 虚拟版本不支持续期. + */ + CODE_84116(84116, "虚拟版本不支持续期"), + /** + * 在虚拟正式版期内不能扩容. + */ + CODE_84117(84117, "在虚拟正式版期内不能扩容"), + /** + * 虚拟正式版期内不能变更版本. + */ + CODE_84118(84118, "虚拟正式版期内不能变更版本"), + /** + * 当前企业未报备,无法进行代下单. + */ + CODE_84119(84119, "当前企业未报备,无法进行代下单"), + /** + * 当前应用版本已删除. + */ + CODE_84120(84120, "当前应用版本已删除"), + /** + * 应用版本已删除,无法扩容. + */ + CODE_84121(84121, "应用版本已删除,无法扩容"), + /** + * 应用版本已删除,无法续期. + */ + CODE_84122(84122, "应用版本已删除,无法续期"), + /** + * 非虚拟版本,无法升级. + */ + CODE_84123(84123, "非虚拟版本,无法升级"), + /** + * 非行业方案订单,不能添加部分应用版本的订单. + */ + CODE_84124(84124, "非行业方案订单,不能添加部分应用版本的订单"), + /** + * 购买人数不能少于最少购买人数. + */ + CODE_84125(84125, "购买人数不能少于最少购买人数"), + /** + * 购买人数不能多于最大购买人数. + */ + CODE_84126(84126, "购买人数不能多于最大购买人数"), + /** + * 无应用管理权限. + */ + CODE_84127(84127, "无应用管理权限"), + /** + * 无该行业方案下全部应用的管理权限. + */ + CODE_84128(84128, "无该行业方案下全部应用的管理权限"), + /** + * 付费策略已被删除,无法下单. + */ + CODE_84129(84129, "付费策略已被删除,无法下单"), + /** + * 订单生效时间不合法. + */ + CODE_84130(84130, "订单生效时间不合法"), + /** + * 文件转译解析错误。排查方法:只支持utf8文件转译,可能是不支持的文件类型或者格式 + */ + CODE_84200(84200, "文件转译解析错误。排查方法:只支持utf8文件转译,可能是不支持的文件类型或者格式"), /** * 包含不合法的词语. */ diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java index c75f759660..486791986b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMpErrorMsgEnum.java @@ -86,9 +86,9 @@ public enum WxMpErrorMsgEnum { */ CODE_40016(40016, "不合法的按钮个数"), /** - * 不合法的按钮个数. + * 不合法的按钮类型. */ - CODE_40017(40017, "不合法的按钮个数"), + CODE_40017(40017, "不合法的按钮类型"), /** * 不合法的按钮名字长度. */ diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/BaseWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/BaseWxRedisOps.java new file mode 100644 index 0000000000..17e992ab25 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/BaseWxRedisOps.java @@ -0,0 +1,37 @@ +package me.chanjar.weixin.common.redis; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * 微信redis操作基本类 + *

+ * 非内置实现redis相关操作, 请实现该类 + */ +public abstract class BaseWxRedisOps implements WxRedisOps { + + @Override + public String getValue(String key) { + throw new UnsupportedOperationException(); + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + @Override + public Long getExpire(String key) { + throw new UnsupportedOperationException(); + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + throw new UnsupportedOperationException(); + } + + @Override + public Lock getLock(String key) { + throw new UnsupportedOperationException(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java new file mode 100644 index 0000000000..b42142943f --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/JedisWxRedisOps.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.common.redis; + +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.util.locks.JedisDistributedLock; +import redis.clients.jedis.Jedis; +import redis.clients.util.Pool; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +@RequiredArgsConstructor +public class JedisWxRedisOps implements WxRedisOps { + + private final Pool jedisPool; + + @Override + public String getValue(String key) { + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.get(key); + } + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + try (Jedis jedis = this.jedisPool.getResource()) { + if (expire <= 0) { + jedis.set(key, value); + } else { + jedis.psetex(key, timeUnit.toMillis(expire), value); + } + } + } + + @Override + public Long getExpire(String key) { + try (Jedis jedis = this.jedisPool.getResource()) { + return jedis.ttl(key); + } + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + try (Jedis jedis = this.jedisPool.getResource()) { + jedis.pexpire(key, timeUnit.toMillis(expire)); + } + } + + @Override + public Lock getLock(String key) { + return new JedisDistributedLock(jedisPool, key); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java new file mode 100644 index 0000000000..652cec84a1 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOps.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.common.redis; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.StringRedisTemplate; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@RequiredArgsConstructor +public class RedisTemplateWxRedisOps implements WxRedisOps { + + private final StringRedisTemplate redisTemplate; + + @Override + public String getValue(String key) { + return redisTemplate.opsForValue().get(key); + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + if (expire <= 0) { + redisTemplate.opsForValue().set(key, value); + } else { + redisTemplate.opsForValue().set(key, value, expire, timeUnit); + } + } + + @Override + public Long getExpire(String key) { + return redisTemplate.getExpire(key); + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + redisTemplate.expire(key, expire, timeUnit); + } + + @Override + public Lock getLock(String key) { + return new ReentrantLock(); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java new file mode 100644 index 0000000000..d51cd3e1ad --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/RedissonWxRedisOps.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.common.redis; + +import lombok.RequiredArgsConstructor; +import org.redisson.api.RedissonClient; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +@RequiredArgsConstructor +public class RedissonWxRedisOps implements WxRedisOps { + + private final RedissonClient redissonClient; + + @Override + public String getValue(String key) { + Object value = redissonClient.getBucket(key).get(); + return value == null ? null : value.toString(); + } + + @Override + public void setValue(String key, String value, int expire, TimeUnit timeUnit) { + if (expire <= 0) { + redissonClient.getBucket(key).set(value); + } else { + redissonClient.getBucket(key).set(value, expire, timeUnit); + } + } + + @Override + public Long getExpire(String key) { + long expire = redissonClient.getBucket(key).remainTimeToLive(); + if (expire > 0) { + expire = expire / 1000; + } + return expire; + } + + @Override + public void expire(String key, int expire, TimeUnit timeUnit) { + redissonClient.getBucket(key).expire(expire, timeUnit); + } + + @Override + public Lock getLock(String key) { + return redissonClient.getLock(key); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/WxRedisOps.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/WxRedisOps.java new file mode 100644 index 0000000000..5489165e74 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/redis/WxRedisOps.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.common.redis; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; + +/** + * 微信Redis相关操作 + *

+ * 该接口不承诺稳定, 外部实现请继承{@link BaseWxRedisOps} + * + * @see BaseWxRedisOps 实现需要继承该类 + * @see JedisWxRedisOps jedis实现 + * @see RedissonWxRedisOps redisson实现 + * @see RedisTemplateWxRedisOps redisTemplate实现 + */ +public interface WxRedisOps { + + String getValue(String key); + + void setValue(String key, String value, int expire, TimeUnit timeUnit); + + Long getExpire(String key); + + void expire(String key, int expire, TimeUnit timeUnit); + + Lock getLock(String key); +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxService.java new file mode 100644 index 0000000000..fc49bfd9ce --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxService.java @@ -0,0 +1,41 @@ +package me.chanjar.weixin.common.service; + +import me.chanjar.weixin.common.error.WxErrorException; + +/** + * 微信服务接口. + * + * @author Binary Wang + * @date 2020-04-25 + */ +public interface WxService { + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. + * + * @param queryParam 参数 + * @param url 请求接口地址 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String get(String url, String queryParam) throws WxErrorException; + + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. + * + * @param postData 请求参数json值 + * @param url 请求接口地址 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String post(String url, String postData) throws WxErrorException; + + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. + * + * @param url 请求接口地址 + * @param obj 请求对象 + * @return 接口响应字符串 + * @throws WxErrorException 异常 + */ + String post(String url, Object obj) throws WxErrorException; +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java index 2472cb44b8..591b7025dd 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/session/StandardSessionManager.java @@ -1,13 +1,12 @@ package me.chanjar.weixin.common.session; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; - +import me.chanjar.weixin.common.util.res.StringManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import me.chanjar.weixin.common.util.res.StringManager; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; /** * 基于内存的session manager. @@ -15,7 +14,6 @@ * @author Daniel Qian */ public class StandardSessionManager implements WxSessionManager, InternalSessionManager { - protected static final StringManager SM = StringManager.getManager(Constants.PACKAGE); /** * The descriptive name of this Manager implementation (for logging). @@ -51,7 +49,9 @@ public class StandardSessionManager implements WxSessionManager, InternalSession */ protected int maxInactiveInterval = 30 * 60; - // Number of sessions created by this manager + /** + * Number of sessions created by this manager + */ protected long sessionCounter = 0; protected volatile int maxActive = 0; @@ -154,12 +154,10 @@ public InternalSession createSession(String sessionId) { session.setValid(true); session.setCreationTime(System.currentTimeMillis()); session.setMaxInactiveInterval(this.maxInactiveInterval); - String id = sessionId; - session.setId(id); + session.setId(sessionId); this.sessionCounter++; - return (session); - + return session; } @@ -181,10 +179,8 @@ protected InternalSession getNewSession() { return new StandardSession(this); } - @Override public void add(InternalSession session) { - // 当第一次有session创建的时候,开启session清理线程 if (!this.backgroundProcessStarted.getAndSet(true)) { Thread t = new Thread(new Runnable() { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java index 4b7f9be6a7..768f2e5324 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/BeanUtils.java @@ -50,14 +50,13 @@ public static void checkRequiredFields(Object bean) throws WxErrorException { } } field.setAccessible(isAccessible); - } catch (SecurityException | IllegalArgumentException - | IllegalAccessException e) { + } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { log.error(e.getMessage(), e); } } if (!requiredFields.isEmpty()) { - String msg = "必填字段 " + requiredFields + " 必须提供值"; + String msg = String.format("必填字段【%s】必须提供值!", requiredFields); log.debug(msg); throw new WxErrorException(WxError.builder().errorMsg(msg).build()); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java index cd3a7a984c..c2ffdb001b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/XmlUtils.java @@ -1,21 +1,21 @@ package me.chanjar.weixin.common.util; -import java.io.StringReader; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.Node; import org.dom4j.io.SAXReader; import org.dom4j.tree.DefaultText; +import org.xml.sax.SAXException; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Sets; +import java.io.StringReader; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; /** *

@@ -31,13 +31,18 @@ public static Map xml2Map(String xmlString) {
     Map map = new HashMap<>(16);
     try {
       SAXReader saxReader = new SAXReader();
+      saxReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
+      saxReader.setFeature("http://javax.xml.XMLConstants/feature/secure-processing", true);
+      saxReader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+      saxReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
+      saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
       Document doc = saxReader.read(new StringReader(xmlString));
       Element root = doc.getRootElement();
       List elements = root.elements();
       for (Element element : elements) {
-          map.put(element.getName(), element2MapOrString(element));
+        map.put(element.getName(), element2MapOrString(element));
       }
-    } catch (DocumentException e) {
+    } catch (DocumentException | SAXException e) {
       throw new RuntimeException(e);
     }
 
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java
index 05c396e029..f221cdcbbe 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimpleGetRequestExecutor.java
@@ -3,8 +3,9 @@
 import java.io.IOException;
 
 import me.chanjar.weixin.common.WxType;
+import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientSimpleGetRequestExecutor;
+import me.chanjar.weixin.common.util.http.apache.ApacheSimpleGetRequestExecutor;
 import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimpleGetRequestExecutor;
 import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimpleGetRequestExecutor;
 
@@ -29,7 +30,7 @@ public void execute(String uri, String data, ResponseHandler handler, Wx
   public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
-        return new ApacheHttpClientSimpleGetRequestExecutor(requestHttp);
+        return new ApacheSimpleGetRequestExecutor(requestHttp);
       case JODD_HTTP:
         return new JoddHttpSimpleGetRequestExecutor(requestHttp);
       case OK_HTTP:
@@ -39,4 +40,12 @@ public static RequestExecutor create(RequestHttp requestHttp) {
     }
   }
 
+  protected String handleResponse(WxType wxType, String responseContent) throws WxErrorException {
+    WxError error = WxError.fromJson(responseContent, wxType);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    }
+
+    return responseContent;
+  }
 }
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java
index 1209790b65..804f13fe54 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/SimplePostRequestExecutor.java
@@ -1,10 +1,12 @@
 package me.chanjar.weixin.common.util.http;
 
 import me.chanjar.weixin.common.WxType;
+import me.chanjar.weixin.common.error.WxError;
 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 org.jetbrains.annotations.NotNull;
 
 import java.io.IOException;
 
@@ -39,4 +41,21 @@ public static RequestExecutor create(RequestHttp requestHttp) {
     }
   }
 
+  @NotNull
+  public String handleResponse(WxType wxType, String responseContent) throws WxErrorException {
+    if (responseContent.isEmpty()) {
+      throw new WxErrorException(WxError.builder().errorCode(9999).errorMsg("无响应内容").build());
+    }
+
+    if (responseContent.startsWith("")) {
+      //xml格式输出直接返回
+      return responseContent;
+    }
+
+    WxError error = WxError.fromJson(responseContent, wxType);
+    if (error.getErrorCode() != 0) {
+      throw new WxErrorException(error);
+    }
+    return responseContent;
+  }
 }
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java
index 51c37f59a5..aeb3bb301e 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheMediaDownloadRequestExecutor.java
@@ -63,7 +63,12 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError
         fileName = String.valueOf(System.currentTimeMillis());
       }
 
-      return FileUtils.createTmpFile(inputStream, FilenameUtils.getBaseName(fileName), FilenameUtils.getExtension(fileName),
+      String baseName = FilenameUtils.getBaseName(fileName);
+      if (StringUtils.isBlank(fileName) || baseName.length() < 3) {
+        baseName = String.valueOf(System.currentTimeMillis());
+      }
+
+      return FileUtils.createTmpFile(inputStream, baseName, FilenameUtils.getExtension(fileName),
         super.tmpDirFile);
 
     } finally {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java
similarity index 79%
rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java
rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java
index cc830013d2..32299f56fe 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientSimpleGetRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimpleGetRequestExecutor.java
@@ -19,8 +19,8 @@
  * @author ecoolper
  * @date 2017/5/4
  */
-public class ApacheHttpClientSimpleGetRequestExecutor extends SimpleGetRequestExecutor {
-  public ApacheHttpClientSimpleGetRequestExecutor(RequestHttp requestHttp) {
+public class ApacheSimpleGetRequestExecutor extends SimpleGetRequestExecutor {
+  public ApacheSimpleGetRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
 
@@ -40,11 +40,7 @@ public String execute(String uri, String queryParam, WxType wxType) throws WxErr
 
     try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpGet)) {
       String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
-      WxError error = WxError.fromJson(responseContent, wxType);
-      if (error.getErrorCode() != 0) {
-        throw new WxErrorException(error);
-      }
-      return responseContent;
+      return handleResponse(wxType, responseContent);
     } finally {
       httpGet.releaseConnection();
     }
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java
index 960ea865af..598278c78c 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheSimplePostRequestExecutor.java
@@ -12,6 +12,7 @@
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.entity.StringEntity;
 import org.apache.http.impl.client.CloseableHttpClient;
+import org.jetbrains.annotations.NotNull;
 
 import java.io.IOException;
 
@@ -42,22 +43,10 @@ public String execute(String uri, String postEntity, WxType wxType) throws WxErr
 
     try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) {
       String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response);
-      if (responseContent.isEmpty()) {
-        throw new WxErrorException(WxError.builder().errorCode(9999).errorMsg("无响应内容").build());
-      }
-
-      if (responseContent.startsWith("")) {
-        //xml格式输出直接返回
-        return responseContent;
-      }
-
-      WxError error = WxError.fromJson(responseContent, wxType);
-      if (error.getErrorCode() != 0) {
-        throw new WxErrorException(error);
-      }
-      return responseContent;
+      return this.handleResponse(wxType, responseContent);
     } finally {
       httpPost.releaseConnection();
     }
   }
+
 }
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java
index dfca21a7b2..3fb08ab2c6 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java
@@ -1,5 +1,7 @@
 package me.chanjar.weixin.common.util.http.apache;
 
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpHost;
 import org.apache.http.annotation.NotThreadSafe;
@@ -23,8 +25,6 @@
 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
 import org.apache.http.protocol.HttpContext;
 import org.apache.http.ssl.SSLContexts;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import javax.net.ssl.SSLContext;
 import java.io.IOException;
@@ -41,26 +41,65 @@
  *
  * @author kakotor
  */
+@Slf4j
+@Data
 @NotThreadSafe
 public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
-  protected final Logger log = LoggerFactory.getLogger(DefaultApacheHttpClientBuilder.class);
   private final AtomicBoolean prepared = new AtomicBoolean(false);
-  private int connectionRequestTimeout = 3000;
+
+  /**
+   * 获取链接的超时时间设置
+   * 

+ * 设置为零时不超时,一直等待. + * 设置为负数是使用系统默认设置(非3000ms的默认值,而是httpClient的默认设置). + *

+ */ + private int connectionRequestTimeout = -1; + + /** + * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用 + *

+ * 设置为零时不超时,一直等待. + * 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置). + *

+ */ private int connectionTimeout = 5000; + /** + * 默认NIO的socket超时设置,默认5000ms. + */ private int soTimeout = 5000; + /** + * 空闲链接的超时时间,默认60000ms. + *

+ * 超时的链接将在下一次空闲链接检查是被销毁 + *

+ */ private int idleConnTimeout = 60000; + /** + * 检查空间链接的间隔周期,默认60000ms. + */ private int checkWaitTime = 60000; + /** + * 每路的最大链接数,默认10 + */ private int maxConnPerHost = 10; + /** + * 最大总连接数,默认50 + */ private int maxTotalConn = 50; + /** + * 自定义httpclient的User Agent + */ private String userAgent; - private HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() { + + private final HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() { @Override public boolean retryRequest(IOException exception, int executionCount, HttpContext context) { return false; } }; private SSLConnectionSocketFactory sslConnectionSocketFactory = SSLConnectionSocketFactory.getSocketFactory(); - private PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); + private final PlainConnectionSocketFactory plainConnectionSocketFactory = PlainConnectionSocketFactory.getSocketFactory(); private String httpProxyHost; private int httpProxyPort; private String httpProxyUsername; @@ -111,90 +150,6 @@ public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFac return this; } - /** - * 获取链接的超时时间设置,默认3000ms - *

- * 设置为零时不超时,一直等待. - * 设置为负数是使用系统默认设置(非上述的3000ms的默认值,而是httpclient的默认设置). - *

- * - * @param connectionRequestTimeout 获取链接的超时时间设置(单位毫秒),默认3000ms - */ - public void setConnectionRequestTimeout(int connectionRequestTimeout) { - this.connectionRequestTimeout = connectionRequestTimeout; - } - - /** - * 建立链接的超时时间,默认为5000ms.由于是在链接池获取链接,此设置应该并不起什么作用 - *

- * 设置为零时不超时,一直等待. - * 设置为负数是使用系统默认设置(非上述的5000ms的默认值,而是httpclient的默认设置). - *

- * - * @param connectionTimeout 建立链接的超时时间设置(单位毫秒),默认5000ms - */ - public void setConnectionTimeout(int connectionTimeout) { - this.connectionTimeout = connectionTimeout; - } - - /** - * 默认NIO的socket超时设置,默认5000ms. - * - * @param soTimeout 默认NIO的socket超时设置,默认5000ms. - * @see java.net.SocketOptions#SO_TIMEOUT - */ - public void setSoTimeout(int soTimeout) { - this.soTimeout = soTimeout; - } - - /** - * 空闲链接的超时时间,默认60000ms. - *

- * 超时的链接将在下一次空闲链接检查是被销毁 - *

- * - * @param idleConnTimeout 空闲链接的超时时间,默认60000ms. - */ - public void setIdleConnTimeout(int idleConnTimeout) { - this.idleConnTimeout = idleConnTimeout; - } - - /** - * 检查空间链接的间隔周期,默认60000ms. - * - * @param checkWaitTime 检查空间链接的间隔周期,默认60000ms. - */ - public void setCheckWaitTime(int checkWaitTime) { - this.checkWaitTime = checkWaitTime; - } - - /** - * 每路的最大链接数,默认10 - * - * @param maxConnPerHost 每路的最大链接数,默认10 - */ - public void setMaxConnPerHost(int maxConnPerHost) { - this.maxConnPerHost = maxConnPerHost; - } - - /** - * 最大总连接数,默认50 - * - * @param maxTotalConn 最大总连接数,默认50 - */ - public void setMaxTotalConn(int maxTotalConn) { - this.maxTotalConn = maxTotalConn; - } - - /** - * 自定义httpclient的User Agent - * - * @param userAgent User Agent - */ - public void setUserAgent(String userAgent) { - this.userAgent = userAgent; - } - public IdleConnectionMonitorThread getIdleConnectionMonitorThread() { return this.idleConnectionMonitorThread; } @@ -268,7 +223,7 @@ public boolean isTrusted(X509Certificate[] chain, String authType) throws Certif null, SSLConnectionSocketFactory.getDefaultHostnameVerifier()); } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { - this.log.error(e.getMessage(), e); + log.error("构建SSL连接工厂时发生异常!", e); } return null; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java index 4f310274df..df508869f0 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaDownloadRequestExecutor.java @@ -60,9 +60,14 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError return null; } + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + try (InputStream inputStream = new ByteArrayInputStream(response.bodyBytes())) { return FileUtils.createTmpFile(inputStream, - FilenameUtils.getBaseName(fileName), + baseName, FilenameUtils.getExtension(fileName), super.tmpDirFile); } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java index c93bd4b180..193787a6bc 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimpleGetRequestExecutor.java @@ -41,13 +41,7 @@ public String execute(String uri, String queryParam, WxType wxType) throws WxErr HttpResponse response = request.send(); response.charset(StringPool.UTF_8); - String responseContent = response.bodyText(); - - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return handleResponse(wxType, response.bodyText()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java index 75114d6a7d..08dd9792ed 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpSimplePostRequestExecutor.java @@ -6,7 +6,6 @@ import jodd.http.ProxyInfo; import jodd.util.StringPool; import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; @@ -40,21 +39,7 @@ public String execute(String uri, String postEntity, WxType wxType) throws WxErr HttpResponse response = request.send(); response.charset(StringPool.UTF_8); - String responseContent = response.bodyText(); - if (responseContent.isEmpty()) { - throw new WxErrorException(WxError.builder().errorCode(9999).errorMsg("无响应内容").build()); - } - - if (responseContent.startsWith("")) { - //xml格式输出直接返回 - return responseContent; - } - - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return this.handleResponse(wxType, response.bodyText()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java index 729f1e186f..86710ccced 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpMediaDownloadRequestExecutor.java @@ -14,8 +14,6 @@ import okio.Okio; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -58,8 +56,13 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError return null; } + String baseName = FilenameUtils.getBaseName(fileName); + if (StringUtils.isBlank(fileName) || baseName.length() < 3) { + baseName = String.valueOf(System.currentTimeMillis()); + } + File file = File.createTempFile( - FilenameUtils.getBaseName(fileName), "." + FilenameUtils.getExtension(fileName), super.tmpDirFile + baseName, "." + FilenameUtils.getExtension(fileName), super.tmpDirFile ); try (BufferedSink sink = Okio.buffer(Okio.sink(file))) { diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java index dbaa27c544..09012692f5 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimpleGetRequestExecutor.java @@ -35,12 +35,7 @@ public String execute(String uri, String queryParam, WxType wxType) throws WxErr OkHttpClient client = requestHttp.getRequestHttpClient(); Request request = new Request.Builder().url(uri).build(); Response response = client.newCall(request).execute(); - String responseContent = response.body().string(); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - return responseContent; + return this.handleResponse(wxType, response.body().string()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java index 3be6152055..d4351b87ac 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/okhttp/OkHttpSimplePostRequestExecutor.java @@ -2,13 +2,13 @@ import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.WxType; -import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; import okhttp3.*; import java.io.IOException; +import java.util.Objects; /** * . @@ -24,16 +24,10 @@ public OkHttpSimplePostRequestExecutor(RequestHttp requestHttp) { @Override public String execute(String uri, String postEntity, WxType wxType) throws WxErrorException, IOException { - RequestBody body = RequestBody.create(MediaType.parse("text/plain; charset=utf-8"), postEntity); + RequestBody body = RequestBody.Companion.create(postEntity, MediaType.parse("text/plain; charset=utf-8")); Request request = new Request.Builder().url(uri).post(body).build(); Response response = requestHttp.getRequestHttpClient().newCall(request).execute(); - String responseContent = response.body().string(); - WxError error = WxError.fromJson(responseContent, wxType); - if (error.getErrorCode() != 0) { - throw new WxErrorException(error); - } - - return responseContent; + return this.handleResponse(wxType, Objects.requireNonNull(response.body()).string()); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java new file mode 100644 index 0000000000..b136a4c25a --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/JedisDistributedLock.java @@ -0,0 +1,73 @@ +package me.chanjar.weixin.common.util.locks; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; + +import com.github.jedis.lock.JedisLock; +import redis.clients.jedis.Jedis; +import redis.clients.util.Pool; + +/** + * JedisPool 分布式锁 + * + * @author 007 + */ +public class JedisDistributedLock implements Lock { + private final Pool jedisPool; + private final JedisLock lock; + + public JedisDistributedLock(Pool jedisPool, String key){ + this.jedisPool = jedisPool; + this.lock = new JedisLock(key); + } + + @Override + public void lock() { + try (Jedis jedis = jedisPool.getResource()) { + if (!lock.acquire(jedis)) { + throw new RuntimeException("acquire timeouted"); + } + } catch (InterruptedException e) { + throw new RuntimeException("lock failed", e); + } + } + + @Override + public void lockInterruptibly() throws InterruptedException { + try (Jedis jedis = jedisPool.getResource()) { + if (!lock.acquire(jedis)) { + throw new RuntimeException("acquire timeouted"); + } + } + } + + @Override + public boolean tryLock() { + try (Jedis jedis = jedisPool.getResource()) { + return lock.acquire(jedis); + } catch (InterruptedException e) { + throw new RuntimeException("lock failed", e); + } + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + try (Jedis jedis = jedisPool.getResource()) { + return lock.acquire(jedis); + } + } + + @Override + public void unlock() { + try (Jedis jedis = jedisPool.getResource()) { + lock.release(jedis); + } + } + + @Override + public Condition newCondition() { + throw new RuntimeException("unsupported method"); + } + +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java index 639fcf08d1..5fd7ceb2cb 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/XStreamInitializer.java @@ -1,13 +1,18 @@ package me.chanjar.weixin.common.util.xml; -import java.io.Writer; - import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.basic.*; +import com.thoughtworks.xstream.converters.collections.CollectionConverter; import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider; +import com.thoughtworks.xstream.converters.reflection.ReflectionConverter; import com.thoughtworks.xstream.core.util.QuickWriter; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; import com.thoughtworks.xstream.io.xml.XppDriver; +import com.thoughtworks.xstream.security.NoTypePermission; +import com.thoughtworks.xstream.security.WildcardTypePermission; + +import java.io.Writer; public class XStreamInitializer { private static final XppDriver XPP_DRIVER = new XppDriver() { @@ -41,14 +46,34 @@ public String encodeNode(String name) { }; public static XStream getInstance() { - XStream xstream = new XStream(new PureJavaReflectionProvider(), XPP_DRIVER); + XStream xstream = new XStream(new PureJavaReflectionProvider(), XPP_DRIVER) { + // only register the converters we need; other converters generate a private access warning in the console on Java9+... + @Override + protected void setupConverters() { + registerConverter(new NullConverter(), PRIORITY_VERY_HIGH); + registerConverter(new IntConverter(), PRIORITY_NORMAL); + registerConverter(new FloatConverter(), PRIORITY_NORMAL); + registerConverter(new DoubleConverter(), PRIORITY_NORMAL); + registerConverter(new LongConverter(), PRIORITY_NORMAL); + registerConverter(new ShortConverter(), PRIORITY_NORMAL); + registerConverter(new BooleanConverter(), PRIORITY_NORMAL); + registerConverter(new ByteConverter(), PRIORITY_NORMAL); + registerConverter(new StringConverter(), PRIORITY_NORMAL); + registerConverter(new DateConverter(), PRIORITY_NORMAL); + registerConverter(new CollectionConverter(getMapper()), PRIORITY_NORMAL); + registerConverter(new ReflectionConverter(getMapper(), getReflectionProvider()), PRIORITY_VERY_LOW); + } + }; xstream.ignoreUnknownElements(); xstream.setMode(XStream.NO_REFERENCES); XStream.setupDefaultSecurity(xstream); - xstream.allowTypesByWildcard(new String[]{ - "me.chanjar.weixin.**", "cn.binarywang.wx.**", "com.github.binarywang.**" - }); + xstream.autodetectAnnotations(true); + // setup proper security by limiting which classes can be loaded by XStream + xstream.addPermission(NoTypePermission.NONE); + xstream.addPermission(new WildcardTypePermission(new String[]{ + "me.chanjar.weixin.**", "cn.binarywang.wx.**", "com.github.binarywang.**" + })); xstream.setClassLoader(Thread.currentThread().getContextClassLoader()); return xstream; } diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java new file mode 100644 index 0000000000..96ba20ba2b --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/CommonWxRedisOpsTest.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.common.redis; + +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.concurrent.TimeUnit; + +public class CommonWxRedisOpsTest { + + protected WxRedisOps wxRedisOps; + private String key = "access_token"; + private String value = String.valueOf(System.currentTimeMillis()); + + @Test + public void testGetValue() { + wxRedisOps.setValue(key, value, 3, TimeUnit.SECONDS); + Assert.assertEquals(wxRedisOps.getValue(key), value); + } + + @Test + public void testSetValue() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + wxRedisOps.setValue(key, value, 0, TimeUnit.SECONDS); + wxRedisOps.setValue(key, value, 1, TimeUnit.SECONDS); + } + + @Test + public void testGetExpire() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + Assert.assertTrue(wxRedisOps.getExpire(key) < 0); + wxRedisOps.setValue(key, value, 4, TimeUnit.SECONDS); + Long expireSeconds = wxRedisOps.getExpire(key); + Assert.assertTrue(expireSeconds <= 4 && expireSeconds >= 0); + } + + @Test + public void testExpire() { + String key = "access_token", value = String.valueOf(System.currentTimeMillis()); + wxRedisOps.setValue(key, value, -1, TimeUnit.SECONDS); + wxRedisOps.expire(key, 4, TimeUnit.SECONDS); + Long expireSeconds = wxRedisOps.getExpire(key); + Assert.assertTrue(expireSeconds <= 4 && expireSeconds >= 0); + } + + @Test + public void testGetLock() { + Assert.assertNotNull(wxRedisOps.getLock("access_token_lock")); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java new file mode 100644 index 0000000000..2ff2c37b81 --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/JedisWxRedisOpsTest.java @@ -0,0 +1,21 @@ +package me.chanjar.weixin.common.redis; + +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; +import redis.clients.jedis.JedisPool; + +public class JedisWxRedisOpsTest extends CommonWxRedisOpsTest { + + JedisPool jedisPool; + + @BeforeTest + public void init() { + this.jedisPool = new JedisPool("127.0.0.1", 6379); + this.wxRedisOps = new JedisWxRedisOps(jedisPool); + } + + @AfterTest + public void destroy() { + this.jedisPool.close(); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java new file mode 100644 index 0000000000..bf3b35a7cc --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedisTemplateWxRedisOpsTest.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.common.redis; + +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +public class RedisTemplateWxRedisOpsTest extends CommonWxRedisOpsTest { + + StringRedisTemplate redisTemplate; + + @BeforeTest + public void init() { + JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); + connectionFactory.setHostName("127.0.0.1"); + connectionFactory.setPort(6379); + connectionFactory.afterPropertiesSet(); + StringRedisTemplate redisTemplate = new StringRedisTemplate(connectionFactory); + this.redisTemplate = redisTemplate; + this.wxRedisOps = new RedisTemplateWxRedisOps(this.redisTemplate); + } + + @AfterTest + public void destroy() { + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java new file mode 100644 index 0000000000..48cf7b29be --- /dev/null +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/redis/RedissonWxRedisOpsTest.java @@ -0,0 +1,27 @@ +package me.chanjar.weixin.common.redis; + +import org.redisson.Redisson; +import org.redisson.api.RedissonClient; +import org.redisson.config.Config; +import org.redisson.config.TransportMode; +import org.testng.annotations.AfterTest; +import org.testng.annotations.BeforeTest; + +public class RedissonWxRedisOpsTest extends CommonWxRedisOpsTest { + + RedissonClient redissonClient; + + @BeforeTest + public void init() { + Config config = new Config(); + config.useSingleServer().setAddress("redis://127.0.0.1:6379"); + config.setTransportMode(TransportMode.NIO); + this.redissonClient = Redisson.create(config); + this.wxRedisOps = new RedissonWxRedisOps(this.redissonClient); + } + + @AfterTest + public void destroy() { + this.redissonClient.shutdown(); + } +} diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java index 1afd1c1d9c..7b6bb536f4 100644 --- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java +++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/XmlUtilsTest.java @@ -1,10 +1,10 @@ package me.chanjar.weixin.common.util; +import org.testng.annotations.Test; + import java.util.List; import java.util.Map; -import org.testng.annotations.*; - import static org.assertj.core.api.Assertions.assertThat; /** @@ -17,6 +17,17 @@ */ public class XmlUtilsTest { + @Test(expectedExceptions = {RuntimeException.class}) + public void testXml2Map_xxe() { + String xml = "\n" + + "\n" + + "\n" + + "]>\n" + + ""; + XmlUtils.xml2Map(xml); + } + @Test public void testXml2Map() { String xml = "\n" + diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml index 5892d6a3d6..cbd9be4d40 100644 --- a/weixin-java-cp/pom.xml +++ b/weixin-java-cp/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 3.7.0 + 3.8.0 weixin-java-cp @@ -38,7 +38,11 @@ org.slf4j slf4j-api - + + + org.redisson + redisson + org.testng testng diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java index df5326f249..5aa2336a71 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpExternalContactService.java @@ -1,8 +1,10 @@ package me.chanjar.weixin.cp.api; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; +import me.chanjar.weixin.cp.bean.*; +import java.util.Calendar; +import java.util.Date; import java.util.List; /** @@ -83,6 +85,81 @@ public interface WxCpExternalContactService { * @return List of CpUser id * @throws WxErrorException . */ - List listFollowUser() throws WxErrorException; + List listFollowers() throws WxErrorException; + /** + * 企业和第三方可通过此接口,获取所有离职成员的客户列表,并可进一步调用离职成员的外部联系人再分配接口将这些客户重新分配给其他企业成员。 + * @param page + * @param pageSize + * @return + * @throws WxErrorException + */ + WxCpUserExternalUnassignList listUnassignedList(Integer page, Integer pageSize) throws WxErrorException; + + /** + * 企业可通过此接口,将已离职成员的外部联系人分配给另一个成员接替联系。 + * @param externalUserid + * @param handOverUserid + * @param takeOverUserid + * @return + * @throws WxErrorException + */ + WxCpBaseResp transferExternalContact(String externalUserid,String handOverUserid,String takeOverUserid)throws WxErrorException; + + /**
+    * 该接口用于获取配置过客户群管理的客户群列表。
+    * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+    * 暂不支持第三方调用。
+    * 微信文档:https://work.weixin.qq.com/api/doc/90000/90135/92119
+    * 
+ */ + WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex,Integer pageSize,int status,String[] userIds,String[] partyIds) throws WxErrorException; + + /** + *
+   * 通过客户群ID,获取详情。包括群名、群成员列表、群成员入群时间、入群方式。(客户群是由具有客户群使用权限的成员创建的外部群)
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 暂不支持第三方调用。
+   * 微信文档:https://work.weixin.qq.com/api/doc/90000/90135/92122
+   * 
+ * + * @param chatId + * @return + * @throws WxErrorException + */ + WxCpUserExternalGroupChatInfo getGroupChat(String chatId) throws WxErrorException; + + /** + *
+   * 企业可通过此接口获取成员联系客户的数据,包括发起申请数、新增客户数、聊天数、发送消息数和删除/拉黑成员的客户数等指标。
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 第三方应用需拥有“企业客户”权限。
+   * 第三方/自建应用调用时传入的userid和partyid要在应用的可见范围内;
+   * 
+ * @param startTime + * @param endTime + * @param userIds + * @param partyIds + * @return + * @throws WxErrorException + */ + WxCpUserExternalUserBehaviorStatistic getUserBehaviorStatistic(Date startTime, Date endTime, String[] userIds, String[] partyIds) throws WxErrorException; + + /** + *
+   * 获取指定日期全天的统计数据。注意,企业微信仅存储60天的数据。
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?)。
+   * 暂不支持第三方调用。
+   * 
+ * @param startTime + * @param orderBy + * @param orderAsc + * @param pageIndex + * @param pageSize + * @param userIds + * @param partyIds + * @return + * @throws WxErrorException + */ + WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime,Integer orderBy,Integer orderAsc,Integer pageIndex,Integer pageSize, String[] userIds, String[] partyIds) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java index edc2f552ce..ad2f403af6 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java @@ -6,7 +6,9 @@ import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; +import me.chanjar.weixin.cp.bean.WxCpTpAuthInfo; import me.chanjar.weixin.cp.bean.WxCpTpCorp; +import me.chanjar.weixin.cp.bean.WxCpTpPermanentCodeInfo; import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; /** @@ -88,8 +90,46 @@ public interface WxCpTpService { * @param authCode . * @return . */ + @Deprecated WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException; + /** + * 获取企业永久授权码信息 + *
+   *   原来的方法实现不全
+   * 
+ * + * @param authCode + * @return + * + * @author yuan + * @since 2020-03-18 + * + * @throws WxErrorException + */ + WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException; + + /** + *
+   *   获取预授权链接
+   * 
+ * @param redirectUri 授权完成后的回调网址 + * @param state a-zA-Z0-9的参数值(不超过128个字节),用于第三方自行校验session,防止跨域攻击 + * @return + * @throws WxErrorException + */ + String getPreAuthUrl(String redirectUri,String state) throws WxErrorException; + + /** + * 获取企业的授权信息 + * + * @param authCorpId 授权企业的corpId + * @param permanentCode 授权企业的永久授权码 + * @return + * @throws WxErrorException + */ + WxCpTpAuthInfo getAuthInfo(String authCorpId,String permanentCode) throws WxErrorException; + /** * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. * diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java index dea5428c1b..dea647f3f9 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java @@ -3,6 +3,7 @@ import com.google.common.base.Joiner; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.WxType; import me.chanjar.weixin.common.bean.WxAccessToken; @@ -15,12 +16,13 @@ import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; import me.chanjar.weixin.cp.api.WxCpTpService; -import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; -import me.chanjar.weixin.cp.bean.WxCpTpCorp; +import me.chanjar.weixin.cp.bean.*; import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; +import org.apache.commons.lang3.StringUtils; import java.io.File; import java.io.IOException; +import java.net.URLEncoder; import java.util.HashMap; import java.util.Map; @@ -123,6 +125,35 @@ public WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException { return wxCpTpCorp; } + @Override + public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException{ + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("auth_code", authCode); + String result = post(configStorage.getApiUrl(GET_PERMANENT_CODE), jsonObject.toString()); + return WxCpTpPermanentCodeInfo.fromJson(result); + } + + @Override + @SneakyThrows + public String getPreAuthUrl(String redirectUri,String state) throws WxErrorException{ + String result = get(configStorage.getApiUrl(GET_PREAUTH_CODE),null); + WxCpTpPreauthCode preauthCode = WxCpTpPreauthCode.fromJson(result); + String preAuthUrl = "https://open.work.weixin.qq.com/3rdapp/install?suite_id="+configStorage.getSuiteId()+ + "&pre_auth_code="+preauthCode.getPreAuthCode()+"&redirect_uri="+ URLEncoder.encode(redirectUri,"utf-8"); + if(StringUtils.isNotBlank(state)) + preAuthUrl += "&state="+state; + return preAuthUrl; + } + + @Override + public WxCpTpAuthInfo getAuthInfo(String authCorpId, String permanentCode) throws WxErrorException{ + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("auth_corpid", authCorpId); + jsonObject.addProperty("permanent_code", permanentCode); + String result = post(configStorage.getApiUrl(GET_AUTH_INFO), jsonObject.toString()); + return WxCpTpAuthInfo.fromJson(result); + } + @Override public String get(String url, String queryParam) throws WxErrorException { return execute(SimpleGetRequestExecutor.create(this), url, queryParam); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java index 044b1e5d49..044155847e 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImpl.java @@ -1,22 +1,27 @@ package me.chanjar.weixin.cp.api.impl; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxCpErrorMsgEnum; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.api.WxCpExternalContactService; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactInfo; -import me.chanjar.weixin.cp.bean.WxCpUserExternalContactList; -import me.chanjar.weixin.cp.bean.WxCpUserWithExternalPermission; +import me.chanjar.weixin.cp.bean.*; +import org.apache.commons.lang3.ArrayUtils; +import java.util.Collections; +import java.util.Date; import java.util.List; import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.ExternalContact.*; +/** + * @author 曹祖鹏 & yuanqixun + */ +@RequiredArgsConstructor public class WxCpExternalContactServiceImpl implements WxCpExternalContactService { - private WxCpService mainService; - - public WxCpExternalContactServiceImpl(WxCpService mainService) { - this.mainService = mainService; - } + private final WxCpService mainService; @Override public WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException { @@ -35,14 +40,114 @@ public WxCpUserExternalContactInfo getContactDetail(String userId) throws WxErro @Override public List listExternalContacts(String userId) throws WxErrorException { final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_EXTERNAL_CONTACT + userId); - String responseContent = this.mainService.get(url, null); - return WxCpUserExternalContactList.fromJson(responseContent).getExternalUserId(); + try { + String responseContent = this.mainService.get(url, null); + return WxCpUserExternalContactList.fromJson(responseContent).getExternalUserId(); + } catch (WxErrorException e) { + // not external contact,无客户则返回空列表 + if (e.getError().getErrorCode() == WxCpErrorMsgEnum.CODE_84061.getCode()) { + return Collections.emptyList(); + } + throw e; + } } @Override - public List listFollowUser() throws WxErrorException { + public List listFollowers() throws WxErrorException { final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_FOLLOW_USER_LIST); String responseContent = this.mainService.get(url, null); - return WxCpUserWithExternalPermission.fromJson(responseContent).getFollowUser(); + return WxCpUserWithExternalPermission.fromJson(responseContent).getFollowers(); + } + + @Override + public WxCpUserExternalUnassignList listUnassignedList(Integer pageIndex, Integer pageSize) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("page_id", pageIndex == null ? 0 : pageIndex); + json.addProperty("page_size", pageSize == null ? 100 : pageSize); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_UNASSIGNED_CONTACT); + final String result = this.mainService.post(url, json.toString()); + return WxCpUserExternalUnassignList.fromJson(result); + } + + @Override + public WxCpBaseResp transferExternalContact(String externalUserid, String handOverUserid, String takeOverUserid) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("external_userid", externalUserid); + json.addProperty("handover_userid", handOverUserid); + json.addProperty("takeover_userid", takeOverUserid); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(TRANSFER_UNASSIGNED_CONTACT); + final String result = this.mainService.post(url, json.toString()); + return WxCpBaseResp.fromJson(result); + } + + @Override + public WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pageSize, int status, String[] userIds, String[] partyIds) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("offset", pageIndex == null ? 0 : pageIndex); + json.addProperty("limit", pageSize == null ? 100 : pageSize); + json.addProperty("status_filter", status); + if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { + JsonObject ownerFilter = new JsonObject(); + if (ArrayUtils.isNotEmpty(userIds)) { + json.add("userid", new Gson().toJsonTree(userIds).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(partyIds)) { + json.add("partyid", new Gson().toJsonTree(partyIds).getAsJsonArray()); + } + json.add("owner_filter", ownerFilter); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_LIST); + final String result = this.mainService.post(url, json.toString()); + return WxCpUserExternalGroupChatList.fromJson(result); + } + + @Override + public WxCpUserExternalGroupChatInfo getGroupChat(String chatId) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("chat_id", chatId); + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GROUP_CHAT_INFO); + final String result = this.mainService.post(url, json.toString()); + return WxCpUserExternalGroupChatInfo.fromJson(result); + } + + @Override + public WxCpUserExternalUserBehaviorStatistic getUserBehaviorStatistic(Date startTime, Date endTime, String[] userIds, String[] partyIds) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("start_time", startTime.getTime() / 1000); + json.addProperty("end_time", endTime.getTime() / 1000); + if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { + if (ArrayUtils.isNotEmpty(userIds)) { + json.add("userid", new Gson().toJsonTree(userIds).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(partyIds)) { + json.add("partyid", new Gson().toJsonTree(partyIds).getAsJsonArray()); + } + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_USER_BEHAVIOR_DATA); + final String result = this.mainService.post(url, json.toString()); + return WxCpUserExternalUserBehaviorStatistic.fromJson(result); + } + + @Override + public WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, Integer orderBy, Integer orderAsc, Integer pageIndex, Integer pageSize, String[] userIds, String[] partyIds) throws WxErrorException { + JsonObject json = new JsonObject(); + json.addProperty("day_begin_time", startTime.getTime() / 1000); + json.addProperty("order_by", orderBy == null ? 1 : orderBy); + json.addProperty("order_asc", orderAsc == null ? 0 : orderAsc); + json.addProperty("offset", pageIndex == null ? 0 : pageIndex); + json.addProperty("limit", pageSize == null ? 500 : pageSize); + if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { + JsonObject ownerFilter = new JsonObject(); + if (ArrayUtils.isNotEmpty(userIds)) { + json.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray()); + } + if (ArrayUtils.isNotEmpty(partyIds)) { + json.add("userid_list", new Gson().toJsonTree(partyIds).getAsJsonArray()); + } + json.add("owner_filter", ownerFilter); + } + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(LIST_GROUP_CHAT_DATA); + final String result = this.mainService.post(url, json.toString()); + return WxCpUserExternalGroupChatStatistic.fromJson(result); } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java index a7538c6ead..016c880fd1 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceImpl.java @@ -1,12 +1,125 @@ package me.chanjar.weixin.cp.api.impl; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import me.chanjar.weixin.common.WxType; +import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.constant.WxCpApiPathConsts; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.BasicResponseHandler; +import org.apache.http.impl.client.CloseableHttpClient; + +import java.io.IOException; +import java.util.concurrent.locks.Lock; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_AGENT_CONFIG_TICKET; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_JSAPI_TICKET; + /** *
  *  默认接口实现类,使用apache httpclient实现
  * Created by Binary Wang on 2017-5-27.
  * 
+ *
+ * 增加分布式锁(基于WxCpConfigStorage实现)的支持
+ * Updated by yuanqixun on 2020-05-13
+ * 
+ * * * @author Binary Wang */ public class WxCpServiceImpl extends WxCpServiceApacheHttpClientImpl { + @Override + public String getAccessToken(boolean forceRefresh) throws WxErrorException { + if (!getWxCpConfigStorage().isAccessTokenExpired() && !forceRefresh) { + return getWxCpConfigStorage().getAccessToken(); + } + Lock lock = getWxCpConfigStorage().getAccessTokenLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (!getWxCpConfigStorage().isAccessTokenExpired() && !forceRefresh) { + return getWxCpConfigStorage().getAccessToken(); + } + String url = String.format(getWxCpConfigStorage().getApiUrl(WxCpApiPathConsts.GET_TOKEN), this.configStorage.getCorpId(), this.configStorage.getCorpSecret()); + try { + HttpGet httpGet = new HttpGet(url); + if (getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom() + .setProxy(getRequestHttpProxy()).build(); + httpGet.setConfig(config); + } + String resultContent; + try (CloseableHttpClient httpClient = getRequestHttpClient(); + CloseableHttpResponse response = httpClient.execute(httpGet)) { + resultContent = new BasicResponseHandler().handleResponse(response); + } finally { + httpGet.releaseConnection(); + } + WxError error = WxError.fromJson(resultContent, WxType.CP); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + + WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); + getWxCpConfigStorage().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } finally { + lock.unlock(); + } + return getWxCpConfigStorage().getAccessToken(); + } + + @Override + public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException { + if (forceRefresh) { + getWxCpConfigStorage().expireAgentJsapiTicket(); + } + if (getWxCpConfigStorage().isAgentJsapiTicketExpired()) { + Lock lock = getWxCpConfigStorage().getAgentJsapiTicketLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (getWxCpConfigStorage().isAgentJsapiTicketExpired()) { + String responseContent = this.get(getWxCpConfigStorage().getApiUrl(GET_AGENT_CONFIG_TICKET), null); + JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); + getWxCpConfigStorage().updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(), + jsonObject.get("expires_in").getAsInt()); + } + } finally { + lock.unlock(); + } + } + return getWxCpConfigStorage().getAgentJsapiTicket(); + } + + @Override + public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { + if (forceRefresh) { + getWxCpConfigStorage().expireJsapiTicket(); + } + + if (getWxCpConfigStorage().isJsapiTicketExpired()) { + Lock lock = getWxCpConfigStorage().getJsapiTicketLock(); + lock.lock(); + try { + // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 + if (getWxCpConfigStorage().isJsapiTicketExpired()) { + String responseContent = this.get(getWxCpConfigStorage().getApiUrl(GET_JSAPI_TICKET), null); + JsonObject tmpJsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); + getWxCpConfigStorage().updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(), + tmpJsonObject.get("expires_in").getAsInt()); + } + } finally { + lock.unlock(); + } + } + return getWxCpConfigStorage().getJsapiTicket(); + } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java index cc76e9cf4c..6aff7ed530 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceJoddHttpImpl.java @@ -3,8 +3,8 @@ import jodd.http.HttpConnectionProvider; import jodd.http.HttpRequest; import jodd.http.HttpResponse; -import jodd.http.JoddHttp; import jodd.http.ProxyInfo; +import jodd.http.net.SocketHttpConnectionProvider; import me.chanjar.weixin.common.WxType; import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.error.WxError; @@ -68,7 +68,7 @@ public void initHttp() { configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword()); } - httpClient = JoddHttp.httpConnectionProvider; + httpClient = new SocketHttpConnectionProvider(); } @Override diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java new file mode 100644 index 0000000000..e94110a055 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpBaseResp.java @@ -0,0 +1,28 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * @author yqx + * @date 2020/3/16 + */ +@Getter +@Setter +public class WxCpBaseResp { + @SerializedName("errcode") + protected Long errcode; + + @SerializedName("errmsg") + protected String errmsg; + + public boolean success() { + return getErrcode() == 0; + } + + public static WxCpBaseResp fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpBaseResp.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java index dc71e027e3..f5b9b32592 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpDepart.java @@ -6,16 +6,17 @@ import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; /** - * 微信部门. + * 企业微信的部门. * * @author Daniel Qian */ @Data public class WxCpDepart implements Serializable { - private static final long serialVersionUID = -5028321625140879571L; + private Long id; private String name; + private String enName; private Long parentId; private Long order; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java new file mode 100644 index 0000000000..4354865a28 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpAuthInfo.java @@ -0,0 +1,202 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 服务商模式获取授权信息 + * + * @author yuanqixun + */ +@Getter +@Setter +public class WxCpTpAuthInfo extends WxCpBaseResp { + private static final long serialVersionUID = -5028321625140879571L; + + /** + * 服务商信息 + */ + @SerializedName("dealer_corp_info") + private DealerCorpInfo dealerCorpInfo; + + /** + * 授权企业信息 + */ + @SerializedName("auth_corp_info") + private AuthCorpInfo authCorpInfo; + + /** + * 授权信息。如果是通讯录应用,且没开启实体应用,是没有该项的。通讯录应用拥有企业通讯录的全部信息读写权限 + */ + @SerializedName("auth_info") + private AuthInfo authInfo; + + @Getter + @Setter + public static class DealerCorpInfo { + @SerializedName("corpid") + private String corpId; + + @SerializedName("corp_name") + private String corpName; + } + + @Getter + @Setter + public static class AuthCorpInfo { + @SerializedName("corpid") + private String corpId; + + @SerializedName("corp_name") + private String corpName; + + @SerializedName("corp_type") + private String corpType; + + @SerializedName("corp_square_logo_url") + private String corpSquareLogoUrl; + + @SerializedName("corp_round_logo_url") + private String corpRoundLogoUrl; + + @SerializedName("corp_user_max") + private String corpUserMax; + + @SerializedName("corp_agent_max") + private String corpAgentMax; + + /** + * 所绑定的企业微信主体名称(仅认证过的企业有) + */ + @SerializedName("corp_full_name") + private String corpFullName; + + /** + * 认证到期时间 + */ + @SerializedName("verified_end_time") + private Long verifiedEndTime; + + /** + * 企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队号 + */ + @SerializedName("subject_type") + private Integer subjectType; + + /** + * 授权企业在微工作台(原企业号)的二维码,可用于关注微工作台 + */ + @SerializedName("corp_wxqrcode") + private String corpWxQrcode; + + @SerializedName("corp_scale") + private String corpScale; + + @SerializedName("corp_industry") + private String corpIndustry; + + @SerializedName("corp_sub_industry") + private String corpSubIndustry; + + @SerializedName("location") + private String location; + + } + + /** + * 授权信息 + */ + @Getter + @Setter + public static class AuthInfo { + + /** + * 授权的应用信息,注意是一个数组,但仅旧的多应用套件授权时会返回多个agent,对新的单应用授权,永远只返回一个agent + */ + @SerializedName("agent") + private List agents; + + } + + @Getter + @Setter + public static class Agent { + @SerializedName("agentid") + private Integer agentId; + + @SerializedName("name") + private String name; + + @SerializedName("round_logo_url") + private String roundLogoUrl; + + @SerializedName("square_logo_url") + private String squareLogoUrl; + + /** + * 旧的多应用套件中的对应应用id,新开发者请忽略 + */ + @SerializedName("appid") + @Deprecated + private String appid; + + /** + * 应用权限 + */ + @SerializedName("privilege") + private Privilege privilege; + + } + + /** + * 应用对应的权限 + */ + @Getter + @Setter + public static class Privilege { + + /** + * 权限等级。 + * 1:通讯录基本信息只读 + * 2:通讯录全部信息只读 + * 3:通讯录全部信息读写 + * 4:单个基本信息只读 + * 5:通讯录全部信息只写 + */ + @SerializedName("level") + private Integer level; + + @SerializedName("allow_party") + private List allowParties; + + @SerializedName("allow_user") + private List allowUsers; + + @SerializedName("allow_tag") + private List allowTags; + + @SerializedName("extra_party") + private List extraParties; + + @SerializedName("extra_user") + private List extraUsers; + + @SerializedName("extra_tag") + private List extraTags; + + } + + + public static WxCpTpAuthInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpAuthInfo.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java index 9ca59b843d..f77fdb78df 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpCorp.java @@ -1,42 +1,44 @@ -package me.chanjar.weixin.cp.bean; - -import java.io.Serializable; - -import com.google.gson.annotations.SerializedName; - -import lombok.Data; -import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; - -/** - * 微信部门. - * - * @author Daniel Qian - */ -@Data -public class WxCpTpCorp implements Serializable { - - private static final long serialVersionUID = -5028321625140879571L; - @SerializedName("corpid") - private String corpId; - @SerializedName("corp_name") - private String corpName; - @SerializedName("corp_full_name") - private String corpFullName; - @SerializedName("corp_type") - private String corpType; - @SerializedName("corp_square_logo_url") - private String corpSquareLogoUrl; - @SerializedName("corp_user_max") - private String corpUserMax; - @SerializedName("permanent_code") - private String permanentCode; - - public static WxCpTpCorp fromJson(String json) { - return WxCpGsonBuilder.create().fromJson(json, WxCpTpCorp.class); - } - - public String toJson() { - return WxCpGsonBuilder.create().toJson(this); - } - -} +package me.chanjar.weixin.cp.bean; + +import java.io.Serializable; + +import com.google.gson.annotations.SerializedName; + +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 微信部门. + * + * @author Daniel Qian + */ +@Data +public class WxCpTpCorp implements Serializable { + + private static final long serialVersionUID = -5028321625140879571L; + @SerializedName("corpid") + private String corpId; + @SerializedName("corp_name") + private String corpName; + @SerializedName("corp_full_name") + private String corpFullName; + @SerializedName("corp_type") + private String corpType; + @SerializedName("corp_square_logo_url") + private String corpSquareLogoUrl; + @SerializedName("corp_user_max") + private String corpUserMax; + @SerializedName("permanent_code") + private String permanentCode; + @SerializedName("auth_info") + private String authInfo; + + public static WxCpTpCorp fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpCorp.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java new file mode 100644 index 0000000000..cd57119d1e --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPermanentCodeInfo.java @@ -0,0 +1,219 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 服务商模式获取永久授权码信息 + * + * @author yunaqixun + */ +@Getter +@Setter +public class WxCpTpPermanentCodeInfo extends WxCpBaseResp { + + private static final long serialVersionUID = -5028321625140879571L; + + @SerializedName("access_token") + private String accessToken; + + @SerializedName("expires_in") + private Long expiresIn; + + @SerializedName("permanent_code") + private String permanentCode; + + /** + * 授权企业信息 + */ + @SerializedName("auth_corp_info") + private AuthCorpInfo authCorpInfo; + + /** + * 授权信息。如果是通讯录应用,且没开启实体应用,是没有该项的。通讯录应用拥有企业通讯录的全部信息读写权限 + */ + @SerializedName("auth_info") + private AuthInfo authInfo; + + /** + * 授权用户信息 + */ + @SerializedName("auth_user_info") + private AuthUserInfo authUserInfo; + + + @Getter + @Setter + public static class AuthCorpInfo { + @SerializedName("corpid") + private String corpId; + + @SerializedName("corp_name") + private String corpName; + + @SerializedName("corp_type") + private String corpType; + + @SerializedName("corp_square_logo_url") + private String corpSquareLogoUrl; + + @SerializedName("corp_round_logo_url") + private String corpRoundLogoUrl; + + @SerializedName("corp_user_max") + private String corpUserMax; + + @SerializedName("corp_agent_max") + private String corpAgentMax; + + /** + * 所绑定的企业微信主体名称(仅认证过的企业有) + */ + @SerializedName("corp_full_name") + private String corpFullName; + + /** + * 认证到期时间 + */ + @SerializedName("verified_end_time") + private Long verifiedEndTime; + + /** + * 企业类型,1. 企业; 2. 政府以及事业单位; 3. 其他组织, 4.团队号 + */ + @SerializedName("subject_type") + private Integer subjectType; + + /** + * 授权企业在微工作台(原企业号)的二维码,可用于关注微工作台 + */ + @SerializedName("corp_wxqrcode") + private String corpWxQrcode; + + @SerializedName("corp_scale") + private String corpScale; + + @SerializedName("corp_industry") + private String corpIndustry; + + @SerializedName("corp_sub_industry") + private String corpSubIndustry; + + @SerializedName("location") + private String location; + + } + + /** + * 授权信息 + */ + @Getter + @Setter + public static class AuthInfo { + + /** + * 授权的应用信息,注意是一个数组,但仅旧的多应用套件授权时会返回多个agent,对新的单应用授权,永远只返回一个agent + */ + @SerializedName("agent") + private List agents; + + } + + @Getter + @Setter + public static class Agent { + @SerializedName("agentid") + private Integer agentId; + + @SerializedName("name") + private String name; + + @SerializedName("round_logo_url") + private String roundLogoUrl; + + @SerializedName("square_logo_url") + private String squareLogoUrl; + + /** + * 旧的多应用套件中的对应应用id,新开发者请忽略 + */ + @SerializedName("appid") + @Deprecated + private String appid; + + /** + * 应用权限 + */ + @SerializedName("privilege") + private Privilege privilege; + + } + + /** + * 授权人员信息 + */ + @Getter + @Setter + public static class AuthUserInfo { + @SerializedName("userid") + private String userId; + + @SerializedName("name") + private String name; + + @SerializedName("avatar") + private String avatar; + } + + /** + * 应用对应的权限 + */ + @Getter + @Setter + public static class Privilege { + + /** + * 权限等级。 + * 1:通讯录基本信息只读 + * 2:通讯录全部信息只读 + * 3:通讯录全部信息读写 + * 4:单个基本信息只读 + * 5:通讯录全部信息只写 + */ + @SerializedName("level") + private Integer level; + + @SerializedName("allow_party") + private List allowParties; + + @SerializedName("allow_user") + private List allowUsers; + + @SerializedName("allow_tag") + private List allowTags; + + @SerializedName("extra_party") + private List extraParties; + + @SerializedName("extra_user") + private List extraUsers; + + @SerializedName("extra_tag") + private List extraTags; + + + } + + public static WxCpTpPermanentCodeInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpPermanentCodeInfo.class); + } + + public String toJson() { + return WxCpGsonBuilder.create().toJson(this); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPreauthCode.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPreauthCode.java new file mode 100644 index 0000000000..8c102ae4a2 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpPreauthCode.java @@ -0,0 +1,26 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 预授权码返回 + * @author yqx + * @date 2020/3/19 + */ +@Getter +@Setter +public class WxCpTpPreauthCode extends WxCpBaseResp { + + @SerializedName("pre_auth_code") + String preAuthCode; + + @SerializedName("expires_in") + Long expiresIn; + + public static WxCpTpPreauthCode fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpTpPreauthCode.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java index 53e068dbfe..f20f74b757 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java @@ -1,68 +1,72 @@ -package me.chanjar.weixin.cp.bean; - -import java.io.Serializable; -import java.util.Map; - -import com.thoughtworks.xstream.annotations.XStreamAlias; -import com.thoughtworks.xstream.annotations.XStreamConverter; -import lombok.Data; -import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.util.XmlUtils; -import me.chanjar.weixin.common.util.crypto.WxCryptUtil; -import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; -import me.chanjar.weixin.cp.bean.outxmlbuilder.ImageBuilder; -import me.chanjar.weixin.cp.bean.outxmlbuilder.NewsBuilder; -import me.chanjar.weixin.cp.bean.outxmlbuilder.TextBuilder; -import me.chanjar.weixin.cp.bean.outxmlbuilder.VideoBuilder; -import me.chanjar.weixin.cp.bean.outxmlbuilder.VoiceBuilder; -import me.chanjar.weixin.cp.config.WxCpConfigStorage; -import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; -import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; -import me.chanjar.weixin.cp.util.xml.XStreamTransformer; - -/** - * 回调推送的message - * https://work.weixin.qq.com/api/doc#90001/90143/90612 - * - * @author zhenjun cai - */ -@XStreamAlias("xml") -@Slf4j -@Data -public class WxCpTpXmlMessage implements Serializable { - - private static final long serialVersionUID = 6031833682211475786L; - /** - * 使用dom4j解析的存放所有xml属性和值的map. - */ - private Map allFieldsMap; - - @XStreamAlias("SuiteId") - @XStreamConverter(value = XStreamCDataConverter.class) - protected String suiteId; - - @XStreamAlias("InfoType") - @XStreamConverter(value = XStreamCDataConverter.class) - protected String infoType; - - @XStreamAlias("TimeStamp") - @XStreamConverter(value = XStreamCDataConverter.class) - protected String timeStamp; - - @XStreamAlias("SuiteTicket") - @XStreamConverter(value = XStreamCDataConverter.class) - protected String suiteTicket; - - @XStreamAlias("AuthCode") - @XStreamConverter(value = XStreamCDataConverter.class) - protected String authCode; - - public static WxCpTpXmlMessage fromXml(String xml) { - //修改微信变态的消息内容格式,方便解析 - //xml = xml.replace("", ""); - final WxCpTpXmlMessage xmlPackage = XStreamTransformer.fromXml(WxCpTpXmlMessage.class, xml); - xmlPackage.setAllFieldsMap(XmlUtils.xml2Map(xml)); - return xmlPackage; - } - -} +package me.chanjar.weixin.cp.bean; + +import java.io.Serializable; +import java.util.Map; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamConverter; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.util.XmlUtils; +import me.chanjar.weixin.common.util.crypto.WxCryptUtil; +import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; +import me.chanjar.weixin.cp.bean.outxmlbuilder.ImageBuilder; +import me.chanjar.weixin.cp.bean.outxmlbuilder.NewsBuilder; +import me.chanjar.weixin.cp.bean.outxmlbuilder.TextBuilder; +import me.chanjar.weixin.cp.bean.outxmlbuilder.VideoBuilder; +import me.chanjar.weixin.cp.bean.outxmlbuilder.VoiceBuilder; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; +import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; +import me.chanjar.weixin.cp.util.xml.XStreamTransformer; + +/** + * 回调推送的message + * https://work.weixin.qq.com/api/doc#90001/90143/90612 + * + * @author zhenjun cai + */ +@XStreamAlias("xml") +@Slf4j +@Data +public class WxCpTpXmlMessage implements Serializable { + + private static final long serialVersionUID = 6031833682211475786L; + /** + * 使用dom4j解析的存放所有xml属性和值的map. + */ + private Map allFieldsMap; + + @XStreamAlias("SuiteId") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String suiteId; + + @XStreamAlias("InfoType") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String infoType; + + @XStreamAlias("TimeStamp") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String timeStamp; + + @XStreamAlias("SuiteTicket") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String suiteTicket; + + @XStreamAlias("AuthCode") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String authCode; + + @XStreamAlias("AuthCorpId") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String authCorpId; + + public static WxCpTpXmlMessage fromXml(String xml) { + //修改微信变态的消息内容格式,方便解析 + //xml = xml.replace("", ""); + final WxCpTpXmlMessage xmlPackage = XStreamTransformer.fromXml(WxCpTpXmlMessage.class, xml); + xmlPackage.setAllFieldsMap(XmlUtils.xml2Map(xml)); + return xmlPackage; + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java index 90354154a6..07ecb4aa85 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUser.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import java.io.Serializable; @@ -16,8 +17,10 @@ * @author Daniel Qian */ @Data +@Accessors(chain = true) public class WxCpUser implements Serializable { private static final long serialVersionUID = -5696099236344075582L; + private String userId; private String name; private Long[] departIds; @@ -56,13 +59,19 @@ public class WxCpUser implements Serializable { * 成员对外信息. */ private List externalAttrs = new ArrayList<>(); + private String externalPosition; + private String externalCorpName; public void addExternalAttr(ExternalAttribute externalAttr) { this.externalAttrs.add(externalAttr); } public void addExtAttr(String name, String value) { - this.extAttrs.add(new Attr(name, value)); + this.extAttrs.add(new Attr().setType(0).setName(name).setTextValue(value)); + } + + public void addExtAttr(Attr attr) { + this.extAttrs.add(attr); } public static WxCpUser fromJson(String json) { @@ -74,10 +83,16 @@ public String toJson() { } @Data - @AllArgsConstructor + @Accessors(chain = true) public static class Attr { + /** + * 属性类型: 0-文本 1-网页 + */ + private int type; private String name; - private String value; + private String textValue; + private String webUrl; + private String webTitle; } @Data diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatInfo.java new file mode 100644 index 0000000000..e5d8db7262 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatInfo.java @@ -0,0 +1,75 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * @author yqx + * @date 2020/3/116 + */ +@Getter +@Setter +public class WxCpUserExternalGroupChatInfo extends WxCpBaseResp{ + + @SerializedName("group_chat") + private GroupChat groupChat; + + @Getter + @Setter + public static class GroupChat { + @SerializedName("chat_id") + private String chatId; + + @SerializedName("name") + private String name; + + @SerializedName("owner") + private String owner; + + @SerializedName("create_time") + private Long createTime; + + @SerializedName("notice") + private String notice; + + @SerializedName("member_list") + private List memberList; + + } + + @Getter + @Setter + public static class GroupMember { + @SerializedName("userid") + private String userId; + + /** + * 成员类型。 + * 1 - 企业成员 + * 2 - 外部联系人 + */ + @SerializedName("type") + private int type; + + @SerializedName("join_time") + private Long joinTime; + + /** + * 入群方式。 + * 1 - 由成员邀请入群(直接邀请入群) + * 2 - 由成员邀请入群(通过邀请链接入群) + * 3 - 通过扫描群二维码入群 + */ + @SerializedName("join_scene") + private int joinScene; + + } + + public static WxCpUserExternalGroupChatInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalGroupChatInfo.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatList.java new file mode 100644 index 0000000000..0a0e970efc --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatList.java @@ -0,0 +1,46 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * @author yqx + * @date 2020/3/116 + */ +@Getter +@Setter +public class WxCpUserExternalGroupChatList extends WxCpBaseResp { + + @SerializedName("group_chat_list") + private List groupChatList; + + @Getter + @Setter + public static class ChatStatus { + + /** + * 客户群ID + */ + @SerializedName("chat_id") + private String chatId; + + /** + * 客户群状态 + * 0 - 正常 + * 1 - 跟进人离职 + * 2 - 离职继承中 + * 3 - 离职继承完成 + */ + @SerializedName("status") + private int status; + + } + + public static WxCpUserExternalGroupChatList fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalGroupChatList.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatStatistic.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatStatistic.java new file mode 100644 index 0000000000..242ceb4f8b --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalGroupChatStatistic.java @@ -0,0 +1,90 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 联系客户群统计数据 + * + * @author yqx + * @date 2020/3/16 + */ +@Getter +@Setter +public class WxCpUserExternalGroupChatStatistic extends WxCpBaseResp{ + + @SerializedName("total") + int total; + + @SerializedName("next_offset") + int nextOffset; + + @SerializedName("items") + List itemList; + + @Getter + @Setter + public static class StatisticItem { + + @SerializedName("owner") + String owner; + + @SerializedName("data") + ItemData itemData; + } + + @Getter + @Setter + public static class ItemData { + + /** + * 新增客户群数量 + */ + @SerializedName("new_chat_cnt") + int newChatCnt; + + /** + * 截至当天客户群总数量 + */ + @SerializedName("chat_total") + int chatTotal; + + /** + * 截至当天有发过消息的客户群数量 + */ + @SerializedName("chat_has_msg") + int chatHasMsg; + + /** + * 客户群新增群人数。 + */ + @SerializedName("new_member_cnt") + int newMemberCnt; + + /** + * 截至当天客户群总人数 + */ + @SerializedName("member_total") + int memberTotal; + + /** + * 截至当天有发过消息的群成员数 + */ + @SerializedName("member_has_msg") + int memberHasMsg; + + /** + * 截至当天客户群消息总数 + */ + @SerializedName("msg_total") + int msgTotal; + } + + public static WxCpUserExternalGroupChatStatistic fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalGroupChatStatistic.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUnassignList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUnassignList.java new file mode 100644 index 0000000000..ec86dea767 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUnassignList.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 离职员工外部联系人列表 + * @author yqx + * @date 2020/3/15 + */ +@Getter +@Setter +public class WxCpUserExternalUnassignList extends WxCpBaseResp{ + + @SerializedName("info") + private List unassignInfos; + + @SerializedName("is_last") + private boolean isLast; + + @Getter + @Setter + public static class UnassignInfo { + + /** + * 离职成员userid + */ + @SerializedName("handover_userid") + private String handoverUserid; + + /** + * 外部联系人userid + */ + @SerializedName("external_userid") + private String externalUserid; + + /** + * 成员离职时间 + */ + @SerializedName("dimission_time") + private Long dimissionTime; + } + + public static WxCpUserExternalUnassignList fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalUnassignList.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUserBehaviorStatistic.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUserBehaviorStatistic.java new file mode 100644 index 0000000000..bc1aa0ca53 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserExternalUserBehaviorStatistic.java @@ -0,0 +1,78 @@ +package me.chanjar.weixin.cp.bean; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 联系客户统计数据 + * @author yqx + * @date 2020/3/16 + */ +@Getter +@Setter +public class WxCpUserExternalUserBehaviorStatistic extends WxCpBaseResp { + + @SerializedName("behavior_data") + private List behaviorList; + + @Getter + @Setter + public static class Behavior { + + /** + * 数据日期,为当日0点的时间戳 + */ + @SerializedName("stat_time") + private Long statTime; + + /** + * 聊天总数, 成员有主动发送过消息的聊天数,包括单聊和群聊。 + */ + @SerializedName("chat_cnt") + private int chatCnt; + + /** + * 发送消息数,成员在单聊和群聊中发送的消息总数。 + */ + @SerializedName("message_cnt") + private int messageCnt; + + /** + * 已回复聊天占比,客户主动发起聊天后,成员在一个自然日内有回复过消息的聊天数/客户主动发起的聊天数比例,不包括群聊,仅在确有回复时返回。 + */ + @SerializedName("reply_percentage") + private double replyPercentage; + + /** + * 平均首次回复时长,单位为分钟,即客户主动发起聊天后,成员在一个自然日内首次回复的时长间隔为首次回复时长,所有聊天的首次回复总时长/已回复的聊天总数即为平均首次回复时长,不包括群聊,仅在确有回复时返回。 + */ + @SerializedName("avg_reply_time") + private int avgReplyTime; + + /** + * 删除/拉黑成员的客户数,即将成员删除或加入黑名单的客户数。 + */ + @SerializedName("negative_fee_back_cnt") + private int negativeFeeBackCnt; + + /** + * 发起申请数,成员通过「搜索手机号」、「扫一扫」、「从微信好友中添加」、「从群聊中添加」、「添加共享、分配给我的客户」、「添加单向、双向删除好友关系的好友」、「从新的联系人推荐中添加」等渠道主动向客户发起的好友申请数量。 + */ + @SerializedName("new_apply_cnt") + private int newApplyCnt; + + /** + * 新增客户数,成员新添加的客户数量。 + */ + @SerializedName("new_contact_cnt") + private int newContactCnt; + } + + public static WxCpUserExternalUserBehaviorStatistic fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalUserBehaviorStatistic.class); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserWithExternalPermission.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserWithExternalPermission.java index 2530c79dc8..e64a273d74 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserWithExternalPermission.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpUserWithExternalPermission.java @@ -2,46 +2,26 @@ import com.google.gson.annotations.Expose; import com.google.gson.annotations.SerializedName; +import lombok.Data; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import java.util.List; +/** + * @author 曹祖鹏 + */ +@Data public class WxCpUserWithExternalPermission { @SerializedName("errcode") @Expose - private Long errcode; + private Long errCode; @SerializedName("errmsg") @Expose - private String errmsg; + private String errMsg; @SerializedName("follow_user") @Expose - private List followUser = null; - - public Long getErrcode() { - return errcode; - } - - public void setErrcode(Long errcode) { - this.errcode = errcode; - } - - public String getErrmsg() { - return errmsg; - } - - public void setErrmsg(String errmsg) { - this.errmsg = errmsg; - } - - public List getFollowUser() { - return followUser; - } - - public void setFollowUser(List followUser) { - this.followUser = followUser; - } - + private List followers = null; public static WxCpUserWithExternalPermission fromJson(String json) { return WxCpGsonBuilder.create().fromJson(json, WxCpUserWithExternalPermission.class); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java index 712eed83f0..ccae5a4644 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java @@ -233,6 +233,13 @@ public class WxCpXmlMessage implements Serializable { @XStreamConverter(value = XStreamCDataConverter.class) private String position; + /** + * 群ID. + */ + @XStreamAlias("ChatId") + @XStreamConverter(value = XStreamCDataConverter.class) + private String chatId; + /** * 性别,1表示男性,2表示女性. */ @@ -396,6 +403,12 @@ public class WxCpXmlMessage implements Serializable { @XStreamAlias("SendLocationInfo") private SendLocationInfo sendLocationInfo = new SendLocationInfo(); + + @XStreamAlias("ApprovalInfo") + private ApprovalInfo approvalInfo=new ApprovalInfo(); + + + protected static WxCpXmlMessage fromXml(String xml) { //修改微信变态的消息内容格式,方便解析 xml = xml.replace("", ""); @@ -515,4 +528,57 @@ public static class SendLocationInfo { } + @XStreamAlias("ApprovalInfo") + @Data + public static class ApprovalInfo { + + /** + * 审批编号 + */ + @XStreamAlias("SpNo") + private String spNo; + /** + * 审批申请类型名称(审批模板名称) + */ + @XStreamAlias("SpName") + private String spName; + /** + * 申请单状态:1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付 + */ + @XStreamAlias("SpStatus") + private Integer spStatus; + + /** + * 审批模板id。 + */ + @XStreamAlias("templateId") + private String templateId; + /** + * 审批申请提交时间,Unix时间戳 + */ + @XStreamAlias("ApplyTime") + private Integer applyTime; + + /** + * 申请人信息 + */ + @XStreamAlias("Applyer") + private Applyer applyer; + /** + * 审批申请单变化类型 + */ + @XStreamAlias("StatuChangeEvent") + private Integer statuChangeEvent; + + @XStreamAlias("Applyer") + @Data + public static class Applyer { + @XStreamAlias("Applyer") + private String UserId; + @XStreamAlias("Party") + private String party; + } + + } + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java index 595d12c5bc..2b2a9fe051 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/MpnewsArticle.java @@ -1,7 +1,9 @@ package me.chanjar.weixin.cp.bean.article; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import java.io.Serializable; @@ -13,6 +15,8 @@ * @author Binary Wang */ @Data +@NoArgsConstructor +@AllArgsConstructor @Builder(builderMethodName = "newBuilder") public class MpnewsArticle implements Serializable { private static final long serialVersionUID = 6985871812170756481L; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalComment.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalComment.java index 8a70e3e6e9..1bf06ec51b 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalComment.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalComment.java @@ -13,7 +13,6 @@ */ @Data public class WxCpApprovalComment implements Serializable { - private static final long serialVersionUID = -5430367411926856292L; /** diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetail.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetail.java index 5021a57ee6..a22a435844 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetail.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/WxCpApprovalDetail.java @@ -13,7 +13,6 @@ */ @Data public class WxCpApprovalDetail implements Serializable { - private static final long serialVersionUID = 1353393306564207170L; /** @@ -49,19 +48,20 @@ public class WxCpApprovalDetail implements Serializable { /** * 申请人信息 */ - private WxCpApprovalApplyer applyer; + @SerializedName("applyer") + private WxCpApprovalApplyer applier; /** * 审批流程信息,可能有多个审批节点 */ @SerializedName("sp_record") - private WxCpApprovalRecord spRecord; + private WxCpApprovalRecord[] spRecords; /** * 抄送信息,可能有多个抄送节点 */ @SerializedName("notifyer") - private WxCpOperator notifyer; + private WxCpOperator[] notifiers; /** * 审批申请数据 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java index 7798338aa1..92b47f56da 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/applydata/ContentValue.java @@ -11,16 +11,15 @@ */ @Data public class ContentValue implements Serializable { - private static final long serialVersionUID = -5607678965965065261L; private String text; @SerializedName("new_number") - private Integer newNumber; + private Double newNumber; @SerializedName("new_money") - private Integer newMoney; + private Double newMoney; private ContentValue.Date date; @@ -34,26 +33,25 @@ public class ContentValue implements Serializable { private List children; + private Attendance attendance; + @Data public static class Date implements Serializable { - private static final long serialVersionUID = -6181554080062231138L; private String type; @SerializedName("s_timestamp") - private Long timestamp; + private Double timestamp; } @Data public static class Selector implements Serializable { - private static final long serialVersionUID = 7305458759126951773L; private String type; private List