diff --git a/.gitignore b/.gitignore index db0804163d..2a629437b0 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ sonar-project.properties # STS .factorypath +*.zip diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75db09403b..ba8e495afb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,8 +1,8 @@ # 代码贡献指南 -1. 首先非常欢迎和感谢对本项目发起Pull Request的同学。 -1. **特别提示:请务必在develop分支提交PR,master分支目前仅是正式版的代码,即发布正式版本后才会从develop分支进行合并。** +1. 首先非常欢迎和感谢对本项目发起`Pull Request`的同学。 +1. **特别提示:请务必在 `develop` 分支提交 `PR`,`release` 分支目前仅是正式版的代码,即发布正式版本后才会从 `develop` 分支进行合并。** 1. 本项目代码风格为使用2个空格代表一个Tab,因此在提交代码时请注意一下,否则很容易在IDE格式化代码后与原代码产生大量diff,这样会给其他人阅读代码带来极大的困扰。 -1. 为了便于设置,本项目引入editorconfig支持,请使用Eclipse的同学在贡献代码前安装相关插件,而IntelliJ IDEA新版本自带支持,如果没有可自行安装插件。 +1. 为了便于设置,本项目引入`editorconfig`支持,请使用Eclipse的同学在贡献代码前安装相关插件,而`IntelliJ IDEA`新版本自带支持,如果没有可自行安装插件。 1. **提交代码前,请检查代码是否已经格式化,并且保证新增加或者修改的方法都有完整的参数说明,而public方法必须拥有相应的单元测试并通过测试。** 1. 本项目可以采用两种方式接受代码贡献: - 第一种就是基于[Git Flow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow)开发流程,因此在发起Pull Request的时候请选择develop分支,详细步骤参考后文,推荐使用此种方式贡献代码。 diff --git a/README.md b/README.md index c5f82fc0e7..ab7ada780b 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,7 @@ - 维沃吼吼 - 王朝社区(比亚迪新能源社区) - 极吼吼手机上门回收换新 +- 未来信封 #### 公众号: @@ -138,6 +139,7 @@ - YshopMall - 好行景区直通车以及全国40多个公众号 - 我奥篮球公众号 +- 未来信封官微 #### 企业号/企业微信: - HTC企业微信 diff --git a/others/weixin-java-osgi/pom.xml b/others/weixin-java-osgi/pom.xml index 039e32e734..da6907f46a 100644 --- a/others/weixin-java-osgi/pom.xml +++ b/others/weixin-java-osgi/pom.xml @@ -28,7 +28,7 @@ com.thoughtworks.xstream xstream - 1.4.10-java7 + 1.4.13-java7 provided diff --git a/pom.xml b/pom.xml index 5b23cb58c7..7118281d00 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 4.0.0 com.github.binarywang wx-java - 3.9.0 + 4.0.0 pom WxJava - Weixin/Wechat Java SDK 微信开发Java SDK @@ -134,7 +134,7 @@ org.jodd jodd-http - 5.1.6 + 5.2.0 provided @@ -172,17 +172,17 @@ org.slf4j slf4j-api - 1.7.24 + 1.7.30 com.thoughtworks.xstream xstream - 1.4.11.1 + 1.4.14 com.google.guava guava - 29.0-android + 29.0-jre com.google.code.gson @@ -239,6 +239,12 @@ 3.0.0 test + + com.github.dreamhead + moco-runner + 1.1.0 + test + redis.clients @@ -262,7 +268,7 @@ org.springframework.data spring-data-redis - 1.8.23.RELEASE + 2.3.3.RELEASE true provided diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index 3ec16a2274..553c01950c 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 3.9.0 + 4.0.0 pom wx-java-spring-boot-starters @@ -14,7 +14,7 @@ WxJava 各个模块的 Spring Boot Starter - 2.1.4.RELEASE + 2.3.3.RELEASE 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 b6e5904acd..dd243cf015 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.9.0 + 4.0.0 4.0.0 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 7c727c9687..a07e8008bc 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 @@ -9,6 +9,7 @@ import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl; import cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl; import com.binarywang.spring.starter.wxjava.miniapp.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.miniapp.properties.RedisProperties; import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties; import lombok.AllArgsConstructor; import me.chanjar.weixin.common.redis.JedisWxRedisOps; @@ -52,14 +53,19 @@ public class WxMaAutoConfiguration { public WxMaService service(WxMaConfig wxMaConfig) { HttpClientType httpClientType = wxMaProperties.getConfigStorage().getHttpClientType(); WxMaService wxMaService; - if (httpClientType == HttpClientType.OkHttp) { - wxMaService = new WxMaServiceOkHttpImpl(); - } else if (httpClientType == HttpClientType.JoddHttp) { - wxMaService = new WxMaServiceJoddHttpImpl(); - } else if (httpClientType == HttpClientType.HttpClient) { - wxMaService = new WxMaServiceHttpClientImpl(); - } else { - wxMaService = new WxMaServiceImpl(); + switch (httpClientType) { + case OkHttp: + wxMaService = new WxMaServiceOkHttpImpl(); + break; + case JoddHttp: + wxMaService = new WxMaServiceJoddHttpImpl(); + break; + case HttpClient: + wxMaService = new WxMaServiceHttpClientImpl(); + break; + default: + wxMaService = new WxMaServiceImpl(); + break; } wxMaService.setWxMaConfig(wxMaConfig); return wxMaService; @@ -102,7 +108,7 @@ private WxMaDefaultConfigImpl wxMaDefaultConfigStorage() { } private WxMaDefaultConfigImpl wxMaJedisConfigStorage() { - WxMaProperties.RedisProperties redisProperties = wxMaProperties.getConfigStorage().getRedis(); + RedisProperties redisProperties = wxMaProperties.getConfigStorage().getRedis(); JedisPool jedisPool; if (StringUtils.isNotEmpty(redisProperties.getHost())) { JedisPoolConfig config = new JedisPoolConfig(); diff --git a/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/RedisProperties.java b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/RedisProperties.java new file mode 100644 index 0000000000..9cfaf80e8d --- /dev/null +++ b/spring-boot-starters/wx-java-miniapp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/miniapp/properties/RedisProperties.java @@ -0,0 +1,43 @@ +package com.binarywang.spring.starter.wxjava.miniapp.properties; + +import lombok.Data; + +/** + * redis 配置. + * + * @author Binary Wang + * @date 2020-08-30 + */ +@Data +public class RedisProperties { + + /** + * 主机地址.不填则从spring容器内获取JedisPool + */ + 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-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 0139215ea7..25a004776f 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 @@ -88,37 +88,4 @@ public static class ConfigStorage { private String httpProxyPassword; } - @Data - public static class RedisProperties { - - /** - * 主机地址.不填则从spring容器内获取JedisPool - */ - 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 adc3a31f46..81a075432f 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 @@ -16,16 +16,23 @@ wx.mp.token = @token wx.mp.aesKey = @aesKey # 存储配置redis(可选) - wx.mp.config-storage.type = redis # 配置类型: memory(默认), redis, jedis, redistemplate + wx.mp.config-storage.type = Jedis # 配置类型: Memory(默认), 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 + #单机和sentinel同时存在时,优先使用sentinel配置 + #wx.mp.config-storage.redis.sentinel-ips=127.0.0.1:16379,127.0.0.1:26379 + #wx.mp.config-storage.redis.sentinel-name=mymaster # http客户端配置 - wx.mp.config-storage.http-client-type=httpclient # http客户端类型: httpclient(默认), okhttp, joddhttp + 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= + # 公众号地址host配置 + #wx.mp.hosts.api-host=http://proxy.com/ + #wx.mp.hosts.open-host=http://proxy.com/ + #wx.mp.hosts.mp-host=http://proxy.com/ ``` 3. 自动注入的类型 - `WxMpService` 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 7e59fc8c83..6aa7368413 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.9.0 + 4.0.0 4.0.0 @@ -30,6 +30,16 @@ ${spring.boot.version} provided + + org.jodd + jodd-http + provided + + + com.squareup.okhttp3 + okhttp + 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 1c942bbfa2..3b8733c286 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,8 +1,8 @@ package com.binarywang.spring.starter.wxjava.mp.config; +import com.binarywang.spring.starter.wxjava.mp.enums.HttpClientType; import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; -import me.chanjar.weixin.common.api.WxOcrService; -import me.chanjar.weixin.mp.api.*; +import me.chanjar.weixin.mp.api.WxMpService; import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl; @@ -23,16 +23,21 @@ public class WxMpServiceAutoConfiguration { @Bean @ConditionalOnMissingBean public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpProperties wxMpProperties) { - WxMpProperties.HttpClientType httpClientType = wxMpProperties.getConfigStorage().getHttpClientType(); + HttpClientType httpClientType = wxMpProperties.getConfigStorage().getHttpClientType(); WxMpService wxMpService; - if (httpClientType == WxMpProperties.HttpClientType.okhttp) { - wxMpService = newWxMpServiceOkHttpImpl(); - } else if (httpClientType == WxMpProperties.HttpClientType.joddhttp) { - wxMpService = newWxMpServiceJoddHttpImpl(); - } else if (httpClientType == WxMpProperties.HttpClientType.httpclient) { - wxMpService = newWxMpServiceHttpClientImpl(); - } else { - wxMpService = newWxMpServiceImpl(); + switch (httpClientType) { + case OkHttp: + wxMpService = newWxMpServiceOkHttpImpl(); + break; + case JoddHttp: + wxMpService = newWxMpServiceJoddHttpImpl(); + break; + case HttpClient: + wxMpService = newWxMpServiceHttpClientImpl(); + break; + default: + wxMpService = newWxMpServiceImpl(); + break; } wxMpService.setWxMpConfigStorage(configStorage); 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 fc15e7605e..a814f73a8e 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,10 +1,15 @@ package com.binarywang.spring.starter.wxjava.mp.config; +import com.binarywang.spring.starter.wxjava.mp.enums.StorageType; +import com.binarywang.spring.starter.wxjava.mp.properties.RedisProperties; import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties; +import com.google.common.collect.Sets; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; 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.bean.WxMpHostConfig; import me.chanjar.weixin.mp.config.WxMpConfigStorage; import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl; import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl; @@ -16,17 +21,21 @@ import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.core.StringRedisTemplate; import redis.clients.jedis.JedisPool; +import redis.clients.jedis.JedisPoolAbstract; import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.JedisSentinelPool; + +import java.util.Set; /** * 微信公众号存储策略自动配置. * * @author someone */ +@Slf4j @Configuration @RequiredArgsConstructor public class WxMpStorageAutoConfiguration { - private final ApplicationContext applicationContext; private final WxMpProperties wxMpProperties; @@ -40,41 +49,73 @@ public class WxMpStorageAutoConfiguration { @Bean @ConditionalOnMissingBean(WxMpConfigStorage.class) public WxMpConfigStorage wxMpConfigStorage() { - WxMpProperties.StorageType type = wxMpProperties.getConfigStorage().getType(); + 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(); + switch (type) { + case Jedis: + config = jedisConfigStorage(); + break; + case RedisTemplate: + config = redisTemplateConfigStorage(); + break; + default: + config = defaultConfigStorage(); + break; + } + // wx host config + if (null != wxMpProperties.getHosts() && StringUtils.isNotEmpty(wxMpProperties.getHosts().getApiHost())) { + WxMpHostConfig hostConfig = new WxMpHostConfig(); + hostConfig.setApiHost(wxMpProperties.getHosts().getApiHost()); + hostConfig.setMpHost(wxMpProperties.getHosts().getMpHost()); + hostConfig.setOpenHost(wxMpProperties.getHosts().getOpenHost()); + config.setHostConfig(hostConfig); } return config; } - private WxMpConfigStorage wxMpInMemoryConfigStorage() { + private WxMpConfigStorage defaultConfigStorage() { WxMpDefaultConfigImpl config = new WxMpDefaultConfigImpl(); setWxMpInfo(config); return config; } - private WxMpConfigStorage wxMpInJedisConfigStorage() { - JedisPool jedisPool; + private WxMpConfigStorage jedisConfigStorage() { + JedisPoolAbstract jedisPool; if (StringUtils.isNotEmpty(redisHost) || StringUtils.isNotEmpty(redisHost2)) { jedisPool = getJedisPool(); } else { jedisPool = applicationContext.getBean(JedisPool.class); } WxRedisOps redisOps = new JedisWxRedisOps(jedisPool); - WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps, wxMpProperties.getConfigStorage().getKeyPrefix()); + WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps, + wxMpProperties.getConfigStorage().getKeyPrefix()); setWxMpInfo(wxMpRedisConfig); return wxMpRedisConfig; } - private WxMpConfigStorage wxMpInRedisTemplateConfigStorage() { - StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + private WxMpConfigStorage redisTemplateConfigStorage() { + StringRedisTemplate redisTemplate = null; + try { + redisTemplate = applicationContext.getBean(StringRedisTemplate.class); + } catch (Exception e) { + log.error(e.getMessage(), e); + } + try { + if (null == redisTemplate) { + redisTemplate = (StringRedisTemplate) applicationContext.getBean("stringRedisTemplate"); + } + } catch (Exception e) { + log.error(e.getMessage(), e); + } + + if (null == redisTemplate) { + redisTemplate = (StringRedisTemplate) applicationContext.getBean("redisTemplate"); + } + WxRedisOps redisOps = new RedisTemplateWxRedisOps(redisTemplate); - WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps, wxMpProperties.getConfigStorage().getKeyPrefix()); + WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisOps, + wxMpProperties.getConfigStorage().getKeyPrefix()); + setWxMpInfo(wxMpRedisConfig); return wxMpRedisConfig; } @@ -95,9 +136,9 @@ private void setWxMpInfo(WxMpDefaultConfigImpl config) { } } - private JedisPool getJedisPool() { + private JedisPoolAbstract getJedisPool() { WxMpProperties.ConfigStorage storage = wxMpProperties.getConfigStorage(); - WxMpProperties.RedisProperties redis = storage.getRedis(); + RedisProperties redis = storage.getRedis(); JedisPoolConfig config = new JedisPoolConfig(); if (redis.getMaxActive() != null) { @@ -114,6 +155,10 @@ private JedisPool getJedisPool() { } config.setTestOnBorrow(true); config.setTestWhileIdle(true); + if (StringUtils.isNotEmpty(redis.getSentinelIps())) { + Set sentinels = Sets.newHashSet(redis.getSentinelIps().split(",")); + return new JedisSentinelPool(redis.getSentinelName(), sentinels); + } 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/enums/HttpClientType.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java new file mode 100644 index 0000000000..1fa235e4af --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/HttpClientType.java @@ -0,0 +1,22 @@ +package com.binarywang.spring.starter.wxjava.mp.enums; + +/** + * httpclient类型. + * + * @author Binary Wang + * @date 2020-08-30 + */ +public enum HttpClientType { + /** + * HttpClient. + */ + HttpClient, + /** + * OkHttp. + */ + OkHttp, + /** + * JoddHttp. + */ + JoddHttp, +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/StorageType.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/StorageType.java new file mode 100644 index 0000000000..7dcb5a1157 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/enums/StorageType.java @@ -0,0 +1,22 @@ +package com.binarywang.spring.starter.wxjava.mp.enums; + +/** + * storage类型. + * + * @author Binary Wang + * @date 2020-08-30 + */ +public enum StorageType { + /** + * 内存. + */ + Memory, + /** + * redis(JedisClient). + */ + Jedis, + /** + * redis(RedisTemplate). + */ + RedisTemplate +} diff --git a/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/HostConfig.java b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/HostConfig.java new file mode 100644 index 0000000000..b8c0f1594f --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/HostConfig.java @@ -0,0 +1,18 @@ +package com.binarywang.spring.starter.wxjava.mp.properties; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class HostConfig implements Serializable { + + private static final long serialVersionUID = -4172767630740346001L; + + private String apiHost; + + private String openHost; + + private String mpHost; + +} 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 new file mode 100644 index 0000000000..59f82558d7 --- /dev/null +++ b/spring-boot-starters/wx-java-mp-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/mp/properties/RedisProperties.java @@ -0,0 +1,56 @@ +package com.binarywang.spring.starter.wxjava.mp.properties; + +import lombok.Data; + +import java.io.Serializable; + +/** + * redis 配置属性. + * + * @author Binary Wang + * @date 2020-08-30 + */ +@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; + + /** + * sentinel ips + */ + private String sentinelIps; + + /** + * sentinel name + */ + private String sentinelName; + + 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 60b39d9cdc..2e3abe223b 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 @@ -1,12 +1,14 @@ package com.binarywang.spring.starter.wxjava.mp.properties; +import com.binarywang.spring.starter.wxjava.mp.enums.HttpClientType; +import com.binarywang.spring.starter.wxjava.mp.enums.StorageType; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import java.io.Serializable; +import static com.binarywang.spring.starter.wxjava.mp.enums.StorageType.Memory; import static com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties.PREFIX; -import static com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties.StorageType.memory; /** @@ -39,6 +41,11 @@ public class WxMpProperties { */ private String aesKey; + /** + * 自定义host配置 + */ + private HostConfig hosts; + /** * 存储策略 */ @@ -51,7 +58,7 @@ public static class ConfigStorage implements Serializable { /** * 存储类型. */ - private StorageType type = memory; + private StorageType type = Memory; /** * 指定key前缀. @@ -66,7 +73,7 @@ public static class ConfigStorage implements Serializable { /** * http客户端类型. */ - private HttpClientType httpClientType = HttpClientType.httpclient; + private HttpClientType httpClientType = HttpClientType.HttpClient; /** * http代理主机. @@ -90,73 +97,4 @@ public static class ConfigStorage implements Serializable { } - public enum StorageType { - /** - * 内存. - */ - memory, - /** - * jedis. - */ - redis, - /** - * redis(JedisClient). - */ - 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/pom.xml b/spring-boot-starters/wx-java-open-spring-boot-starter/pom.xml index 5a98c83a40..462723d8a6 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.9.0 + 4.0.0 4.0.0 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 c97f00451d..25daf0d4f9 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 @@ -133,6 +133,7 @@ private RedissonClient getRedissonClient() { Config config = new Config(); config.useSingleServer() .setAddress("redis://" + redis.getHost() + ":" + redis.getPort()) + .setDatabase(redis.getDatabase()) .setPassword(redis.getPassword()); config.setTransportMode(TransportMode.NIO); return Redisson.create(config); 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 1a93eb54f9..50ed40360d 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.9.0 + 4.0.0 4.0.0 diff --git a/weixin-graal/pom.xml b/weixin-graal/pom.xml index 016544bc6c..8e01bca88f 100644 --- a/weixin-graal/pom.xml +++ b/weixin-graal/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 3.9.0 + 4.0.0 weixin-graal diff --git a/weixin-graal/src/main/java/cn/binarywang/wx/graal/GraalProcessor.java b/weixin-graal/src/main/java/com/github/binarywang/wx/graal/GraalProcessor.java similarity index 97% rename from weixin-graal/src/main/java/cn/binarywang/wx/graal/GraalProcessor.java rename to weixin-graal/src/main/java/com/github/binarywang/wx/graal/GraalProcessor.java index a7b02cae99..a983a51897 100644 --- a/weixin-graal/src/main/java/cn/binarywang/wx/graal/GraalProcessor.java +++ b/weixin-graal/src/main/java/com/github/binarywang/wx/graal/GraalProcessor.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.graal; +package com.github.binarywang.wx.graal; import lombok.Data; @@ -26,12 +26,12 @@ * @author outersky */ @SupportedAnnotationTypes("lombok.Data") -@SupportedSourceVersion(SourceVersion.RELEASE_7) +@SupportedSourceVersion(SourceVersion.RELEASE_8) public class GraalProcessor extends AbstractProcessor { private static final String REFLECTION_CONFIG_JSON = "reflection-config.json"; private static final String NATIVE_IMAGE_PROPERTIES = "native-image.properties"; - private SortedSet classSet = new TreeSet<>(); + private final SortedSet classSet = new TreeSet<>(); private String shortestPackageName = null; @Override diff --git a/weixin-graal/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/weixin-graal/src/main/resources/META-INF/services/javax.annotation.processing.Processor index fed7c4d9cd..f358b92ef9 100644 --- a/weixin-graal/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/weixin-graal/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1 +1 @@ -cn.binarywang.wx.graal.GraalProcessor +com.github.binarywang.wx.graal.GraalProcessor diff --git a/weixin-java-common/pom.xml b/weixin-java-common/pom.xml index 8786e0458d..0dddfb44b0 100644 --- a/weixin-java-common/pom.xml +++ b/weixin-java-common/pom.xml @@ -6,7 +6,7 @@ com.github.binarywang wx-java - 3.9.0 + 4.0.0 weixin-java-common @@ -50,7 +50,7 @@ org.slf4j jcl-over-slf4j - 1.7.24 + 1.7.30 @@ -165,7 +165,7 @@ 3.5.1 - cn.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor + com.github.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java index 99acd4f867..1e953d080b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxConsts.java @@ -121,31 +121,6 @@ public static class KefuMsgType { public static final String MINIPROGRAM_NOTICE = "miniprogram_notice"; } - /** - * 群机器人的消息类型. - */ - public static class GroupRobotMsgType { - /** - * 文本消息. - */ - public static final String TEXT = "text"; - - /** - * 图片消息. - */ - public static final String IMAGE = "image"; - - /** - * markdown消息. - */ - public static final String MARKDOWN = "markdown"; - - /** - * 图文消息(点击跳转到外链). - */ - public static final String NEWS = "news"; - } - /** * 表示是否是保密消息,0表示否,1表示是,默认0. */ diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateChecker.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateChecker.java index d7ac36c7c6..465f35434b 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateChecker.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxMessageInMemoryDuplicateChecker.java @@ -61,23 +61,20 @@ protected void checkBackgroundProcessStarted() { if (this.backgroundProcessStarted.getAndSet(true)) { return; } - Thread t = new Thread(new Runnable() { - @Override - public void run() { - try { - while (true) { - Thread.sleep(WxMessageInMemoryDuplicateChecker.this.clearPeriod); - Long now = System.currentTimeMillis(); - for (Map.Entry entry : - WxMessageInMemoryDuplicateChecker.this.msgId2Timestamp.entrySet()) { - if (now - entry.getValue() > WxMessageInMemoryDuplicateChecker.this.timeToLive) { - WxMessageInMemoryDuplicateChecker.this.msgId2Timestamp.entrySet().remove(entry); - } + Thread t = new Thread(() -> { + try { + while (true) { + Thread.sleep(WxMessageInMemoryDuplicateChecker.this.clearPeriod); + Long now = System.currentTimeMillis(); + for (Map.Entry entry : + WxMessageInMemoryDuplicateChecker.this.msgId2Timestamp.entrySet()) { + if (now - entry.getValue() > WxMessageInMemoryDuplicateChecker.this.timeToLive) { + WxMessageInMemoryDuplicateChecker.this.msgId2Timestamp.entrySet().remove(entry); } } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } }); t.setDaemon(true); diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ToJson.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ToJson.java new file mode 100644 index 0000000000..b8bfaabb01 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/ToJson.java @@ -0,0 +1,15 @@ +package me.chanjar.weixin.common.bean; + +/** + * 包含toJson()方法的接口. + * + * @author Binary Wang + * @date 2020-10-05 + */ +public interface ToJson { + /** + * 转换为json字符串 + * @return json字符串 + */ + String toJson(); +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxOAuth2UserInfo.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxOAuth2UserInfo.java new file mode 100644 index 0000000000..e647560026 --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/WxOAuth2UserInfo.java @@ -0,0 +1,66 @@ +package me.chanjar.weixin.common.bean; + + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * oauth2用户个人信息. + * + * @author Binary Wang + * @date 2020-10-11 + */ +@Data +public class WxOAuth2UserInfo implements Serializable { + private static final long serialVersionUID = 3181943506448954725L; + + /** + * openid 普通用户的标识,对当前开发者帐号唯一 + */ + private String openid; + /** + * nickname 普通用户昵称 + */ + private String nickname; + /** + * sex 普通用户性别,1为男性,2为女性 + */ + private Integer sex; + /** + * city 普通用户个人资料填写的城市 + */ + private String city; + + /** + * province 普通用户个人资料填写的省份 + */ + private String province; + /** + * country 国家,如中国为CN + */ + private String country; + /** + * headimgurl 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像), + * 用户没有头像时该项为空 + */ + @SerializedName("headimgurl") + private String headImgUrl; + /** + * unionid 用户统一标识。针对一个微信开放平台帐号下的应用,同一用户的unionid是唯一的。 + */ + @SerializedName("unionid") + private String unionId; + + /** + * privilege 用户特权信息,json数组,如微信沃卡用户为(chinaunicom) + */ + @SerializedName("privilege") + private String[] privileges; + + public static WxOAuth2UserInfo fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOAuth2UserInfo.class); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuRule.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuRule.java index 49d4e891c4..437b4ba9fe 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuRule.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/menu/WxMenuRule.java @@ -24,6 +24,7 @@ public class WxMenuRule implements Serializable { private String country; private String province; private String city; + @SerializedName("client_platform_type") private String clientPlatformType; private String language; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java similarity index 51% rename from weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java index 24b87d7a0d..0ab32d6574 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpOAuth2AccessToken.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/bean/oauth2/WxOAuth2AccessToken.java @@ -1,39 +1,48 @@ -package me.chanjar.weixin.mp.bean.result; - -import java.io.Serializable; +package me.chanjar.weixin.common.bean.oauth2; +import com.google.gson.annotations.SerializedName; import lombok.Data; -import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; /** * https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842 + * + * @author Daniel Qian */ @Data -public class WxMpOAuth2AccessToken implements Serializable { +public class WxOAuth2AccessToken implements Serializable { private static final long serialVersionUID = -1345910558078620805L; + @SerializedName("access_token") private String accessToken; + @SerializedName("expires_in") private int expiresIn = -1; + @SerializedName("refresh_token") private String refreshToken; + @SerializedName("openid") private String openId; + @SerializedName("scope") private String scope; /** * https://mp.weixin.qq.com/cgi-bin/announce?action=getannouncement&announce_id=11513156443eZYea&version=&lang=zh_CN. * 本接口在scope参数为snsapi_base时不再提供unionID字段。 */ + @SerializedName("unionid") private String unionId; - public static WxMpOAuth2AccessToken fromJson(String json) { - return WxMpGsonBuilder.create().fromJson(json, WxMpOAuth2AccessToken.class); + public static WxOAuth2AccessToken fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, WxOAuth2AccessToken.class); } @Override public String toString() { - return WxMpGsonBuilder.create().toJson(this); + return WxGsonBuilder.create().toJson(this); } } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxErrorException.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxErrorException.java index 6e9a2c538d..ca6c62611c 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxErrorException.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxErrorException.java @@ -6,7 +6,11 @@ public class WxErrorException extends Exception { private static final long serialVersionUID = -6357149550353160810L; - private WxError error; + private final WxError error; + + public WxErrorException(String message) { + this(WxError.builder().errorCode(-1).errorMsg(message).build()); + } public WxErrorException(WxError error) { super(error.toString()); @@ -18,6 +22,11 @@ public WxErrorException(WxError error, Throwable cause) { this.error = error; } + public WxErrorException(Throwable cause) { + super(cause.getMessage(), cause); + this.error = WxError.builder().errorCode(-1).errorMsg(cause.getMessage()).build(); + } + public WxError getError() { return this.error; } diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java index eced6027e9..2a9fe01845 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxMaErrorMsgEnum.java @@ -464,11 +464,200 @@ public enum WxMaErrorMsgEnum { CODE_85004(85004, "微信号已经绑定"), + /** + * 53010 + * 名称格式不合法 + */ + CODE_53010(53010, "名称格式不合法"), + + /** + * 53011 + * 名称检测命中频率限制 + */ + CODE_53011(53011, "名称检测命中频率限制"), + + /** + * 53012 + * 禁止使用该名称 + */ + CODE_53012(53012, "禁止使用该名称"), + + /** + * 53013 + * 公众号:名称与已有公众号名称重复;小程序:该名称与已有小程序名称重复 + */ + CODE_53013(53013, "公众号:名称与已有公众号名称重复;小程序:该名称与已有小程序名称重复"), + + /** + * 53014 + * 公众号:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A} + */ + CODE_53014(53014, "公众号:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A}"), + + /** + * 53015 + * 公众号:该名称与已有小程序名称重复,需与该小程序帐号相同主体才可申请;小程序:该名称与已有公众号名称重复,需与该公众号帐号相同主体才可申请 + */ + CODE_53015(53015, "公众号:该名称与已有小程序名称重复,需与该小程序帐号相同主体才可申请;小程序:该名称与已有公众号名称重复,需与该公众号帐号相同主体才可申请"), + + /** + * 53016 + * 公众号:该名称与已有多个小程序名称重复,暂不支持申请;小程序:该名称与已有多个公众号名称重复,暂不支持申请 + */ + CODE_53016(53016, "公众号:该名称与已有多个小程序名称重复,暂不支持申请;小程序:该名称与已有多个公众号名称重复,暂不支持申请"), + + /** + * 53017 + * 公众号:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A} + */ + CODE_53017(53017, "公众号:小程序已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A};小程序:公众号已有{名称 A+}时,需与该帐号相同主体才可申请{名称 A}"), + + /** + * 53018 + * 名称命中微信号 + */ + CODE_53018(53018, "名称命中微信号"), + + /** + * 53019 + * 名称在保护期内 + */ + CODE_53019(53019, "名称在保护期内"), + + /** + * 61070 + * 法人姓名与微信号不一致 name, wechat name not in accordance + */ + CODE_61070(61070, "法人姓名与微信号不一致"), + + /** + * 85015 + * 该账号不是小程序账号 + */ + CODE_85015(85015, "该账号不是小程序账号"), + + /** + * 85066 + * 链接错误 + */ + CODE_85066(85066, "链接错误"), + + /** + * 85068 + * 测试链接不是子链接 + */ + CODE_85068(85068, "测试链接不是子链接"), + + /** + * 85069 + * 校验文件失败 + */ + CODE_85069(85069, "校验文件失败"), + + /** + * 85070 + * 个人类型小程序无法设置二维码规则 + */ + CODE_85070(85070, "个人类型小程序无法设置二维码规则"), + + /** + * 85071 + * 已添加该链接,请勿重复添加 + */ + CODE_85071(85071, "已添加该链接,请勿重复添加"), + + /** + * 85072 + * 该链接已被占用 + */ + CODE_85072(85072, "该链接已被占用"), + + /** + * 85073 + * 二维码规则已满 + */ + CODE_85073(85073, "二维码规则已满"), + + /** + * 85074 + * 小程序未发布, 小程序必须先发布代码才可以发布二维码跳转规则 + */ + CODE_85074(85074, "小程序未发布, 小程序必须先发布代码才可以发布二维码跳转规则"), + + /** + * 85075 + * 个人类型小程序无法设置二维码规则 + */ + CODE_85075(85075, "个人类型小程序无法设置二维码规则"), + + /** + * 86004 + * 无效微信号 invalid wechat + */ + CODE_86004(86004, "无效微信号"), + + /** + * 89247 + * 内部错误 inner error + */ + CODE_89247(89247, "内部错误"), + + /** + * 89248 + * 企业代码类型无效,请选择正确类型填写 invalid code_type type + */ + CODE_89248(89248, "企业代码类型无效,请选择正确类型填写"), + + /** + * 89249 + * 该主体已有任务执行中,距上次任务 24h 后再试 task running + */ + CODE_89249(89249, "该主体已有任务执行中,距上次任务 24h 后再试"), + + /** + * 89250 + * 未找到该任务 task not found + */ + CODE_89250(89250, "未找到该任务"), + + + /** + * 89251 + * 待法人人脸核身校验 legal person checking + */ + CODE_89251(89251, "待法人人脸核身校验"), + + /** + * 89252 + * 法人&企业信息一致性校验中 front checking + */ + CODE_89252(89252, "法人&企业信息一致性校验中"), + + /** + * 89253 + * 缺少参数 lack of some params + */ + CODE_89253(89253, "缺少参数s"), + + + /** + * 89254 + * 第三方权限集不全,补全权限集全网发布后生效 lack of some component rights + */ + CODE_89254(89254, "第三方权限集不全,补全权限集全网发布后生效"), + + /** + * 89255 + * code参数无效,请检查code长度以及内容是否正确 code参数无效,请检查code长度以及内容是否正确_; + * 注意code_type的值不同需要传的code长度不一样 ;注意code_type的值不同需要传的code长度不一样 enterprise code_invalid invalid + */ + CODE_89255(89255, "code参数无效,请检查code长度以及内容是否正确_;注意code_type的值不同需要传的code长度不一样 ;注意code_type的值不同需要传的code长度不一样"), + // CODE_504002(-504002, "云函数未找到 Function not found"), ; - private int code; - private String msg; + private final int code; + private final String msg; WxMaErrorMsgEnum(int code, String msg) { this.code = code; diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxRuntimeException.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxRuntimeException.java new file mode 100644 index 0000000000..ccb8aecefb --- /dev/null +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/error/WxRuntimeException.java @@ -0,0 +1,23 @@ +package me.chanjar.weixin.common.error; + +/** + * WxJava专用的runtime exception. + * + * @author Binary Wang + * @date 2020-09-26 + */ +public class WxRuntimeException extends RuntimeException { + private static final long serialVersionUID = 4881698471192264412L; + + public WxRuntimeException(Throwable e) { + super(e); + } + + public WxRuntimeException(String msg) { + super(msg); + } + + public WxRuntimeException(String msg, Throwable e) { + super(msg, e); + } +} diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxImgProcService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxImgProcService.java similarity index 99% rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxImgProcService.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxImgProcService.java index c7d1bcfc14..a9ef694ad5 100644 --- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxImgProcService.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxImgProcService.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.common.api; +package me.chanjar.weixin.common.service; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.bean.imgproc.WxImgProcAiCropResult; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxOAuth2Service.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOAuth2Service.java similarity index 59% rename from weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxOAuth2Service.java rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOAuth2Service.java index 1fbb1f1a92..97a74d2c68 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxOAuth2Service.java +++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOAuth2Service.java @@ -1,8 +1,8 @@ -package me.chanjar.weixin.mp.api; +package me.chanjar.weixin.common.service; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; -import me.chanjar.weixin.mp.bean.result.WxMpUser; /** * oauth2 相关接口. @@ -17,12 +17,12 @@ public interface WxOAuth2Service { * 详情请见: http://mp.weixin.qq.com/wiki/index.php?title=网页授权获取用户基本信息 * * - * @param redirectURI 用户授权完成后的重定向链接,无需urlencode, 方法内会进行encode + * @param redirectUri 用户授权完成后的重定向链接,无需urlencode, 方法内会进行encode * @param scope scope * @param state state * @return url */ - String buildAuthorizationUrl(String redirectURI, String scope, String state); + String buildAuthorizationUrl(String redirectUri, String scope, String state); /** *
@@ -34,7 +34,18 @@ public interface WxOAuth2Service {
    * @return token对象
    * @throws WxErrorException .
    */
-  WxMpOAuth2AccessToken getAccessToken(String code) throws WxErrorException;
+  WxOAuth2AccessToken getAccessToken(String code) throws WxErrorException;
+
+  /**
+   * 用code换取oauth2的access token.
+   *
+   * @param appId     the appid
+   * @param appSecret the secret
+   * @param code      code
+   * @return token对象
+   * @throws WxErrorException .
+   */
+  WxOAuth2AccessToken getAccessToken(String appId, String appSecret, String code) throws WxErrorException;
 
   /**
    * 
@@ -45,7 +56,7 @@ public interface WxOAuth2Service {
    * @return 新的token对象
    * @throws WxErrorException .
    */
-  WxMpOAuth2AccessToken refreshAccessToken(String refreshToken) throws WxErrorException;
+  WxOAuth2AccessToken refreshAccessToken(String refreshToken) throws WxErrorException;
 
   /**
    * 
@@ -57,7 +68,7 @@ public interface WxOAuth2Service {
    * @return 用户对象
    * @throws WxErrorException .
    */
-  WxMpUser getUserInfo(WxMpOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException;
+  WxOAuth2UserInfo getUserInfo(WxOAuth2AccessToken oAuth2AccessToken, String lang) throws WxErrorException;
 
   /**
    * 
@@ -67,6 +78,6 @@ public interface WxOAuth2Service {
    * @param oAuth2AccessToken token对象
    * @return 是否有效
    */
-  boolean validateAccessToken(WxMpOAuth2AccessToken oAuth2AccessToken);
+  boolean validateAccessToken(WxOAuth2AccessToken oAuth2AccessToken);
 
 }
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxOcrService.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java
similarity index 98%
rename from weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxOcrService.java
rename to weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java
index 480ed3e95b..7b4fe337e5 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/api/WxOcrService.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/service/WxOcrService.java
@@ -1,4 +1,4 @@
-package me.chanjar.weixin.common.api;
+package me.chanjar.weixin.common.service;
 
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.bean.ocr.WxOcrBankCardResult;
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
index fc49bfd9ce..24897561cc 100644
--- 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
@@ -1,5 +1,7 @@
 package me.chanjar.weixin.common.service;
 
+import com.google.gson.JsonObject;
+import me.chanjar.weixin.common.bean.ToJson;
 import me.chanjar.weixin.common.error.WxErrorException;
 
 /**
@@ -38,4 +40,24 @@ public interface WxService {
    * @throws WxErrorException 异常
    */
   String post(String url, Object obj) throws WxErrorException;
+
+  /**
+   * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求.
+   *
+   * @param url        请求接口地址
+   * @param jsonObject 请求对象
+   * @return 接口响应字符串
+   * @throws WxErrorException 异常
+   */
+  String post(String url, JsonObject jsonObject) throws WxErrorException;
+
+  /**
+   * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求.
+   *
+   * @param url 请求接口地址
+   * @param obj 请求对象,实现了ToJson接口
+   * @return 接口响应字符串
+   * @throws WxErrorException 异常
+   */
+  String post(String url, ToJson 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 591b7025dd..290a0c04f7 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
@@ -183,18 +183,15 @@ protected InternalSession getNewSession() {
   public void add(InternalSession session) {
     // 当第一次有session创建的时候,开启session清理线程
     if (!this.backgroundProcessStarted.getAndSet(true)) {
-      Thread t = new Thread(new Runnable() {
-        @Override
-        public void run() {
-          while (true) {
-            try {
-              // 每秒清理一次
-              Thread.sleep(StandardSessionManager.this.backgroundProcessorDelay * 1000L);
-              backgroundProcess();
-            } catch (InterruptedException e) {
-              Thread.currentThread().interrupt();
-              StandardSessionManager.this.log.error("SessionManagerImpl.backgroundProcess error", e);
-            }
+      Thread t = new Thread(() -> {
+        while (true) {
+          try {
+            // 每秒清理一次
+            Thread.sleep(StandardSessionManager.this.backgroundProcessorDelay * 1000L);
+            backgroundProcess();
+          } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            StandardSessionManager.this.log.error("SessionManagerImpl.backgroundProcess error", e);
           }
         }
       });
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 768f2e5324..73b6cff368 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
@@ -58,7 +58,7 @@ public static void checkRequiredFields(Object bean) throws WxErrorException {
     if (!requiredFields.isEmpty()) {
       String msg = String.format("必填字段【%s】必须提供值!", requiredFields);
       log.debug(msg);
-      throw new WxErrorException(WxError.builder().errorMsg(msg).build());
+      throw new WxErrorException(msg);
     }
   }
 
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/LogExceptionHandler.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/LogExceptionHandler.java
index 7487a0fe29..35b0eea822 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/LogExceptionHandler.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/LogExceptionHandler.java
@@ -1,20 +1,17 @@
 package me.chanjar.weixin.common.util;
 
+import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.api.WxErrorExceptionHandler;
 import me.chanjar.weixin.common.error.WxErrorException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 
+/**
+ * @author Daniel Qian
+ */
+@Slf4j
 public class LogExceptionHandler implements WxErrorExceptionHandler {
-
-  private Logger log = LoggerFactory.getLogger(WxErrorExceptionHandler.class);
-
   @Override
   public void handle(WxErrorException e) {
-
-    this.log.error("Error happens", e);
-
+    log.error("Error happens", e);
   }
 
 }
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 c2ffdb001b..91c6b8f2ec 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
@@ -3,10 +3,9 @@
 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 me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import org.dom4j.*;
 import org.dom4j.io.SAXReader;
 import org.dom4j.tree.DefaultText;
 import org.xml.sax.SAXException;
@@ -43,21 +42,23 @@ public static Map xml2Map(String xmlString) {
         map.put(element.getName(), element2MapOrString(element));
       }
     } catch (DocumentException | SAXException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
 
     return map;
   }
 
   private static Object element2MapOrString(Element element) {
-    Map result = Maps.newHashMap();
 
     final List content = element.content();
-    if (content.size() <= 1) {
+    final Set names = names(content);
+
+    // 判断节点下有无非文本节点(非Text和CDATA),如无,直接取Text文本内容
+    if (names.size() < 1) {
       return element.getText();
     }
 
-    final Set names = names(content);
+    Map result = Maps.newHashMap();
     if (names.size() == 1) {
       // 说明是个列表,各个子对象是相同的name
       List list = Lists.newArrayList();
@@ -90,7 +91,8 @@ private static Object element2MapOrString(Element element) {
   private static Set names(List nodes) {
     Set names = Sets.newHashSet();
     for (Node node : nodes) {
-      if (node instanceof DefaultText) {
+      // 如果节点类型是Text或CDATA跳过
+      if (node instanceof DefaultText || node instanceof CDATA) {
         continue;
       }
       names.add(node.getName());
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
index 0103fc7f06..d47414fefc 100755
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/crypto/WxCryptUtil.java
@@ -14,6 +14,7 @@
 
 import com.google.common.base.CharMatcher;
 import com.google.common.io.BaseEncoding;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import org.apache.commons.codec.binary.Base64;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
@@ -77,7 +78,7 @@ private static String extractEncryptPart(String xml) {
       Element root = document.getDocumentElement();
       return root.getElementsByTagName("Encrypt").item(0).getTextContent();
     } catch (Exception e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
   }
 
@@ -198,7 +199,7 @@ protected String encrypt(String randomStr, String plainText) {
       // 使用BASE64对加密后的字符串进行编码
       return BASE64.encodeToString(encrypted);
     } catch (Exception e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
   }
 
@@ -224,7 +225,7 @@ public String decrypt(String msgSignature, String timeStamp, String nonce, Strin
     // 验证安全签名
     String signature = SHA1.gen(this.token, timeStamp, nonce, cipherText);
     if (!signature.equals(msgSignature)) {
-      throw new RuntimeException("加密消息签名校验失败");
+      throw new WxRuntimeException("加密消息签名校验失败");
     }
 
     // 解密
@@ -252,7 +253,7 @@ public String decrypt(String cipherText) {
       // 解密
       original = cipher.doFinal(encrypted);
     } catch (Exception e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
 
     String xmlContent;
@@ -269,12 +270,12 @@ public String decrypt(String cipherText) {
       xmlContent = new String(Arrays.copyOfRange(bytes, 20, 20 + xmlLength), CHARSET);
       fromAppid = new String(Arrays.copyOfRange(bytes, 20 + xmlLength, bytes.length), CHARSET);
     } catch (Exception e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
 
     // appid不相同的情况 暂时忽略这段判断
 //    if (!fromAppid.equals(this.appidOrCorpid)) {
-//      throw new RuntimeException("AppID不正确,请核实!");
+//      throw new WxRuntimeException("AppID不正确,请核实!");
 //    }
 
     return xmlContent;
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java
index a00c9cbade..d60f5cedd5 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/fs/FileUtils.java
@@ -1,11 +1,16 @@
 package me.chanjar.weixin.common.util.fs;
 
+import org.apache.commons.io.IOUtils;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.nio.file.Files;
 import java.util.Base64;
 
+import static org.apache.commons.io.FileUtils.openOutputStream;
+
 public class FileUtils {
 
   /**
@@ -20,10 +25,16 @@ public static File createTmpFile(InputStream inputStream, String name, String ex
     File resultFile = File.createTempFile(name, '.' + ext, tmpDirFile);
 
     resultFile.deleteOnExit();
-    org.apache.commons.io.FileUtils.copyToFile(inputStream, resultFile);
+    copyToFile(inputStream, resultFile);
     return resultFile;
   }
 
+  private static void copyToFile(final InputStream source, final File destination) throws IOException {
+    try (InputStream in = source; OutputStream out = openOutputStream(destination)) {
+      IOUtils.copy(in, out);
+    }
+  }
+
   /**
    * 创建临时文件.
    *
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java
index 0d68518849..f338ece672 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/HttpResponseProxy.java
@@ -58,7 +58,7 @@ public String getFileName() throws WxErrorException {
   private String getFileName(CloseableHttpResponse response) throws WxErrorException {
     Header[] contentDispositionHeader = response.getHeaders("Content-disposition");
     if (contentDispositionHeader == null || contentDispositionHeader.length == 0) {
-      throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").errorCode(99999).build());
+      throw new WxErrorException("无法获取到文件名,Content-disposition为空");
     }
 
     return this.extractFileNameFromContentString(contentDispositionHeader[0].getValue());
@@ -76,7 +76,7 @@ private String getFileName(Response response) throws WxErrorException {
 
   private String extractFileNameFromContentString(String content) throws WxErrorException {
     if (content == null || content.length() == 0) {
-      throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").errorCode(99999).build());
+      throw new WxErrorException("无法获取到文件名,content为空");
     }
 
     Matcher m = PATTERN.matcher(content);
@@ -84,7 +84,7 @@ private String extractFileNameFromContentString(String content) throws WxErrorEx
       return m.group(1);
     }
 
-    throw new WxErrorException(WxError.builder().errorMsg("无法获取到文件名").errorCode(99999).build());
+    throw new WxErrorException("无法获取到文件名,header信息有问题");
   }
 
 }
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 a0ce7a17e2..266fd226e7 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
@@ -1,7 +1,5 @@
 package me.chanjar.weixin.common.util.http;
 
-import java.io.IOException;
-
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -9,6 +7,8 @@
 import me.chanjar.weixin.common.util.http.jodd.JoddHttpSimpleGetRequestExecutor;
 import me.chanjar.weixin.common.util.http.okhttp.OkHttpSimpleGetRequestExecutor;
 
+import java.io.IOException;
+
 /**
  * 简单的GET请求执行器.
  * 请求的参数是String, 返回的结果也是String
@@ -27,7 +27,7 @@ public void execute(String uri, String data, ResponseHandler handler, Wx
     handler.handle(this.execute(uri, data, wxType));
   }
 
-  public static RequestExecutor create(RequestHttp requestHttp) {
+  public static RequestExecutor create(RequestHttp requestHttp) {
     switch (requestHttp.getRequestType()) {
       case APACHE_HTTP:
         return new ApacheSimpleGetRequestExecutor(requestHttp);
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 c0952b32d4..0366b156af 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
@@ -44,7 +44,7 @@ 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());
+      throw new WxErrorException("无响应内容");
     }
 
     if (responseContent.startsWith("")) {
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 5c67cbffe1..00df2a640a 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
@@ -19,6 +19,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
 
 /**
  * .
@@ -47,7 +48,7 @@ public File execute(String uri, String queryParam, WxType wxType) throws WxError
     request.withConnectionProvider(requestHttp.getRequestHttpClient());
 
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
 
     String contentType = response.header("Content-Type");
     if (contentType != null && contentType.startsWith("application/json")) {
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java
index 93f25fb740..89ea05a029 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/jodd/JoddHttpMediaUploadRequestExecutor.java
@@ -14,6 +14,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * .
@@ -35,7 +36,7 @@ public WxMediaUploadResult execute(String uri, File file, WxType wxType) throws
     request.withConnectionProvider(requestHttp.getRequestHttpClient());
     request.form("media", file);
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
 
     String responseContent = response.bodyText();
     WxError error = WxError.fromJson(responseContent, wxType);
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 e8adad6b66..5960274eb6 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
@@ -11,6 +11,7 @@
 import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * .
@@ -38,7 +39,7 @@ public String execute(String uri, String queryParam, WxType wxType) throws WxErr
     }
     request.withConnectionProvider(requestHttp.getRequestHttpClient());
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
 
     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 7b2f5f61ef..50360cae56 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
@@ -11,6 +11,7 @@
 import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * .
@@ -37,7 +38,7 @@ public String execute(String uri, String postEntity, WxType wxType) throws WxErr
       request.bodyText(postEntity);
     }
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
 
     return this.handleResponse(wxType, response.bodyText());
   }
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java
index 882853945a..ee330b03e2 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/json/GsonHelper.java
@@ -4,6 +4,8 @@
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import jodd.util.MathUtil;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 
 import java.util.List;
 
@@ -151,4 +153,54 @@ public static Long[] getLongArray(JsonObject o, String string) {
   public static JsonArray getAsJsonArray(JsonElement element) {
     return element == null ? null : element.getAsJsonArray();
   }
+
+  /**
+   * 快速构建JsonObject对象,批量添加一堆属性
+   *
+   * @param keyOrValue 包含key或value的数组
+   * @return JsonObject对象.
+   */
+  public static JsonObject buildJsonObject(Object... keyOrValue) {
+    JsonObject result = new JsonObject();
+    put(result, keyOrValue);
+    return result;
+  }
+
+  /**
+   * 批量向JsonObject对象中添加属性
+   *
+   * @param jsonObject 原始JsonObject对象
+   * @param keyOrValue 包含key或value的数组
+   */
+  public static void put(JsonObject jsonObject, Object... keyOrValue) {
+    if (MathUtil.isOdd(keyOrValue.length)) {
+      throw new WxRuntimeException("参数个数必须为偶数");
+    }
+
+    for (int i = 0; i < keyOrValue.length / 2; i++) {
+      final Object key = keyOrValue[2 * i];
+      final Object value = keyOrValue[2 * i + 1];
+      if (value == null) {
+        jsonObject.add(key.toString(), null);
+        continue;
+      }
+
+      if (value instanceof Boolean) {
+        jsonObject.addProperty(key.toString(), (Boolean) value);
+      } else if (value instanceof Character) {
+        jsonObject.addProperty(key.toString(), (Character) value);
+      } else if (value instanceof Number) {
+        jsonObject.addProperty(key.toString(), (Number) value);
+      } else if (value instanceof JsonElement) {
+        jsonObject.add(key.toString(), (JsonElement) value);
+      } else if (value instanceof List) {
+        JsonArray array = new JsonArray();
+        ((List) value).forEach(a -> array.add(a.toString()));
+        jsonObject.add(key.toString(), array);
+      } else {
+        jsonObject.addProperty(key.toString(), value.toString());
+      }
+    }
+
+  }
 }
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
index 074f9d9351..7261d4a118 100644
--- 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
@@ -5,6 +5,7 @@
 import java.util.concurrent.locks.Lock;
 
 import com.github.jedis.lock.JedisLock;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import redis.clients.jedis.Jedis;
 import redis.clients.jedis.util.Pool;
 
@@ -26,10 +27,10 @@ public JedisDistributedLock(Pool jedisPool, String key){
   public void lock() {
     try (Jedis jedis = jedisPool.getResource()) {
       if (!lock.acquire(jedis)) {
-        throw new RuntimeException("acquire timeouted");
+        throw new WxRuntimeException("acquire timeouted");
       }
     } catch (InterruptedException e) {
-      throw new RuntimeException("lock failed", e);
+      throw new WxRuntimeException("lock failed", e);
     }
   }
 
@@ -37,7 +38,7 @@ public void lock() {
   public void lockInterruptibly() throws InterruptedException {
     try (Jedis jedis = jedisPool.getResource()) {
       if (!lock.acquire(jedis)) {
-        throw new RuntimeException("acquire timeouted");
+        throw new WxRuntimeException("acquire timeouted");
       }
     }
   }
@@ -47,7 +48,7 @@ public boolean tryLock() {
     try (Jedis jedis = jedisPool.getResource()) {
       return lock.acquire(jedis);
     } catch (InterruptedException e) {
-      throw new RuntimeException("lock failed", e);
+      throw new WxRuntimeException("lock failed", e);
     }
   }
 
@@ -67,7 +68,7 @@ public void unlock() {
 
   @Override
   public Condition newCondition() {
-    throw new RuntimeException("unsupported method");
+    throw new WxRuntimeException("unsupported method");
   }
 
 }
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java
index dfac1c28fb..8c5ccc26fd 100644
--- a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLock.java
@@ -3,8 +3,6 @@
 import lombok.Getter;
 import lombok.NonNull;
 import org.jetbrains.annotations.NotNull;
-import org.springframework.dao.DataAccessException;
-import org.springframework.data.redis.connection.RedisConnection;
 import org.springframework.data.redis.connection.RedisStringCommands;
 import org.springframework.data.redis.core.RedisCallback;
 import org.springframework.data.redis.core.StringRedisTemplate;
@@ -76,13 +74,10 @@ public boolean tryLock() {
     }
     final byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);
     final byte[] valueBytes = value.getBytes(StandardCharsets.UTF_8);
-    List redisResults = redisTemplate.executePipelined(new RedisCallback() {
-      @Override
-      public String doInRedis(RedisConnection connection) throws DataAccessException {
-        connection.set(keyBytes, valueBytes, Expiration.milliseconds(leaseMilliseconds), RedisStringCommands.SetOption.SET_IF_ABSENT);
-        connection.get(keyBytes);
-        return null;
-      }
+    List redisResults = redisTemplate.executePipelined((RedisCallback) connection -> {
+      connection.set(keyBytes, valueBytes, Expiration.milliseconds(leaseMilliseconds), RedisStringCommands.SetOption.SET_IF_ABSENT);
+      connection.get(keyBytes);
+      return null;
     });
     Object currentLockSecret = redisResults.size() > 1 ? redisResults.get(1) : redisResults.get(0);
     return currentLockSecret != null && currentLockSecret.toString().equals(value);
diff --git a/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/StringArrayConverter.java b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/StringArrayConverter.java
new file mode 100644
index 0000000000..44d2926f42
--- /dev/null
+++ b/weixin-java-common/src/main/java/me/chanjar/weixin/common/util/xml/StringArrayConverter.java
@@ -0,0 +1,30 @@
+package me.chanjar.weixin.common.util.xml;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.thoughtworks.xstream.converters.basic.StringConverter;
+
+
+/**
+ * String 数组转换
+ * @author chily.lin
+ */
+public class StringArrayConverter  extends StringConverter {
+  @Override
+  public boolean canConvert(Class type) {
+    return type == String[].class;
+  }
+
+  @Override
+  public String toString(Object obj) {
+    return "";
+  }
+
+  @Override
+  public Object fromString(String str) {
+    final Iterable iterable = Splitter.on(",").split(str);
+    String[] results = Iterables.toArray(iterable, String.class);
+    return results;
+  }
+}
diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonHelperTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonHelperTest.java
new file mode 100644
index 0000000000..396862e708
--- /dev/null
+++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/json/GsonHelperTest.java
@@ -0,0 +1,139 @@
+package me.chanjar.weixin.common.util.json;
+
+import com.google.gson.JsonObject;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * GsonHelper 的单元测试.
+ *
+ * @author Binary Wang
+ * @date 2020-09-04
+ */
+public class GsonHelperTest {
+
+  @Test
+  public void testIsNull() {
+  }
+
+  @Test
+  public void testIsNotNull() {
+  }
+
+  @Test
+  public void testGetLong() {
+  }
+
+  @Test
+  public void testGetPrimitiveLong() {
+  }
+
+  @Test
+  public void testGetInteger() {
+  }
+
+  @Test
+  public void testGetPrimitiveInteger() {
+  }
+
+  @Test
+  public void testGetDouble() {
+  }
+
+  @Test
+  public void testGetPrimitiveDouble() {
+  }
+
+  @Test
+  public void testGetFloat() {
+  }
+
+  @Test
+  public void testGetPrimitiveFloat() {
+  }
+
+  @Test
+  public void testGetBoolean() {
+  }
+
+  @Test
+  public void testGetString() {
+  }
+
+  @Test
+  public void testGetAsString() {
+  }
+
+  @Test
+  public void testGetAsLong() {
+  }
+
+  @Test
+  public void testGetAsPrimitiveLong() {
+  }
+
+  @Test
+  public void testGetAsInteger() {
+  }
+
+  @Test
+  public void testGetAsPrimitiveInt() {
+  }
+
+  @Test
+  public void testGetAsBoolean() {
+  }
+
+  @Test
+  public void testGetAsPrimitiveBool() {
+  }
+
+  @Test
+  public void testGetAsDouble() {
+  }
+
+  @Test
+  public void testGetAsPrimitiveDouble() {
+  }
+
+  @Test
+  public void testGetAsFloat() {
+  }
+
+  @Test
+  public void testGetAsPrimitiveFloat() {
+  }
+
+  @Test
+  public void testGetIntArray() {
+  }
+
+  @Test
+  public void testGetStringArray() {
+  }
+
+  @Test
+  public void testGetLongArray() {
+  }
+
+  @Test
+  public void testGetAsJsonArray() {
+  }
+
+  @Test
+  public void testBuildSimpleJsonObject() {
+    try {
+      GsonHelper.buildJsonObject(1, 2, 3);
+    } catch (RuntimeException e) {
+      assertThat(e.getMessage()).isEqualTo("参数个数必须为偶数");
+    }
+
+    System.out.println(GsonHelper.buildJsonObject(1, 2));
+    System.out.println(GsonHelper.buildJsonObject(1, null));
+    System.out.println(GsonHelper.buildJsonObject("int", 1, "float", 2.1f, "double", 2.5));
+    System.out.println(GsonHelper.buildJsonObject("boolean", true, "string", "1av"));
+    System.out.println(GsonHelper.buildJsonObject(1, true, "jsonElement", new JsonObject()));
+    System.out.println(GsonHelper.buildJsonObject("num", 2, "string", "cde", "char", 'a', "bool", true));
+  }
+}
diff --git a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java
index 50a17ed94b..4b65e31f0b 100644
--- a/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java
+++ b/weixin-java-common/src/test/java/me/chanjar/weixin/common/util/locks/RedisTemplateSimpleDistributedLockTest.java
@@ -1,6 +1,6 @@
 package me.chanjar.weixin.common.util.locks;
 
-import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.testng.annotations.BeforeTest;
@@ -12,6 +12,7 @@
 
 import static org.testng.Assert.*;
 
+@Slf4j
 @Test(enabled = false)
 public class RedisTemplateSimpleDistributedLockTest {
 
@@ -40,19 +41,19 @@ public void testLockExclusive() throws InterruptedException {
     final CountDownLatch endLatch = new CountDownLatch(threadSize);
 
     for (int i = 0; i < threadSize; i++) {
-      new Thread(new Runnable() {
-        @SneakyThrows
-        @Override
-        public void run() {
+      new Thread(() -> {
+        try {
           startLatch.await();
+        } catch (InterruptedException e) {
+          log.error("unexpected exception", e);
+        }
 
-          redisLock.lock();
-          assertEquals(lockCurrentExecuteCounter.incrementAndGet(), 1, "临界区同时只能有一个线程执行");
-          lockCurrentExecuteCounter.decrementAndGet();
-          redisLock.unlock();
+        redisLock.lock();
+        assertEquals(lockCurrentExecuteCounter.incrementAndGet(), 1, "临界区同时只能有一个线程执行");
+        lockCurrentExecuteCounter.decrementAndGet();
+        redisLock.unlock();
 
-          endLatch.countDown();
-        }
+        endLatch.countDown();
       }).start();
       startLatch.countDown();
     }
diff --git a/weixin-java-cp/pom.xml b/weixin-java-cp/pom.xml
index 5d9fdae34c..969d9c850b 100644
--- a/weixin-java-cp/pom.xml
+++ b/weixin-java-cp/pom.xml
@@ -7,7 +7,7 @@
   
     com.github.binarywang
     wx-java
-    3.9.0
+    4.0.0
   
 
   weixin-java-cp
@@ -83,6 +83,11 @@
       assertj-guava
       test
     
+    
+      com.github.dreamhead
+      moco-runner
+      test
+    
   
 
   
@@ -114,7 +119,7 @@
             3.5.1
             
               
-                cn.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor
+                com.github.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor
               
               
                 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java
new file mode 100644
index 0000000000..7ee8210084
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpAgentWorkBenchService.java
@@ -0,0 +1,18 @@
+package me.chanjar.weixin.cp.api;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.bean.WxCpAgentWorkBench;
+
+/**
+ * @author songshiyu
+ * @date : create in 16:16 2020/9/27
+ * @description: 工作台自定义展示:https://work.weixin.qq.com/api/doc/90000/90135/92535
+ */
+public interface WxCpAgentWorkBenchService {
+
+  void setWorkBenchTemplate(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException;
+
+  String getWorkBenchTemplate(Long agentid) throws WxErrorException;
+
+  void setWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpChatService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpChatService.java
index 462ec75071..9c825d8917 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpChatService.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpChatService.java
@@ -1,7 +1,7 @@
 package me.chanjar.weixin.cp.api;
 
 import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.cp.bean.WxCpAppChatMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpAppChatMessage;
 import me.chanjar.weixin.cp.bean.WxCpChat;
 
 import java.util.List;
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 a386b0ead2..231e0bfa3e 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
@@ -4,6 +4,8 @@
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.cp.bean.WxCpBaseResp;
 import me.chanjar.weixin.cp.bean.external.*;
+import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactBatchInfo;
+import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo;
 
 import java.util.Date;
 import java.util.List;
@@ -109,7 +111,7 @@ public interface WxCpExternalContactService {
    * @deprecated 建议使用 {@link #getContactDetail(String)}
    */
   @Deprecated
-  WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException;
+  WxCpExternalContactInfo getExternalContact(String userId) throws WxErrorException;
 
   /**
    * 获取客户详情.
@@ -130,7 +132,46 @@ public interface WxCpExternalContactService {
    * @return . contact detail
    * @throws WxErrorException .
    */
-  WxCpUserExternalContactInfo getContactDetail(String userId) throws WxErrorException;
+  WxCpExternalContactInfo getContactDetail(String userId) throws WxErrorException;
+
+  /**
+   * 批量获取客户详情.
+   * 
+   *
+   * 企业/第三方可通过此接口获取指定成员添加的客户信息列表。
+   *
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/batch/get_by_user?access_token=ACCESS_TOKEN
+   *
+   * 权限说明:
+   *
+   * 企业需要使用“客户联系”secret或配置到“可调用应用”列表中的自建应用secret所获取的accesstoken来调用(accesstoken如何获取?);
+   * 第三方/自建应用调用时,返回的跟进人follow_user仅包含应用可见范围之内的成员。
+   * 
+ * + * @param userId 企业成员的userid,注意不是外部联系人的帐号 + * @param cursor the cursor + * @param limit the limit + * @return wx cp user external contact batch info + * @throws WxErrorException . + */ + WxCpExternalContactBatchInfo getContactDetailBatch(String userId, String cursor, + Integer limit) + throws WxErrorException; + + /** + * 修改客户备注信息. + *
+   * 企业可通过此接口修改指定用户添加的客户的备注信息。
+   * 请求方式: POST(HTTP)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/externalcontact/remark?access_token=ACCESS_TOKEN
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92115
+   * 
+ * + * @param request 备注信息请求 + * @throws WxErrorException . + */ + void updateRemark(WxCpUpdateRemarkRequest request) throws WxErrorException; /** * 获取客户列表. diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java index 007dff78fc..b5a9579e0d 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpGroupRobotService.java @@ -31,7 +31,7 @@ public interface WxCpGroupRobotService { * @param content markdown内容,最长不超过4096个字节,必须是utf8编码 * @throws WxErrorException 异常 */ - void sendMarkDown(String content) throws WxErrorException; + void sendMarkdown(String content) throws WxErrorException; /** * 发送image类型的消息 @@ -49,4 +49,43 @@ public interface WxCpGroupRobotService { * @throws WxErrorException 异常 */ void sendNews(List articleList) throws WxErrorException; + + /** + * 发送text类型的消息 + * + * @param webhookUrl webhook地址 + * @param content 文本内容,最长不超过2048个字节,必须是utf8编码 + * @param mentionedList userId的列表,提醒群中的指定成员(@某个成员),@all表示提醒所有人,如果开发者获取不到userId,可以使用mentioned_mobile_list + * @param mobileList 手机号列表,提醒手机号对应的群成员(@某个成员),@all表示提醒所有人 + * @throws WxErrorException 异常 + */ + void sendText(String webhookUrl, String content, List mentionedList, List mobileList) throws WxErrorException; + + /** + * 发送markdown类型的消息 + * + * @param webhookUrl webhook地址 + * @param content markdown内容,最长不超过4096个字节,必须是utf8编码 + * @throws WxErrorException 异常 + */ + void sendMarkdown(String webhookUrl, String content) throws WxErrorException; + + /** + * 发送image类型的消息 + * + * @param webhookUrl webhook地址 + * @param base64 图片内容的base64编码 + * @param md5 图片内容(base64编码前)的md5值 + * @throws WxErrorException 异常 + */ + void sendImage(String webhookUrl, String base64, String md5) throws WxErrorException; + + /** + * 发送news类型的消息 + * + * @param webhookUrl webhook地址 + * @param articleList 图文消息,支持1到8条图文 + * @throws WxErrorException 异常 + */ + void sendNews(String webhookUrl, List articleList) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java new file mode 100644 index 0000000000..fae0a6a0d6 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpMessageService.java @@ -0,0 +1,56 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.message.WxCpLinkedCorpMessage; +import me.chanjar.weixin.cp.bean.message.WxCpMessage; +import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult; +import me.chanjar.weixin.cp.bean.message.WxCpMessageSendStatistics; + +/** + * 消息推送接口. + * + * @author Binary Wang + * @date 2020 -08-30 + */ +public interface WxCpMessageService { + /** + *
+   * 发送消息
+   * 详情请见: https://work.weixin.qq.com/api/doc/90000/90135/90236
+   * 
+ * + * @param message 要发送的消息对象 + * @return the wx cp message send result + * @throws WxErrorException the wx error exception + */ + WxCpMessageSendResult send(WxCpMessage message) throws WxErrorException; + + /** + *
+   * 查询应用消息发送统计
+   * 请求方式:POST(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/message/get_statistics?access_token=ACCESS_TOKEN
+   *
+   * 详情请见: https://work.weixin.qq.com/api/doc/90000/90135/92369
+   * 
+ * + * @param timeType 查询哪天的数据,0:当天;1:昨天。默认为0。 + * @return 统计结果 + * @throws WxErrorException the wx error exception + */ + WxCpMessageSendStatistics getStatistics(int timeType) throws WxErrorException; + + /** + *
+   * 互联企业的应用支持推送文本、图片、视频、文件、图文等类型。
+   *
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/linkedcorp/message/send?access_token=ACCESS_TOKEN
+   * 文章地址:https://work.weixin.qq.com/api/doc/90000/90135/90250
+   * 
+ * + * @param message 要发送的消息对象 + * @return the wx cp message send result + * @throws WxErrorException the wx error exception + */ + WxCpMessageSendResult sendLinkedCorpMessage(WxCpLinkedCorpMessage message) throws WxErrorException; +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaCalendarService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaCalendarService.java new file mode 100644 index 0000000000..91010ce212 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpOaCalendarService.java @@ -0,0 +1,83 @@ +package me.chanjar.weixin.cp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.bean.oa.calendar.WxCpOaCalendar; + +import java.util.List; + +/** + * 企业微信日历接口. + * + * @author Binary Wang + * @date 2020-09-20 + */ +public interface WxCpOaCalendarService { + /** + * 创建日历. + *
+   * 该接口用于通过应用在企业内创建一个日历。
+   * 注: 企业微信需要更新到3.0.2及以上版本
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/calendar/add?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92618
+   * 
+ * + * @param calendar 日历对象 + * @return 日历ID + * @throws WxErrorException . + */ + String add(WxCpOaCalendar calendar) throws WxErrorException; + + /** + * 更新日历. + *
+   * 该接口用于修改指定日历的信息。
+   * 注意,更新操作是覆盖式,而不是增量式
+   * 企业微信需要更新到3.0.2及以上版本
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/calendar/update?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92619
+   * 
+ * + * @param calendar 日历对象 + * @throws WxErrorException . + */ + void update(WxCpOaCalendar calendar) throws WxErrorException; + + /** + * 获取日历. + *
+   * 该接口用于获取应用在企业内创建的日历信息。
+   *
+   * 注: 企业微信需要更新到3.0.2及以上版本
+   *
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/calendar/get?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92621
+   * 
+ * + * @param calIds 日历id列表 + * @return 日历对象列表 + * @throws WxErrorException . + */ + List get(List calIds) throws WxErrorException; + + /** + * 删除日历. + *
+   * 该接口用于删除指定日历。
+   * 注: 企业微信需要更新到3.0.2及以上版本
+   * 请求方式: POST(HTTPS)
+   * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/oa/calendar/del?access_token=ACCESS_TOKEN
+   *
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/92620
+   * 
+ * + * @param calId 日历id + * @throws WxErrorException . + */ + void delete(String calId) throws WxErrorException; +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java index 036265815b..1933c14692 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java @@ -1,15 +1,16 @@ package me.chanjar.weixin.cp.api; +import com.google.gson.JsonObject; +import me.chanjar.weixin.common.bean.ToJson; import me.chanjar.weixin.common.bean.WxJsapiSignature; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.service.WxService; import me.chanjar.weixin.common.session.WxSession; import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; -import me.chanjar.weixin.cp.bean.WxCpMessage; -import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; import me.chanjar.weixin.cp.bean.WxCpProviderToken; import me.chanjar.weixin.cp.config.WxCpConfigStorage; @@ -18,7 +19,7 @@ * * @author chanjaster */ -public interface WxCpService { +public interface WxCpService extends WxService { /** *
    * 验证推送过来的消息的正确性
@@ -29,13 +30,16 @@ public interface WxCpService {
    * @param timestamp    时间戳
    * @param nonce        随机数
    * @param data         微信传输过来的数据,有可能是echoStr,有可能是xml消息
+   * @return the boolean
    */
   boolean checkSignature(String msgSignature, String timestamp, String nonce, String data);
 
   /**
    * 获取access_token, 不强制刷新access_token
    *
-   * @see #getAccessToken(boolean)
+   * @return the access token
+   * @throws WxErrorException the wx error exception
+   * @see #getAccessToken(boolean) #getAccessToken(boolean)
    */
   String getAccessToken() throws WxErrorException;
 
@@ -49,13 +53,17 @@ public interface WxCpService {
    * 
* * @param forceRefresh 强制刷新 + * @return the access token + * @throws WxErrorException the wx error exception */ String getAccessToken(boolean forceRefresh) throws WxErrorException; /** * 获得jsapi_ticket,不强制刷新jsapi_ticket * - * @see #getJsapiTicket(boolean) + * @return the jsapi ticket + * @throws WxErrorException the wx error exception + * @see #getJsapiTicket(boolean) #getJsapiTicket(boolean) */ String getJsapiTicket() throws WxErrorException; @@ -68,6 +76,8 @@ public interface WxCpService { * * * @param forceRefresh 强制刷新 + * @return the jsapi ticket + * @throws WxErrorException the wx error exception */ String getJsapiTicket(boolean forceRefresh) throws WxErrorException; @@ -78,7 +88,9 @@ public interface WxCpService { * 签名的jsapi_ticket必须使用以下接口获取。且必须用wx.agentConfig中的agentid对应的应用secret去获取access_token。 * 签名用的noncestr和timestamp必须与wx.agentConfig中的nonceStr和timestamp相同。 * - * @see #getJsapiTicket(boolean) + * @return the agent jsapi ticket + * @throws WxErrorException the wx error exception + * @see #getJsapiTicket(boolean) #getJsapiTicket(boolean) */ String getAgentJsapiTicket() throws WxErrorException; @@ -96,6 +108,8 @@ public interface WxCpService { * * * @param forceRefresh 强制刷新 + * @return the agent jsapi ticket + * @throws WxErrorException the wx error exception */ String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException; @@ -107,23 +121,18 @@ public interface WxCpService { * * * @param url url + * @return the wx jsapi signature + * @throws WxErrorException the wx error exception */ WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; - /** - *
-   * 发送消息
-   * 详情请见: http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
-   * 
- * - * @param message 要发送的消息对象 - */ - WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException; /** * 小程序登录凭证校验 * * @param jsCode 登录时获取的 code + * @return the wx cp ma js code 2 session result + * @throws WxErrorException the wx error exception */ WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException; @@ -134,6 +143,7 @@ public interface WxCpService { * * * @return { "ip_list": ["101.226.103.*", "101.226.62.*"] } + * @throws WxErrorException the wx error exception */ String[] getCallbackIp() throws WxErrorException; @@ -147,37 +157,18 @@ public interface WxCpService { * * @param corpId 服务商的corpid * @param providerSecret 服务商的secret,在服务商管理后台可见 - * @return { - * "errcode":0 , - * "errmsg":"ok" , - * "provider_access_token":"enLSZ5xxxxxxJRL", - * "expires_in":7200 - * } + * @return { "errcode":0 , "errmsg":"ok" , "provider_access_token":"enLSZ5xxxxxxJRL", "expires_in":7200 } * @throws WxErrorException . */ WxCpProviderToken getProviderToken(String corpId, String providerSecret) throws WxErrorException; - /** - * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求 - * - * @param url 接口地址 - * @param queryParam 请求参数 - */ - String get(String url, String queryParam) throws WxErrorException; - - /** - * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求 - * - * @param url 接口地址 - * @param postData 请求body字符串 - */ - String post(String url, String postData) throws WxErrorException; - /** * 当不需要自动带accessToken的时候,可以用这个发起post请求 * * @param url 接口地址 * @param postData 请求body字符串 + * @return the string + * @throws WxErrorException the wx error exception */ String postWithoutToken(String url, String postData) throws WxErrorException; @@ -188,11 +179,13 @@ public interface WxCpService { * 可以参考,{@link MediaUploadRequestExecutor}的实现方法 * * + * @param 请求值类型 + * @param 返回值类型 * @param executor 执行器 * @param uri 请求地址 * @param data 参数 - * @param 请求值类型 - * @param 返回值类型 + * @return the t + * @throws WxErrorException the wx error exception */ T execute(RequestExecutor executor, String uri, E data) throws WxErrorException; @@ -220,6 +213,7 @@ public interface WxCpService { * 获取某个sessionId对应的session,如果sessionId没有对应的session,则新建一个并返回。 * * @param id id可以为任意字符串,建议使用FromUserName作为id + * @return the session */ WxSession getSession(String id); @@ -228,13 +222,14 @@ public interface WxCpService { * * @param id id可以为任意字符串,建议使用FromUserName作为id * @param create 是否新建 + * @return the session */ WxSession getSession(String id, boolean create); /** * 获取WxSessionManager 对象 * - * @return WxSessionManager + * @return WxSessionManager session manager */ WxSessionManager getSessionManager(); @@ -252,6 +247,8 @@ public interface WxCpService { * 上传部门列表覆盖企业号上的部门信息 * * @param mediaId 媒体id + * @return the string + * @throws WxErrorException the wx error exception */ String replaceParty(String mediaId) throws WxErrorException; @@ -259,11 +256,17 @@ public interface WxCpService { * 上传用户列表覆盖企业号上的用户信息 * * @param mediaId 媒体id + * @return the string + * @throws WxErrorException the wx error exception */ String replaceUser(String mediaId) throws WxErrorException; /** * 获取异步任务结果 + * + * @param joinId the join id + * @return the task result + * @throws WxErrorException the wx error exception */ String getTaskResult(String joinId) throws WxErrorException; @@ -275,7 +278,7 @@ public interface WxCpService { /** * 获取WxMpConfigStorage 对象 * - * @return WxMpConfigStorage + * @return WxMpConfigStorage wx cp config storage */ WxCpConfigStorage getWxCpConfigStorage(); @@ -288,75 +291,156 @@ public interface WxCpService { /** * 获取部门相关接口的服务类对象 + * + * @return the department service */ WxCpDepartmentService getDepartmentService(); /** * 获取媒体相关接口的服务类对象 + * + * @return the media service */ WxCpMediaService getMediaService(); /** * 获取菜单相关接口的服务类对象 + * + * @return the menu service */ WxCpMenuService getMenuService(); /** * 获取Oauth2相关接口的服务类对象 + * + * @return the oauth 2 service */ WxCpOAuth2Service getOauth2Service(); /** * 获取标签相关接口的服务类对象 + * + * @return the tag service */ WxCpTagService getTagService(); /** * 获取用户相关接口的服务类对象 + * + * @return the user service */ WxCpUserService getUserService(); + /** + * Gets external contact service. + * + * @return the external contact service + */ WxCpExternalContactService getExternalContactService(); /** * 获取群聊服务 * - * @return 群聊服务 + * @return 群聊服务 chat service */ WxCpChatService getChatService(); /** * 获取任务卡片服务 * - * @return 任务卡片服务 + * @return 任务卡片服务 task card service */ WxCpTaskCardService getTaskCardService(); + /** + * Gets agent service. + * + * @return the agent service + */ WxCpAgentService getAgentService(); - WxCpOaService getOAService(); + /** + * Gets message service. + * + * @return the message service + */ + WxCpMessageService getMessageService(); + + /** + * Gets oa service. + * + * @return the oa service + */ + WxCpOaService getOaService(); + + /** + * 获取日历相关接口的服务类对象 + * + * @return the menu service + */ + WxCpOaCalendarService getOaCalendarService(); /** * 获取群机器人消息推送服务 * - * @return 群机器人消息推送服务 + * @return 群机器人消息推送服务 group robot service */ WxCpGroupRobotService getGroupRobotService(); + /* + * 获取工作台服务 + * + * @return the workbench service + * */ + WxCpAgentWorkBenchService getWorkBenchService(); + /** * http请求对象 + * + * @return the request http */ RequestHttp getRequestHttp(); + /** + * Sets user service. + * + * @param userService the user service + */ void setUserService(WxCpUserService userService); + /** + * Sets department service. + * + * @param departmentService the department service + */ void setDepartmentService(WxCpDepartmentService departmentService); + /** + * Sets media service. + * + * @param mediaService the media service + */ void setMediaService(WxCpMediaService mediaService); + /** + * Sets menu service. + * + * @param menuService the menu service + */ void setMenuService(WxCpMenuService menuService); + /** + * Sets oauth 2 service. + * + * @param oauth2Service the oauth 2 service + */ void setOauth2Service(WxCpOAuth2Service oauth2Service); + /** + * Sets tag service. + * + * @param tagService the tag service + */ void setTagService(WxCpTagService tagService); + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java index 8cf5670f9b..4804dbc818 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpUserService.java @@ -3,7 +3,7 @@ import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.bean.WxCpInviteResult; import me.chanjar.weixin.cp.bean.WxCpUser; -import me.chanjar.weixin.cp.bean.external.WxCpUserExternalContactInfo; +import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo; import java.util.List; import java.util.Map; @@ -25,19 +25,24 @@ public interface WxCpUserService { * * * @param userId 用户id + * @throws WxErrorException the wx error exception */ void authenticate(String userId) throws WxErrorException; /** *
-   * 获取部门成员(详情).
+   * 获取部门成员详情
+   * 请求方式:GET(HTTPS)
+   * 请求地址:https://qyapi.weixin.qq.com/cgi-bin/user/list?access_token=ACCESS_TOKEN&department_id=DEPARTMENT_ID&fetch_child=FETCH_CHILD
    *
-   * http://qydev.weixin.qq.com/wiki/index.php?title=管理成员#.E8.8E.B7.E5.8F.96.E9.83.A8.E9.97.A8.E6.88.90.E5.91.98.28.E8.AF.A6.E6.83.85.29
+   * 文档地址:https://work.weixin.qq.com/api/doc/90000/90135/90201
    * 
* * @param departId 必填。部门id * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 + * @return the list + * @throws WxErrorException the wx error exception */ List listByDepartment(Long departId, Boolean fetchChild, Integer status) throws WxErrorException; @@ -51,6 +56,8 @@ public interface WxCpUserService { * @param departId 必填。部门id * @param fetchChild 非必填。1/0:是否递归获取子部门下面的成员 * @param status 非必填。0获取全部员工,1获取已关注成员列表,2获取禁用成员列表,4获取未关注成员列表。status可叠加 + * @return the list + * @throws WxErrorException the wx error exception */ List listSimpleByDepartment(Long departId, Boolean fetchChild, Integer status) throws WxErrorException; @@ -58,6 +65,7 @@ public interface WxCpUserService { * 新建用户. * * @param user 用户对象 + * @throws WxErrorException the wx error exception */ void create(WxCpUser user) throws WxErrorException; @@ -65,6 +73,7 @@ public interface WxCpUserService { * 更新用户. * * @param user 用户对象 + * @throws WxErrorException the wx error exception */ void update(WxCpUser user) throws WxErrorException; @@ -75,6 +84,7 @@ public interface WxCpUserService { * * * @param userIds 员工UserID列表。对应管理端的帐号 + * @throws WxErrorException the wx error exception */ void delete(String... userIds) throws WxErrorException; @@ -82,6 +92,8 @@ public interface WxCpUserService { * 获取用户. * * @param userid 用户id + * @return the by id + * @throws WxErrorException the wx error exception */ WxCpUser getById(String userid) throws WxErrorException; @@ -97,6 +109,8 @@ public interface WxCpUserService { * @param userIds 成员ID列表, 最多支持1000个。 * @param partyIds 部门ID列表,最多支持100个。 * @param tagIds 标签ID列表,最多支持100个。 + * @return the wx cp invite result + * @throws WxErrorException the wx error exception */ WxCpInviteResult invite(List userIds, List partyIds, List tagIds) throws WxErrorException; @@ -114,9 +128,8 @@ public interface WxCpUserService { * * @param userId 企业内的成员id * @param agentId 非必填,整型,仅用于发红包。其它场景该参数不要填,如微信支付、企业转账、电子发票 - * @return map对象,可能包含以下值: - * - openid 企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid - * - appid 应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到 + * @return map对象 ,可能包含以下值: - openid 企业微信成员userid对应的openid,若有传参agentid,则是针对该agentid的openid。否则是针对企业微信corpid的openid - appid 应用的appid,若请求包中不包含agentid则不返回appid。该appid在使用微信红包时会用到 + * @throws WxErrorException the wx error exception */ Map userId2Openid(String userId, Integer agentId) throws WxErrorException; @@ -134,6 +147,7 @@ public interface WxCpUserService { * * @param openid 在使用微信支付、微信红包和企业转账之后,返回结果的openid * @return userid 该openid在企业微信对应的成员userid + * @throws WxErrorException the wx error exception */ String openid2UserId(String openid) throws WxErrorException; @@ -149,7 +163,7 @@ public interface WxCpUserService { * * * @param mobile 手机号码。长度为5~32个字节 - * @return userid mobile对应的成员userid + * @return userid mobile对应的成员userid * @throws WxErrorException . */ String getUserId(String mobile) throws WxErrorException; @@ -164,10 +178,10 @@ public interface WxCpUserService { * * * @param userId 外部联系人的userid - * @return 联系人详情 + * @return 联系人详情 external contact * @throws WxErrorException . */ - WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException; + WxCpExternalContactInfo getExternalContact(String userId) throws WxErrorException; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java index 52d88e4564..d356819e0d 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpServiceImpl.java @@ -5,10 +5,12 @@ import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.bean.ToJson; import me.chanjar.weixin.common.bean.WxJsapiSignature; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.session.StandardSessionManager; import me.chanjar.weixin.common.session.WxSession; import me.chanjar.weixin.common.session.WxSessionManager; @@ -22,8 +24,6 @@ import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.*; import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; -import me.chanjar.weixin.cp.bean.WxCpMessage; -import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; import me.chanjar.weixin.cp.bean.WxCpProviderToken; import me.chanjar.weixin.cp.config.WxCpConfigStorage; @@ -53,6 +53,9 @@ public abstract class BaseWxCpServiceImpl implements WxCpService, RequestH private WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this); private WxCpExternalContactService externalContactService = new WxCpExternalContactServiceImpl(this); private WxCpGroupRobotService groupRobotService = new WxCpGroupRobotServiceImpl(this); + private WxCpMessageService messageService = new WxCpMessageServiceImpl(this); + private WxCpOaCalendarService oaCalendarService = new WxCpOaCalendarServiceImpl(this); + private WxCpAgentWorkBenchService workBenchService = new WxCpAgentWorkBenchServiceImpl(this); /** * 全局的是否正在刷新access token的锁. @@ -169,16 +172,6 @@ public WxJsapiSignature createJsapiSignature(String url) throws WxErrorException return jsapiSignature; } - @Override - public WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException { - Integer agentId = message.getAgentId(); - if (null == agentId) { - message.setAgentId(this.getWxCpConfigStorage().getAgentId()); - } - - return WxCpMessageSendResult.fromJson(this.post(this.configStorage.getApiUrl(MESSAGE_SEND), message.toJson())); - } - @Override public WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException { Map params = new HashMap<>(2); @@ -219,6 +212,21 @@ public String post(String url, String postData) throws WxErrorException { return execute(SimplePostRequestExecutor.create(this), url, postData); } + @Override + public String post(String url, JsonObject jsonObject) throws WxErrorException { + return this.post(url, jsonObject.toString()); + } + + @Override + public String post(String url, ToJson obj) throws WxErrorException { + return this.post(url, obj.toJson()); + } + + @Override + public String post(String url, Object obj) throws WxErrorException { + return this.post(url, obj.toString()); + } + @Override public String postWithoutToken(String url, String postData) throws WxErrorException { return this.executeNormal(SimplePostRequestExecutor.create(this), url, postData); @@ -237,7 +245,7 @@ public T execute(RequestExecutor executor, String uri, E data) thro if (retryTimes + 1 > this.maxRetryTimes) { log.warn("重试达到最大次数【{}】", this.maxRetryTimes); //最后一次重试失败后,直接抛出异常,不再等待 - throw new RuntimeException("微信服务端异常,超出重试次数"); + throw new WxRuntimeException("微信服务端异常,超出重试次数"); } WxError error = e.getError(); @@ -259,7 +267,7 @@ public T execute(RequestExecutor executor, String uri, E data) thro } while (retryTimes++ < this.maxRetryTimes); log.warn("重试达到最大次数【{}】", this.maxRetryTimes); - throw new RuntimeException("微信服务端异常,超出重试次数"); + throw new WxRuntimeException("微信服务端异常,超出重试次数"); } protected T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException { @@ -295,7 +303,7 @@ protected T executeInternal(RequestExecutor executor, String uri, E return null; } catch (IOException e) { log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); - throw new RuntimeException(e); + throw new WxRuntimeException(e); } } @@ -316,7 +324,7 @@ private T executeNormal(RequestExecutor executor, String uri, E dat return null; } catch (IOException e) { log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uri, data, e.getMessage()); - throw new RuntimeException(e); + throw new WxErrorException(e); } } @@ -432,15 +440,25 @@ public WxCpChatService getChatService() { } @Override - public WxCpOaService getOAService() { + public WxCpOaService getOaService() { return oaService; } + @Override + public WxCpOaCalendarService getOaCalendarService() { + return this.oaCalendarService; + } + @Override public WxCpGroupRobotService getGroupRobotService() { return groupRobotService; } + @Override + public WxCpAgentWorkBenchService getWorkBenchService() { + return workBenchService; + } + @Override public WxCpTaskCardService getTaskCardService() { return taskCardService; @@ -486,6 +504,11 @@ public WxCpAgentService getAgentService() { return agentService; } + @Override + public WxCpMessageService getMessageService() { + return this.messageService; + } + public void setAgentService(WxCpAgentService agentService) { this.agentService = agentService; } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java new file mode 100644 index 0000000000..8c778197ce --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchServiceImpl.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.WxCpAgentWorkBenchService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.WxCpAgentWorkBench; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.WorkBench.WORKBENCH_DATA_SET; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.WorkBench.WORKBENCH_TEMPLATE_GET; +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.WorkBench.WORKBENCH_TEMPLATE_SET; + +/** + * @author songshiyu + * @date : create in 11:24 2020/9/28 + * @description: 工作台自定义展示实现 + */ +@RequiredArgsConstructor +public class WxCpAgentWorkBenchServiceImpl implements WxCpAgentWorkBenchService { + private final WxCpService mainService; + + @Override + public void setWorkBenchTemplate(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException { + final String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_TEMPLATE_SET)); + this.mainService.post(url, wxCpAgentWorkBench.toTemplateString()); + } + + @Override + public String getWorkBenchTemplate(Long agentId) throws WxErrorException { + final String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_TEMPLATE_GET)); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("agentid", agentId); + return this.mainService.post(url, jsonObject.toString()); + } + + @Override + public void setWorkBenchData(WxCpAgentWorkBench wxCpAgentWorkBench) throws WxErrorException { + final String url = String.format(this.mainService.getWxCpConfigStorage().getApiUrl(WORKBENCH_DATA_SET)); + this.mainService.post(url, wxCpAgentWorkBench.toUserDataString()); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImpl.java index 10af36afe6..7783422af9 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImpl.java @@ -6,7 +6,7 @@ import me.chanjar.weixin.common.util.json.WxGsonBuilder; import me.chanjar.weixin.cp.api.WxCpChatService; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.WxCpAppChatMessage; +import me.chanjar.weixin.cp.bean.message.WxCpAppChatMessage; import me.chanjar.weixin.cp.bean.WxCpChat; import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; import org.apache.commons.lang3.StringUtils; 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 b64ec0e870..19e7cdfe79 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 @@ -6,10 +6,13 @@ import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.error.WxCpErrorMsgEnum; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.cp.api.WxCpExternalContactService; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.*; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; import me.chanjar.weixin.cp.bean.external.*; +import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactBatchInfo; +import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; @@ -30,7 +33,7 @@ public class WxCpExternalContactServiceImpl implements WxCpExternalContactServic public WxCpContactWayResult addContactWay(@NonNull WxCpContactWayInfo info) throws WxErrorException { if (info.getContactWay().getUsers() != null && info.getContactWay().getUsers().size() > 100) { - throw new RuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)"); + throw new WxRuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)"); } final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_CONTACT_WAY); @@ -52,10 +55,10 @@ public WxCpContactWayInfo getContactWay(@NonNull String configId) throws WxError @Override public WxCpBaseResp updateContactWay(@NonNull WxCpContactWayInfo info) throws WxErrorException { if (StringUtils.isBlank(info.getContactWay().getConfigId())) { - throw new RuntimeException("更新「联系我」方式需要指定configId"); + throw new WxRuntimeException("更新「联系我」方式需要指定configId"); } if (info.getContactWay().getUsers() != null && info.getContactWay().getUsers().size() > 100) { - throw new RuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)"); + throw new WxRuntimeException("「联系我」使用人数默认限制不超过100人(包括部门展开后的人数)"); } final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_CONTACT_WAY); @@ -67,7 +70,7 @@ public WxCpBaseResp updateContactWay(@NonNull WxCpContactWayInfo info) throws Wx @Override public WxCpBaseResp deleteContactWay(@NonNull String configId) throws WxErrorException { JsonObject json = new JsonObject(); - json.addProperty("config_id",configId); + json.addProperty("config_id", configId); final String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEL_CONTACT_WAY); String responseContent = this.mainService.post(url, json.toString()); @@ -79,8 +82,8 @@ public WxCpBaseResp deleteContactWay(@NonNull String configId) throws WxErrorExc public WxCpBaseResp closeTempChat(@NonNull String userId, @NonNull String externalUserId) throws WxErrorException { JsonObject json = new JsonObject(); - json.addProperty("userid",userId); - json.addProperty("external_userid",externalUserId); + json.addProperty("userid", userId); + json.addProperty("external_userid", externalUserId); final String url = this.mainService.getWxCpConfigStorage().getApiUrl(CLOSE_TEMP_CHAT); @@ -90,17 +93,44 @@ public WxCpBaseResp closeTempChat(@NonNull String userId, @NonNull String extern } @Override - public WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException { + public WxCpExternalContactInfo getExternalContact(String userId) throws WxErrorException { final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_EXTERNAL_CONTACT + userId); String responseContent = this.mainService.get(url, null); - return WxCpUserExternalContactInfo.fromJson(responseContent); + return WxCpExternalContactInfo.fromJson(responseContent); } @Override - public WxCpUserExternalContactInfo getContactDetail(String userId) throws WxErrorException { + public WxCpExternalContactInfo getContactDetail(String userId) throws WxErrorException { final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CONTACT_DETAIL + userId); String responseContent = this.mainService.get(url, null); - return WxCpUserExternalContactInfo.fromJson(responseContent); + return WxCpExternalContactInfo.fromJson(responseContent); + } + + @Override + public WxCpExternalContactBatchInfo getContactDetailBatch(String userId, + String cursor, + Integer limit) + throws WxErrorException { + final String url = + this.mainService + .getWxCpConfigStorage() + .getApiUrl(GET_CONTACT_DETAIL_BATCH); + JsonObject json = new JsonObject(); + json.addProperty("userid", userId); + if (StringUtils.isNotBlank(cursor)) { + json.addProperty("cursor", cursor); + } + if (limit != null) { + json.addProperty("limit", limit); + } + String responseContent = this.mainService.post(url, json.toString()); + return WxCpExternalContactBatchInfo.fromJson(responseContent); + } + + @Override + public void updateRemark(WxCpUpdateRemarkRequest request) throws WxErrorException { + final String url = this.mainService.getWxCpConfigStorage().getApiUrl(UPDATE_REMARK); + this.mainService.post(url, request.toJson()); } @Override @@ -155,10 +185,10 @@ public WxCpUserExternalGroupChatList listGroupChat(Integer pageIndex, Integer pa if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { JsonObject ownerFilter = new JsonObject(); if (ArrayUtils.isNotEmpty(userIds)) { - json.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray()); + ownerFilter.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray()); } if (ArrayUtils.isNotEmpty(partyIds)) { - json.add("partyid_list", new Gson().toJsonTree(partyIds).getAsJsonArray()); + ownerFilter.add("partyid_list", new Gson().toJsonTree(partyIds).getAsJsonArray()); } json.add("owner_filter", ownerFilter); } @@ -205,10 +235,10 @@ public WxCpUserExternalGroupChatStatistic getGroupChatStatistic(Date startTime, if (ArrayUtils.isNotEmpty(userIds) || ArrayUtils.isNotEmpty(partyIds)) { JsonObject ownerFilter = new JsonObject(); if (ArrayUtils.isNotEmpty(userIds)) { - json.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray()); + ownerFilter.add("userid_list", new Gson().toJsonTree(userIds).getAsJsonArray()); } if (ArrayUtils.isNotEmpty(partyIds)) { - json.add("partyid_list", new Gson().toJsonTree(partyIds).getAsJsonArray()); + ownerFilter.add("partyid_list", new Gson().toJsonTree(partyIds).getAsJsonArray()); } json.add("owner_filter", ownerFilter); } @@ -233,66 +263,66 @@ public void sendWelcomeMsg(WxCpWelcomeMsg msg) throws WxErrorException { @Override public WxCpUserExternalTagGroupList getCorpTagList(String[] tagId) throws WxErrorException { JsonObject json = new JsonObject(); - if(ArrayUtils.isNotEmpty(tagId)){ - json.add("tag_id",new Gson().toJsonTree(tagId).getAsJsonArray()); + if (ArrayUtils.isNotEmpty(tagId)) { + json.add("tag_id", new Gson().toJsonTree(tagId).getAsJsonArray()); } final String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_CORP_TAG_LIST); - final String result = this.mainService.post(url,json.toString()); + final String result = this.mainService.post(url, json.toString()); return WxCpUserExternalTagGroupList.fromJson(result); } @Override - public WxCpUserExternalTagGroupInfo addCorpTag(WxCpUserExternalTagGroupInfo tagGroup) throws WxErrorException{ + public WxCpUserExternalTagGroupInfo addCorpTag(WxCpUserExternalTagGroupInfo tagGroup) throws WxErrorException { final String url = this.mainService.getWxCpConfigStorage().getApiUrl(ADD_CORP_TAG); - final String result = this.mainService.post(url,tagGroup.getTagGroup().toJson()); + final String result = this.mainService.post(url, tagGroup.getTagGroup().toJson()); return WxCpUserExternalTagGroupInfo.fromJson(result); } @Override - public WxCpBaseResp editCorpTag(String id, String name, Integer order) throws WxErrorException{ + public WxCpBaseResp editCorpTag(String id, String name, Integer order) throws WxErrorException { JsonObject json = new JsonObject(); - json.addProperty("id",id); - json.addProperty("name",name); - json.addProperty("order",order); + json.addProperty("id", id); + json.addProperty("name", name); + json.addProperty("order", order); final String url = this.mainService.getWxCpConfigStorage().getApiUrl(EDIT_CORP_TAG); - final String result = this.mainService.post(url,json.toString()); + final String result = this.mainService.post(url, json.toString()); return WxCpBaseResp.fromJson(result); } @Override - public WxCpBaseResp delCorpTag(String[] tagId, String[] groupId) throws WxErrorException{ + public WxCpBaseResp delCorpTag(String[] tagId, String[] groupId) throws WxErrorException { JsonObject json = new JsonObject(); - if(ArrayUtils.isNotEmpty(tagId)){ - json.add("tag_id",new Gson().toJsonTree(tagId).getAsJsonArray()); + if (ArrayUtils.isNotEmpty(tagId)) { + json.add("tag_id", new Gson().toJsonTree(tagId).getAsJsonArray()); } - if(ArrayUtils.isNotEmpty(groupId)){ - json.add("group_id",new Gson().toJsonTree(groupId).getAsJsonArray()); + if (ArrayUtils.isNotEmpty(groupId)) { + json.add("group_id", new Gson().toJsonTree(groupId).getAsJsonArray()); } final String url = this.mainService.getWxCpConfigStorage().getApiUrl(DEL_CORP_TAG); - final String result = this.mainService.post(url,json.toString()); + final String result = this.mainService.post(url, json.toString()); return WxCpBaseResp.fromJson(result); } @Override - public WxCpBaseResp markTag(String userid, String externalUserid, String[] addTag, String[] removeTag)throws WxErrorException{ + public WxCpBaseResp markTag(String userid, String externalUserid, String[] addTag, String[] removeTag) throws WxErrorException { JsonObject json = new JsonObject(); - json.addProperty("userid",userid); - json.addProperty("external_userid",externalUserid); + json.addProperty("userid", userid); + json.addProperty("external_userid", externalUserid); - if(ArrayUtils.isNotEmpty(addTag)){ - json.add("add_tag",new Gson().toJsonTree(addTag).getAsJsonArray()); + if (ArrayUtils.isNotEmpty(addTag)) { + json.add("add_tag", new Gson().toJsonTree(addTag).getAsJsonArray()); } - if(ArrayUtils.isNotEmpty(removeTag)){ - json.add("remove_tag",new Gson().toJsonTree(removeTag).getAsJsonArray()); + if (ArrayUtils.isNotEmpty(removeTag)) { + json.add("remove_tag", new Gson().toJsonTree(removeTag).getAsJsonArray()); } final String url = this.mainService.getWxCpConfigStorage().getApiUrl(MARK_TAG); - final String result = this.mainService.post(url,json.toString()); + final String result = this.mainService.post(url, json.toString()); return WxCpBaseResp.fromJson(result); } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java index ed4d8a108e..c20c4a138d 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImpl.java @@ -1,19 +1,24 @@ package me.chanjar.weixin.cp.api.impl; import lombok.RequiredArgsConstructor; -import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.api.WxCpGroupRobotService; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.WxCpGroupRobotMessage; import me.chanjar.weixin.cp.bean.article.NewArticle; +import me.chanjar.weixin.cp.bean.message.WxCpGroupRobotMessage; import me.chanjar.weixin.cp.config.WxCpConfigStorage; import me.chanjar.weixin.cp.constant.WxCpApiPathConsts; +import org.apache.commons.lang3.StringUtils; import java.util.List; +import static me.chanjar.weixin.cp.constant.WxCpConsts.GroupRobotMsgType; +import static me.chanjar.weixin.cp.constant.WxCpConsts.GroupRobotMsgType.MARKDOWN; +import static me.chanjar.weixin.cp.constant.WxCpConsts.GroupRobotMsgType.TEXT; + /** - * 微信群机器人消息发送api 实现 + * 企业微信群机器人消息发送api 实现 * * @author yr * @date 2020-08-20 @@ -22,44 +27,66 @@ public class WxCpGroupRobotServiceImpl implements WxCpGroupRobotService { private final WxCpService cpService; - private String getApiUrl() { - WxCpConfigStorage wxCpConfigStorage = cpService.getWxCpConfigStorage(); - return wxCpConfigStorage.getApiUrl(WxCpApiPathConsts.WEBHOOK_SEND) + wxCpConfigStorage.getWebhookKey(); + private String getWebhookUrl() throws WxErrorException { + WxCpConfigStorage wxCpConfigStorage = this.cpService.getWxCpConfigStorage(); + final String webhookKey = wxCpConfigStorage.getWebhookKey(); + if (StringUtils.isEmpty(webhookKey)) { + throw new WxErrorException("请先设置WebhookKey"); + } + return wxCpConfigStorage.getApiUrl(WxCpApiPathConsts.WEBHOOK_SEND) + webhookKey; } @Override public void sendText(String content, List mentionedList, List mobileList) throws WxErrorException { - WxCpGroupRobotMessage message = new WxCpGroupRobotMessage() - .setMsgType(WxConsts.GroupRobotMsgType.TEXT) + this.sendText(this.getWebhookUrl(), content, mentionedList, mobileList); + } + + @Override + public void sendMarkdown(String content) throws WxErrorException { + this.sendMarkdown(this.getWebhookUrl(), content); + } + + @Override + public void sendImage(String base64, String md5) throws WxErrorException { + this.sendImage(this.getWebhookUrl(), base64, md5); + } + + @Override + public void sendNews(List articleList) throws WxErrorException { + this.sendNews(this.getWebhookUrl(), articleList); + } + + @Override + public void sendText(String webhookUrl, String content, List mentionedList, List mobileList) throws WxErrorException { + this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() + .setMsgType(TEXT) .setContent(content) .setMentionedList(mentionedList) - .setMentionedMobileList(mobileList); - cpService.postWithoutToken(this.getApiUrl(), message.toJson()); + .setMentionedMobileList(mobileList) + .toJson()); } @Override - public void sendMarkDown(String content) throws WxErrorException { - WxCpGroupRobotMessage message = new WxCpGroupRobotMessage() - .setMsgType(WxConsts.GroupRobotMsgType.MARKDOWN) - .setContent(content); - cpService.postWithoutToken(this.getApiUrl(), message.toJson()); + public void sendMarkdown(String webhookUrl, String content) throws WxErrorException { + this.cpService.postWithoutToken(webhookUrl, new WxCpGroupRobotMessage() + .setMsgType(MARKDOWN) + .setContent(content) + .toJson()); } @Override - public void sendImage(String base64, String md5) throws WxErrorException { - WxCpGroupRobotMessage message = new WxCpGroupRobotMessage() - .setMsgType(WxConsts.GroupRobotMsgType.IMAGE) + public void sendImage(String webhookUrl, String base64, String md5) throws WxErrorException { + this.cpService.postWithoutToken(this.getWebhookUrl(), new WxCpGroupRobotMessage() + .setMsgType(GroupRobotMsgType.IMAGE) .setBase64(base64) - .setMd5(md5); - cpService.postWithoutToken(this.getApiUrl(), message.toJson()); + .setMd5(md5).toJson()); } @Override - public void sendNews(List articleList) throws WxErrorException { - WxCpGroupRobotMessage message = new WxCpGroupRobotMessage() - .setMsgType(WxConsts.GroupRobotMsgType.NEWS) - .setArticles(articleList); - cpService.postWithoutToken(this.getApiUrl(), message.toJson()); + public void sendNews(String webhookUrl, List articleList) throws WxErrorException { + this.cpService.postWithoutToken(this.getWebhookUrl(), new WxCpGroupRobotMessage() + .setMsgType(GroupRobotMsgType.NEWS) + .setArticles(articleList).toJson()); } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImpl.java new file mode 100644 index 0000000000..07824c2183 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImpl.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.common.collect.ImmutableMap; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.WxCpMessageService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.message.WxCpLinkedCorpMessage; +import me.chanjar.weixin.cp.bean.message.WxCpMessage; +import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult; +import me.chanjar.weixin.cp.bean.message.WxCpMessageSendStatistics; +import me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Message; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +/** + * 消息推送接口实现类. + * + * @author Binary Wang + * @date 2020-08-30 + */ +@RequiredArgsConstructor +public class WxCpMessageServiceImpl implements WxCpMessageService { + private final WxCpService cpService; + + @Override + public WxCpMessageSendResult send(WxCpMessage message) throws WxErrorException { + Integer agentId = message.getAgentId(); + if (null == agentId) { + message.setAgentId(this.cpService.getWxCpConfigStorage().getAgentId()); + } + + return WxCpMessageSendResult.fromJson(this.cpService.post(this.cpService.getWxCpConfigStorage() + .getApiUrl(Message.MESSAGE_SEND), message.toJson())); + } + + @Override + public WxCpMessageSendStatistics getStatistics(int timeType) throws WxErrorException { + return WxCpMessageSendStatistics.fromJson(this.cpService.post(this.cpService.getWxCpConfigStorage().getApiUrl(Message.GET_STATISTICS), + WxCpGsonBuilder.create().toJson(ImmutableMap.of("time_type", timeType)))); + } + + @Override + public WxCpMessageSendResult sendLinkedCorpMessage(WxCpLinkedCorpMessage message) throws WxErrorException { + Integer agentId = message.getAgentId(); + if (null == agentId) { + message.setAgentId(this.cpService.getWxCpConfigStorage().getAgentId()); + } + + return WxCpMessageSendResult.fromJson(this.cpService.post(this.cpService.getWxCpConfigStorage() + .getApiUrl(Message.LINKEDCORP_MESSAGE_SEND), message.toJson())); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaCalendarServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaCalendarServiceImpl.java new file mode 100644 index 0000000000..7e604934b9 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaCalendarServiceImpl.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.gson.reflect.TypeToken; +import lombok.RequiredArgsConstructor; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.json.GsonHelper; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.api.WxCpOaCalendarService; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.oa.calendar.WxCpOaCalendar; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Oa.*; + +/** + * . + * + * @author Binary Wang + * @date 2020-09-20 + */ +@RequiredArgsConstructor +public class WxCpOaCalendarServiceImpl implements WxCpOaCalendarService { + private final WxCpService wxCpService; + + @Override + public String add(WxCpOaCalendar calendar) throws WxErrorException { + return this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_ADD), calendar); + } + + @Override + public void update(WxCpOaCalendar calendar) throws WxErrorException { + this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_UPDATE), calendar); + } + + @Override + public List get(List calIds) throws WxErrorException { + String response = this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_GET), + GsonHelper.buildJsonObject("cal_id_list", calIds)); + return WxCpGsonBuilder.create().fromJson(GsonParser.parse(response).get("calendar_list").getAsJsonArray().toString(), + new TypeToken>() { + }.getType()); + } + + @Override + public void delete(String calId) throws WxErrorException { + this.wxCpService.post(this.wxCpService.getWxCpConfigStorage().getApiUrl(CALENDAR_DEL), + GsonHelper.buildJsonObject("cal_id", calId)); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java index 17a2ba274d..c5dc8faf34 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImpl.java @@ -7,6 +7,7 @@ import lombok.NonNull; import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.cp.api.WxCpOaService; import me.chanjar.weixin.cp.api.WxCpService; @@ -42,18 +43,18 @@ public String apply(WxCpOaApplyEventRequest request) throws WxErrorException { public List getCheckinData(Integer openCheckinDataType, Date startTime, Date endTime, List userIdList) throws WxErrorException { if (startTime == null || endTime == null) { - throw new RuntimeException("starttime and endtime can't be null"); + throw new WxRuntimeException("starttime and endtime can't be null"); } if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) { - throw new RuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取"); + throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取"); } long endTimestamp = endTime.getTime() / 1000L; long startTimestamp = startTime.getTime() / 1000L; if (endTimestamp - startTimestamp < 0 || endTimestamp - startTimestamp >= MONTH_SECONDS) { - throw new RuntimeException("获取记录时间跨度不超过一个月"); + throw new WxRuntimeException("获取记录时间跨度不超过一个月"); } JsonObject jsonObject = new JsonObject(); @@ -83,11 +84,11 @@ public List getCheckinData(Integer openCheckinDataType, Date st @Override public List getCheckinOption(Date datetime, List userIdList) throws WxErrorException { if (datetime == null) { - throw new RuntimeException("datetime can't be null"); + throw new WxRuntimeException("datetime can't be null"); } if (userIdList == null || userIdList.size() > USER_IDS_LIMIT) { - throw new RuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取"); + throw new WxRuntimeException("用户列表不能为空,不超过 " + USER_IDS_LIMIT + " 个,若用户超过 " + USER_IDS_LIMIT + " 个,请分批获取"); } JsonArray jsonArray = new JsonArray(); @@ -186,7 +187,7 @@ public List getDialRecord(Date startTime, Date endTime, Integer long starttimestamp = startTime.getTime() / 1000L; if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= MONTH_SECONDS) { - throw new RuntimeException("受限于网络传输,起止时间的最大跨度为30天,如超过30天,则以结束时间为基准向前取30天进行查询"); + throw new WxRuntimeException("受限于网络传输,起止时间的最大跨度为30天,如超过30天,则以结束时间为基准向前取30天进行查询"); } jsonObject.addProperty("start_time", starttimestamp); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java index 076d0205a5..b428bc34aa 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceApacheHttpClientImpl.java @@ -5,6 +5,7 @@ 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.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.http.HttpType; import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; @@ -72,7 +73,7 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); } catch (IOException e) { - throw new RuntimeException(e); + throw new WxRuntimeException(e); } } return this.configStorage.getAccessToken(); 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 e78432b5a2..661a0ed79f 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,11 +1,13 @@ package me.chanjar.weixin.cp.api.impl; import com.google.gson.JsonObject; -import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.config.WxCpConfigStorage; import me.chanjar.weixin.cp.constant.WxCpApiPathConsts; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; @@ -29,28 +31,28 @@ * 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(); + final WxCpConfigStorage configStorage = getWxCpConfigStorage(); + if (!configStorage.isAccessTokenExpired() && !forceRefresh) { + return configStorage.getAccessToken(); } - Lock lock = getWxCpConfigStorage().getAccessTokenLock(); + Lock lock = configStorage.getAccessTokenLock(); lock.lock(); try { // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 - if (!getWxCpConfigStorage().isAccessTokenExpired() && !forceRefresh) { - return getWxCpConfigStorage().getAccessToken(); + if (!configStorage.isAccessTokenExpired() && !forceRefresh) { + return configStorage.getAccessToken(); } - String url = String.format(getWxCpConfigStorage().getApiUrl(WxCpApiPathConsts.GET_TOKEN), this.configStorage.getCorpId(), this.configStorage.getCorpSecret()); + String url = String.format(configStorage.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(); + RequestConfig config = RequestConfig.custom().setProxy(getRequestHttpProxy()).build(); httpGet.setConfig(config); } String resultContent; @@ -66,60 +68,62 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { } WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); - getWxCpConfigStorage().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); + configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); } catch (IOException e) { - throw new RuntimeException(e); + throw new WxRuntimeException(e); } } finally { lock.unlock(); } - return getWxCpConfigStorage().getAccessToken(); + return configStorage.getAccessToken(); } @Override public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException { + final WxCpConfigStorage configStorage = getWxCpConfigStorage(); if (forceRefresh) { - getWxCpConfigStorage().expireAgentJsapiTicket(); + configStorage.expireAgentJsapiTicket(); } - if (getWxCpConfigStorage().isAgentJsapiTicketExpired()) { - Lock lock = getWxCpConfigStorage().getAgentJsapiTicketLock(); + if (configStorage.isAgentJsapiTicketExpired()) { + Lock lock = configStorage.getAgentJsapiTicketLock(); lock.lock(); try { // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 - if (getWxCpConfigStorage().isAgentJsapiTicketExpired()) { - String responseContent = this.get(getWxCpConfigStorage().getApiUrl(GET_AGENT_CONFIG_TICKET), null); + if (configStorage.isAgentJsapiTicketExpired()) { + String responseContent = this.get(configStorage.getApiUrl(GET_AGENT_CONFIG_TICKET), null); JsonObject jsonObject = GsonParser.parse(responseContent); - getWxCpConfigStorage().updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(), + configStorage.updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(), jsonObject.get("expires_in").getAsInt()); } } finally { lock.unlock(); } } - return getWxCpConfigStorage().getAgentJsapiTicket(); + return configStorage.getAgentJsapiTicket(); } @Override public String getJsapiTicket(boolean forceRefresh) throws WxErrorException { + final WxCpConfigStorage configStorage = getWxCpConfigStorage(); if (forceRefresh) { - getWxCpConfigStorage().expireJsapiTicket(); + configStorage.expireJsapiTicket(); } - if (getWxCpConfigStorage().isJsapiTicketExpired()) { - Lock lock = getWxCpConfigStorage().getJsapiTicketLock(); + if (configStorage.isJsapiTicketExpired()) { + Lock lock = configStorage.getJsapiTicketLock(); lock.lock(); try { // 拿到锁之后,再次判断一下最新的token是否过期,避免重刷 - if (getWxCpConfigStorage().isJsapiTicketExpired()) { - String responseContent = this.get(getWxCpConfigStorage().getApiUrl(GET_JSAPI_TICKET), null); + if (configStorage.isJsapiTicketExpired()) { + String responseContent = this.get(configStorage.getApiUrl(GET_JSAPI_TICKET), null); JsonObject tmpJsonObject = GsonParser.parse(responseContent); - getWxCpConfigStorage().updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(), + configStorage.updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(), tmpJsonObject.get("expires_in").getAsInt()); } } finally { lock.unlock(); } } - return getWxCpConfigStorage().getJsapiTicket(); + return configStorage.getJsapiTicket(); } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOnTpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOnTpImpl.java index 35eab626a7..aa30385d6c 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOnTpImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpServiceOnTpImpl.java @@ -3,7 +3,7 @@ import lombok.RequiredArgsConstructor; import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.cp.api.WxCpTpService; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; /** *
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java
index d6d401624f..cb122a0142 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpUserServiceImpl.java
@@ -10,7 +10,7 @@
 import me.chanjar.weixin.cp.api.WxCpUserService;
 import me.chanjar.weixin.cp.bean.WxCpInviteResult;
 import me.chanjar.weixin.cp.bean.WxCpUser;
-import me.chanjar.weixin.cp.bean.external.WxCpUserExternalContactInfo;
+import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo;
 import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
 
 import java.util.List;
@@ -193,9 +193,9 @@ public String getUserId(String mobile) throws WxErrorException {
   }
 
   @Override
-  public WxCpUserExternalContactInfo getExternalContact(String userId) throws WxErrorException {
+  public WxCpExternalContactInfo getExternalContact(String userId) throws WxErrorException {
     String url = this.mainService.getWxCpConfigStorage().getApiUrl(GET_EXTERNAL_CONTACT + userId);
     String responseContent = this.mainService.get(url, null);
-    return WxCpUserExternalContactInfo.fromJson(responseContent);
+    return WxCpExternalContactInfo.fromJson(responseContent);
   }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java
new file mode 100644
index 0000000000..c97faa6364
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAgentWorkBench.java
@@ -0,0 +1,137 @@
+package me.chanjar.weixin.cp.bean;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import me.chanjar.weixin.cp.bean.workbench.WorkBenchKeyData;
+import me.chanjar.weixin.cp.bean.workbench.WorkBenchList;
+import me.chanjar.weixin.cp.constant.WxCpConsts;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * @author songshiyu
+ * @date : create in 16:09 2020/9/27
+ * @description: 工作台自定义展示
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxCpAgentWorkBench implements Serializable {
+  private static final long serialVersionUid = 1L;
+
+  /*
+  * 展示类型,目前支持 “keydata”、 “image”、 “list” 、”webview”
+  * */
+  private String type;
+  /*
+  * 用户的userid
+  * */
+  private String userId;
+  /*
+  * 应用id
+  * */
+  private Long agentId;
+  /*
+  * 点击跳转url,若不填且应用设置了主页url,则跳转到主页url,否则跳到应用会话窗口
+  * */
+  private String jumpUrl;
+  /*
+  * 若应用为小程序类型,该字段填小程序pagepath,若未设置,跳到小程序主页
+  * */
+  private String pagePath;
+  /*
+  * 图片url:图片的最佳比例为3.35:1;webview:渲染展示的url
+  * */
+  private String url;
+  /*
+  * 是否覆盖用户工作台的数据。设置为true的时候,会覆盖企业所有用户当前设置的数据。若设置为false,则不会覆盖用户当前设置的所有数据
+  * */
+  private Boolean replaceUserData;
+
+  private List keyDataList;
+
+  private List lists;
+
+  // 生成模板Json字符串
+  public String toTemplateString() {
+    JsonObject templateObject = new JsonObject();
+    templateObject.addProperty("agentid", this.agentId);
+    templateObject.addProperty("type", this.type);
+    if (this.replaceUserData != null) {
+      templateObject.addProperty("replace_user_data", this.replaceUserData);
+    }
+    this.handle(templateObject);
+    return templateObject.toString();
+  }
+
+  // 生成用户数据Json字符串
+  public String toUserDataString() {
+    JsonObject userDataObject = new JsonObject();
+    userDataObject.addProperty("agentid", this.agentId);
+    userDataObject.addProperty("userid", this.userId);
+    userDataObject.addProperty("type", this.type);
+    this.handle(userDataObject);
+    return userDataObject.toString();
+  }
+
+  // 处理不用类型的工作台数据
+  private void handle(JsonObject templateObject) {
+    switch (this.getType()) {
+      case WxCpConsts.WorkBenchType.KEYDATA: {
+        JsonArray keyDataArray = new JsonArray();
+        JsonObject itemsObject = new JsonObject();
+        for (WorkBenchKeyData keyDataItem : this.keyDataList) {
+          JsonObject keyDataObject = new JsonObject();
+          keyDataObject.addProperty("key", keyDataItem.getKey());
+          keyDataObject.addProperty("data", keyDataItem.getData());
+          keyDataObject.addProperty("jump_url", keyDataItem.getJumpUrl());
+          keyDataObject.addProperty("pagepath", keyDataItem.getPagePath());
+          keyDataArray.add(keyDataObject);
+        }
+        itemsObject.add("items", keyDataArray);
+        templateObject.add("keydata", itemsObject);
+        break;
+      }
+      case WxCpConsts.WorkBenchType.IMAGE: {
+        JsonObject image = new JsonObject();
+        image.addProperty("url", this.url);
+        image.addProperty("jump_url", this.jumpUrl);
+        image.addProperty("pagepath", this.pagePath);
+        templateObject.add("image", image);
+        break;
+      }
+      case WxCpConsts.WorkBenchType.LIST: {
+        JsonArray listArray = new JsonArray();
+        JsonObject itemsObject = new JsonObject();
+        for (WorkBenchList listItem : this.lists) {
+          JsonObject listObject = new JsonObject();
+          listObject.addProperty("title", listItem.getTitle());
+          listObject.addProperty("jump_url", listItem.getJumpUrl());
+          listObject.addProperty("pagepath", listItem.getPagePath());
+          listArray.add(listObject);
+        }
+        itemsObject.add("items",listArray);
+        templateObject.add("list", itemsObject);
+        break;
+      }
+      case WxCpConsts.WorkBenchType.WEBVIEW: {
+        JsonObject webview = new JsonObject();
+        webview.addProperty("url", this.url);
+        webview.addProperty("jump_url", this.jumpUrl);
+        webview.addProperty("pagepath", this.pagePath);
+        templateObject.add("webview", webview);
+        break;
+      }
+      default: {
+        //do nothing
+      }
+    }
+  }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpUserDetail.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpUserDetail.java
new file mode 100644
index 0000000000..440e7b4df5
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpUserDetail.java
@@ -0,0 +1,58 @@
+package me.chanjar.weixin.cp.bean;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ *
+ * @author huangxiaoming
+ */
+@Data
+public class WxCpTpUserDetail extends WxCpBaseResp {
+
+  private static final long serialVersionUID = -5028321625140879571L;
+  /**
+   * 用户所属企业的corpid
+   */
+  @SerializedName("corpid")
+  private String corpId;
+
+  /**
+   * 成员UserID
+   */
+  @SerializedName("userid")
+  private String userId;
+
+  /**
+   * 成员姓名
+   */
+  @SerializedName("name")
+  private String name;
+
+  /**
+   * 性别。0表示未定义,1表示男性,2表示女性
+   */
+  @SerializedName("gender")
+  private String gender;
+
+  /**
+   * 头像url。仅在用户同意snsapi_privateinfo授权时返回
+   */
+  @SerializedName("avatar")
+  private String avatar;
+
+  /**
+   * 员工个人二维码(扫描可添加为外部联系人),仅在用户同意snsapi_privateinfo授权时返回
+   */
+  @SerializedName("qr_code")
+  private String qrCode;
+
+  public static WxCpTpUserDetail fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpTpUserDetail.class);
+  }
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpUserInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpUserInfo.java
new file mode 100644
index 0000000000..6739082faf
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpUserInfo.java
@@ -0,0 +1,61 @@
+package me.chanjar.weixin.cp.bean;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+/**
+ * @author huangxiaoming
+ */
+@Data
+public class WxCpTpUserInfo extends WxCpBaseResp {
+
+  private static final long serialVersionUID = -5028321625140879571L;
+
+  /**
+   * 用户所属企业的corpid
+   */
+  @SerializedName("CorpId")
+  private String corpId;
+
+  /**
+   * 用户在企业内的UserID,如果该企业与第三方应用有授权关系时,返回明文UserId,否则返回密文UserId
+   */
+  @SerializedName("UserId")
+  private String userId;
+
+  /**
+   * 手机设备号(由企业微信在安装时随机生成,删除重装会改变,升级不受影响)
+   */
+  @SerializedName("DeviceId")
+  private String deviceId;
+
+  /**
+   * 成员票据,最大为512字节。
+   * scope为snsapi_userinfo或snsapi_privateinfo,且用户在应用可见范围之内时返回此参数。
+   * 后续利用该参数可以获取用户信息或敏感信息,参见:https://work.weixin.qq.com/api/doc/90001/90143/91122
+   */
+  @SerializedName("user_ticket")
+  private String userTicket;
+
+  /**
+   * user_ticket的有效时间(秒),随user_ticket一起返回
+   */
+  @SerializedName("expires_in")
+  private String expiresIn;
+
+  /**
+   * 全局唯一。对于同一个服务商,不同应用获取到企业内同一个成员的open_userid是相同的,最多64个字节。仅第三方应用可获取
+   */
+  @SerializedName("open_userid")
+  private String openUserId;
+
+  public static WxCpTpUserInfo fromJson(String json) {
+    return WxCpGsonBuilder.create().fromJson(json, WxCpTpUserInfo.class);
+  }
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+}
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
deleted file mode 100644
index f39b062c08..0000000000
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpTpXmlMessage.java
+++ /dev/null
@@ -1,63 +0,0 @@
-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.xml.XStreamCDataConverter;
-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 a6ceb4e551..a0ecac2683 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
@@ -33,6 +33,10 @@ public class WxCpUser implements Serializable {
   private String avatar;
   private String thumbAvatar;
   private String mainDepartment;
+  /**
+   * 全局唯一。对于同一个服务商,不同应用获取到企业内同一个成员的open_userid是相同的,最多64个字节。仅第三方应用可获取
+   */
+  private String openUserId;
 
   /**
    * 地址。长度最大128个字符
@@ -86,6 +90,9 @@ public String toJson() {
 
   @Data
   @Accessors(chain = true)
+  @Builder
+  @NoArgsConstructor
+  @AllArgsConstructor
   public static class Attr {
     /**
      * 属性类型: 0-文本 1-网页
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/NewArticle.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/NewArticle.java
index 5eede5fa53..854c0ca89a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/NewArticle.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/article/NewArticle.java
@@ -37,4 +37,8 @@ public class NewArticle implements Serializable {
    */
   private String picUrl;
 
+  /**
+   * 按钮文字,仅在图文数为1条时才生效。 默认为“阅读全文”, 不超过4个文字,超过自动截断。该设置只在企业微信上生效,微工作台(原企业号)上不生效。
+   */
+  private String btnText;
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayResult.java
index 609673a193..742cc49896 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayResult.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpContactWayResult.java
@@ -12,7 +12,10 @@
 public class WxCpContactWayResult extends WxCpBaseResp {
   @SerializedName("config_id")
   private String configId;
-
+  
+  @SerializedName("qr_code")
+  private String qrCode;
+  
   public static WxCpContactWayResult fromJson(String json) {
     return WxCpGsonBuilder.create().fromJson(json, WxCpContactWayResult.class);
   }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUpdateRemarkRequest.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUpdateRemarkRequest.java
new file mode 100644
index 0000000000..678995590b
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUpdateRemarkRequest.java
@@ -0,0 +1,101 @@
+package me.chanjar.weixin.cp.bean.external;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+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;
+
+/**
+ * 修改客户备注信息请求.
+ *
+ * @author Binary Wang
+ * @date 2020-09-19
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class WxCpUpdateRemarkRequest implements Serializable {
+  private static final long serialVersionUID = -4960239393895754138L;
+
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+  /**
+   * 
+   * 字段名:userid
+   * 是否必须:是
+   * 描述:企业成员的userid
+   * 
+ */ + @SerializedName("userid") + private String userId; + + /** + *
+   * 字段名:external_userid
+   * 是否必须:是
+   * 描述:外部联系人userid
+   * 
+ */ + @SerializedName("external_userid") + private String externalUserId; + + /** + *
+   * 字段名:remark
+   * 是否必须:否
+   * 描述:此用户对外部联系人的备注,最多20个字符
+   * 
+ */ + @SerializedName("remark") + private String remark; + + /** + *
+   * 字段名:description
+   * 是否必须:否
+   * 描述:此用户对外部联系人的描述,最多150个字符
+   * 
+ */ + @SerializedName("description") + private String description; + + /** + *
+   * 字段名:remark_company
+   * 是否必须:否
+   * 描述:此用户对外部联系人备注的所属公司名称,最多20个字符
+   * 
+ */ + @SerializedName("remark_company") + private String remarkCompany; + + /** + *
+   * 字段名:remark_mobiles
+   * 是否必须:否
+   * 描述:此用户对外部联系人备注的手机号
+   * 
+ */ + @SerializedName("remark_mobiles") + private String[] remarkMobiles; + + /** + *
+   * 字段名:remark_pic_mediaid
+   * 是否必须:否
+   * 描述:备注图片的mediaid,
+   * 
+ */ + @SerializedName("remark_pic_mediaid") + private String remarkPicMediaId; + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalContactInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalContactInfo.java deleted file mode 100644 index c28326e849..0000000000 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalContactInfo.java +++ /dev/null @@ -1,144 +0,0 @@ -package me.chanjar.weixin.cp.bean.external; - -import com.google.gson.annotations.SerializedName; -import lombok.*; -import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; - -import java.util.List; - -/** - *
- * 外部联系人详情
- * Created by Binary Wang on 2018/9/16.
- * 参考文档:https://work.weixin.qq.com/api/doc#13878
- * 
- * - * @author Binary Wang - */ -@Getter -@Setter -public class WxCpUserExternalContactInfo { - @SerializedName("external_contact") - private ExternalContact externalContact; - - @SerializedName("follow_user") - private List followedUsers; - - @Getter - @Setter - public static class ExternalContact { - @SerializedName("external_userid") - private String externalUserId; - - @SerializedName("position") - private String position; - - @SerializedName("name") - private String name; - - @SerializedName("avatar") - private String avatar; - - @SerializedName("corp_name") - private String corpName; - - @SerializedName("corp_full_name") - private String corpFullName; - - @SerializedName("type") - private Integer type; - - @SerializedName("gender") - private Integer gender; - - @SerializedName("unionid") - private String unionId; - - @SerializedName("external_profile") - private ExternalProfile externalProfile; - } - - @Setter - @Getter - public static class ExternalProfile { - @SerializedName("external_attr") - private List externalAttrs; - } - - @Data - @Builder - @NoArgsConstructor - @AllArgsConstructor - public static class ExternalAttribute { - @Setter - @Getter - public static class Text { - private String value; - } - - @Setter - @Getter - public static class Web { - private String title; - private String url; - } - - @Setter - @Getter - public static class MiniProgram { - @SerializedName("pagepath") - private String pagePath; - private String appid; - private String title; - } - - private int type; - - private String name; - - private Text text; - - private Web web; - - @SerializedName("miniprogram") - private MiniProgram miniProgram; - } - - @Setter - @Getter - public static class FollowedUser { - @SerializedName("userid") - private String userId; - private String remark; - private String description; - @SerializedName("createtime") - private Long createTime; - private String state; - @SerializedName("remark_company") - private String remarkCompany; - @SerializedName("remark_mobiles") - private String[] remarkMobiles; - private Tag[] tags; - @SerializedName("remark_corp_name") - private String remarkCorpName; - @SerializedName("add_way") - private String addWay; - @SerializedName("oper_userid") - private String operUserId; - - } - - public static WxCpUserExternalContactInfo fromJson(String json) { - return WxCpGsonBuilder.create().fromJson(json, WxCpUserExternalContactInfo.class); - } - - @Setter - @Getter - public static class Tag { - @SerializedName("group_name") - private String groupName; - @SerializedName("tag_name") - private String tagName; - private int type; - } -} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalGroupChatInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalGroupChatInfo.java index 0c33309bca..83eb5c3766 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalGroupChatInfo.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalGroupChatInfo.java @@ -58,6 +58,14 @@ public static class GroupMember { @SerializedName("join_time") private Long joinTime; + + /** + * 外部联系人在微信开放平台的唯一身份标识(微信unionid) + * 通过此字段企业可将外部联系人与公众号/小程序用户关联起来 + * 仅当群成员类型是微信用户(包括企业成员未添加好友),且企业或第三方服务商绑定了微信开发者ID有此字段 + */ + @SerializedName("unionid") + private String unionId; /** * 入群方式。 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/ExternalContact.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/ExternalContact.java new file mode 100644 index 0000000000..5b7f9e67b1 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/ExternalContact.java @@ -0,0 +1,103 @@ +package me.chanjar.weixin.cp.bean.external.contact; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +import java.io.Serializable; +import java.util.List; + +/** + * 外部联系人. + * + * @author Binary Wang + * @date 2020-11-04 + */ +@Getter +@Setter +public class ExternalContact implements Serializable { + private static final long serialVersionUID = -1049085217436072418L; + + @SerializedName("external_userid") + private String externalUserId; + + @SerializedName("position") + private String position; + + @SerializedName("name") + private String name; + + @SerializedName("avatar") + private String avatar; + + @SerializedName("corp_name") + private String corpName; + + @SerializedName("corp_full_name") + private String corpFullName; + + @SerializedName("type") + private Integer type; + + @SerializedName("gender") + private Integer gender; + + @SerializedName("unionid") + private String unionId; + + @SerializedName("external_profile") + private ExternalProfile externalProfile; + + @Data + public static class ExternalProfile implements Serializable { + private static final long serialVersionUID = -2899906589789022765L; + + @SerializedName("external_attr") + private List externalAttrs; + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ExternalAttribute implements Serializable { + private static final long serialVersionUID = -1262278808286421085L; + + private int type; + + private String name; + + private Text text; + + private Web web; + + @SerializedName("miniprogram") + private MiniProgram miniProgram; + + @Data + public static class Text implements Serializable { + private static final long serialVersionUID = -8161579335600269094L; + + private String value; + } + + @Data + public static class Web implements Serializable { + private static final long serialVersionUID = 3664557135411521862L; + + private String title; + private String url; + } + + @Data + public static class MiniProgram implements Serializable { + private static final long serialVersionUID = -5329210594501835796L; + + @SerializedName("pagepath") + private String pagePath; + + private String appid; + + private String title; + } + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/FollowedUser.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/FollowedUser.java new file mode 100644 index 0000000000..a9fb7ba836 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/FollowedUser.java @@ -0,0 +1,81 @@ +package me.chanjar.weixin.cp.bean.external.contact; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; + +import java.io.Serializable; + +/** + * 添加了外部联系人的企业成员. + * + * @author Binary Wang + * @date 2020-11-04 + */ +@Data +public class FollowedUser { + @SerializedName("userid") + private String userId; + + private String remark; + + private String description; + + @SerializedName("createtime") + private Long createTime; + + private String state; + + @SerializedName("remark_company") + private String remarkCompany; + + @SerializedName("remark_mobiles") + private String[] remarkMobiles; + + /** + * 批量获取客户详情 接口专用 + */ + @SerializedName("tag_id") + private String[] tagIds; + + /** + * 获取客户详情 接口专用 + */ + private Tag[] tags; + + @SerializedName("remark_corp_name") + private String remarkCorpName; + + @SerializedName("add_way") + private String addWay; + + @SerializedName("oper_userid") + private String operatorUserId; + + @Data + public static class Tag implements Serializable { + private static final long serialVersionUID = -7556237053703295482L; + + /** + * 该成员添加此外部联系人所打标签的分组名称(标签功能需要企业微信升级到2.7.5及以上版本) + */ + @SerializedName("group_name") + private String groupName; + + /** + * 该成员添加此外部联系人所打标签名称 + */ + @SerializedName("tag_name") + private String tagName; + + /** + * 该成员添加此外部联系人所打企业标签的id,仅企业设置(type为1)的标签返回 + */ + @SerializedName("tag_id") + private String tagId; + + /** + * 该成员添加此外部联系人所打标签类型, 1-企业设置, 2-用户自定义 + */ + private int type; + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/WxCpExternalContactBatchInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/WxCpExternalContactBatchInfo.java new file mode 100644 index 0000000000..65e3326132 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/WxCpExternalContactBatchInfo.java @@ -0,0 +1,48 @@ +package me.chanjar.weixin.cp.bean.external.contact; + +import com.google.gson.annotations.SerializedName; +import lombok.Getter; +import lombok.Setter; +import me.chanjar.weixin.cp.bean.WxCpBaseResp; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + *
+ * 批量获取客户详情
+ * 参考文档:https://work.weixin.qq.com/api/doc/90000/90135/92994
+ * 
+ * + * @author alucardxh + */ +@Getter +@Setter +public class WxCpExternalContactBatchInfo extends WxCpBaseResp implements Serializable { + private static final long serialVersionUID = -5166048319463473186L; + + @SerializedName("external_contact_list") + private List externalContactList; + + @SerializedName("next_cursor") + private String nextCursor; + + @Getter + @Setter + public static class ExternalContactInfo implements Serializable { + private static final long serialVersionUID = 4723983768235723206L; + + @SerializedName("external_contact") + private ExternalContact externalContact; + + @SerializedName("follow_info") + private FollowedUser followInfo; + } + + + public static WxCpExternalContactBatchInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpExternalContactBatchInfo.class); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/WxCpExternalContactInfo.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/WxCpExternalContactInfo.java new file mode 100644 index 0000000000..bd7229384c --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/external/contact/WxCpExternalContactInfo.java @@ -0,0 +1,33 @@ +package me.chanjar.weixin.cp.bean.external.contact; + +import com.google.gson.annotations.SerializedName; +import lombok.*; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.io.Serializable; +import java.util.List; + +/** + *
+ * 外部联系人详情
+ * Created by Binary Wang on 2018/9/16.
+ * 参考文档:https://work.weixin.qq.com/api/doc#13878
+ * 
+ * + * @author Binary Wang + */ +@Data +public class WxCpExternalContactInfo implements Serializable { + private static final long serialVersionUID = 4311777322534499260L; + + @SerializedName("external_contact") + private ExternalContact externalContact; + + @SerializedName("follow_user") + private List followedUsers; + + public static WxCpExternalContactInfo fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpExternalContactInfo.class); + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAppChatMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpAppChatMessage.java similarity index 99% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAppChatMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpAppChatMessage.java index 9aa2a2fc49..10dd3c1b27 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpAppChatMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpAppChatMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import com.google.gson.JsonArray; import com.google.gson.JsonObject; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpGroupRobotMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java similarity index 96% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpGroupRobotMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java index 937a88cb09..c97e5eb16c 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpGroupRobotMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpGroupRobotMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -10,7 +10,7 @@ import java.util.List; -import static me.chanjar.weixin.common.api.WxConsts.GroupRobotMsgType.*; +import static me.chanjar.weixin.cp.constant.WxCpConsts.GroupRobotMsgType.*; /** * 微信群机器人消息 diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java new file mode 100644 index 0000000000..0e3f670874 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessage.java @@ -0,0 +1,244 @@ +package me.chanjar.weixin.cp.bean.message; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; +import me.chanjar.weixin.cp.bean.article.MpnewsArticle; +import me.chanjar.weixin.cp.bean.article.NewArticle; +import org.apache.commons.lang3.ArrayUtils; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static me.chanjar.weixin.cp.constant.WxCpConsts.LinkedCorpMsgType.*; + +/** + * 互联企业消息. + * + * @author Binary Wang + * @date 2020-08-30 + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +public class WxCpLinkedCorpMessage implements Serializable { + private static final long serialVersionUID = 8833792280163704238L; + + /** + * 1表示发送给应用可见范围内的所有人(包括互联企业的成员),默认为0 + */ + private Boolean isToAll; + + /** + * 成员ID列表(消息接收者,最多支持1000个)。每个元素的格式为: corpid/userid,其中,corpid为该互联成员所属的企业,userid为该互联成员所属企业中的帐号。如果是本企业的成员,则直接传userid即可 + */ + private String[] toUsers; + /** + * 部门ID列表,最多支持100个。partyid在互联圈子内唯一。每个元素都是字符串类型,格式为:linked_id/party_id,其中linked_id是互联id,party_id是在互联圈子中的部门id。如果是本企业的部门,则直接传party_id即可。 + */ + private String[] toParties; + /** + * 本企业的标签ID列表,最多支持100个。 + */ + private String[] toTags; + + /** + * 企业应用的id,整型。可在应用的设置页面查看 + */ + private Integer agentId; + private String msgType; + /** + * 消息内容,最长不超过2048个字节 + */ + private String content; + + /** + * 图片媒体文件id,可以调用上传临时素材接口获取 + */ + private String mediaId; + private String thumbMediaId; + private String title; + private String description; + /** + * 表示是否是保密消息,0表示否,1表示是,默认0 + */ + private Boolean isSafe; + private String url; + private String btnTxt; + private List articles = new ArrayList<>(); + private List mpNewsArticles = new ArrayList<>(); + private String appId; + private String page; + private Boolean emphasisFirstItem; + private Map contentItems; + + /** + *
+   * 请使用.
+   * {@link LinkedCorpMsgType#TEXT}
+   * {@link LinkedCorpMsgType#IMAGE}
+   * {@link LinkedCorpMsgType#VIDEO}
+   * {@link LinkedCorpMsgType#NEWS}
+   * {@link LinkedCorpMsgType#MPNEWS}
+   * {@link LinkedCorpMsgType#MARKDOWN}
+   * {@link LinkedCorpMsgType#MINIPROGRAM_NOTICE}
+   * 
+ * + * @param msgType 消息类型 + */ + public void setMsgType(String msgType) { + this.msgType = msgType; + } + + public String toJson() { + JsonObject messageJson = new JsonObject(); + + if (ArrayUtils.isNotEmpty(this.getToUsers())) { + messageJson.add("touser", WxGsonBuilder.create().toJsonTree(this.getToUsers())); + } + + if (ArrayUtils.isNotEmpty(this.getToParties())) { + messageJson.add("toparty", WxGsonBuilder.create().toJsonTree(this.getToParties())); + } + + if (ArrayUtils.isNotEmpty(this.getToTags())) { + messageJson.add("totag", WxGsonBuilder.create().toJsonTree(this.getToTags())); + } + + if (this.getIsToAll() != null) { + messageJson.addProperty("toall", this.getIsToAll() ? 1 : 0); + } + messageJson.addProperty("msgtype", this.getMsgType()); + + if (this.getAgentId() != null) { + messageJson.addProperty("agentid", this.getAgentId()); + } + + this.handleMsgType(messageJson); + + if (this.getIsSafe() != null) { + messageJson.addProperty("safe", this.getIsSafe() ? 1 : 0); + } + + return messageJson.toString(); + } + + private void handleMsgType(JsonObject messageJson) { + switch (this.getMsgType()) { + case TEXT: { + JsonObject text = new JsonObject(); + text.addProperty("content", this.getContent()); + messageJson.add("text", text); + break; + } + case MARKDOWN: { + JsonObject text = new JsonObject(); + text.addProperty("content", this.getContent()); + messageJson.add("markdown", text); + break; + } + case TEXTCARD: { + JsonObject text = new JsonObject(); + text.addProperty("title", this.getTitle()); + text.addProperty("description", this.getDescription()); + text.addProperty("url", this.getUrl()); + text.addProperty("btntxt", this.getBtnTxt()); + messageJson.add("textcard", text); + break; + } + case IMAGE: { + JsonObject image = new JsonObject(); + image.addProperty("media_id", this.getMediaId()); + messageJson.add("image", image); + break; + } + case FILE: { + JsonObject image = new JsonObject(); + image.addProperty("media_id", this.getMediaId()); + messageJson.add("file", image); + break; + } + case VIDEO: { + JsonObject video = new JsonObject(); + video.addProperty("media_id", this.getMediaId()); + video.addProperty("title", this.getTitle()); + video.addProperty("description", this.getDescription()); + messageJson.add("video", video); + break; + } + case NEWS: { + JsonObject newsJsonObject = new JsonObject(); + JsonArray articleJsonArray = new JsonArray(); + for (NewArticle article : this.getArticles()) { + JsonObject articleJson = new JsonObject(); + articleJson.addProperty("title", article.getTitle()); + articleJson.addProperty("description", article.getDescription()); + articleJson.addProperty("url", article.getUrl()); + articleJson.addProperty("picurl", article.getPicUrl()); + articleJson.addProperty("btntxt", article.getBtnText()); + articleJsonArray.add(articleJson); + } + newsJsonObject.add("articles", articleJsonArray); + messageJson.add("news", newsJsonObject); + break; + } + case MPNEWS: { + JsonObject newsJsonObject = new JsonObject(); + if (this.getMediaId() != null) { + newsJsonObject.addProperty("media_id", this.getMediaId()); + } else { + JsonArray articleJsonArray = new JsonArray(); + for (MpnewsArticle article : this.getMpNewsArticles()) { + JsonObject articleJson = new JsonObject(); + articleJson.addProperty("title", article.getTitle()); + articleJson.addProperty("thumb_media_id", article.getThumbMediaId()); + articleJson.addProperty("author", article.getAuthor()); + articleJson.addProperty("content_source_url", article.getContentSourceUrl()); + articleJson.addProperty("content", article.getContent()); + articleJson.addProperty("digest", article.getDigest()); + if (article.getShowCoverPic() != null) { + articleJson.addProperty("show_cover_pic", article.getShowCoverPic()); + } + articleJsonArray.add(articleJson); + } + + newsJsonObject.add("articles", articleJsonArray); + } + messageJson.add("mpnews", newsJsonObject); + break; + } + case MINIPROGRAM_NOTICE: { + JsonObject notice = new JsonObject(); + notice.addProperty("appid", this.getAppId()); + notice.addProperty("page", this.getPage()); + notice.addProperty("title", this.getTitle()); + notice.addProperty("description", this.getDescription()); + notice.addProperty("emphasis_first_item", this.getEmphasisFirstItem()); + JsonArray content = new JsonArray(); + for (Map.Entry item : this.getContentItems().entrySet()) { + JsonObject articleJson = new JsonObject(); + articleJson.addProperty("key", item.getKey()); + articleJson.addProperty("value", item.getValue()); + content.add(articleJson); + } + notice.add("content_item", content); + + messageJson.add("miniprogram_notice", notice); + break; + } + default: { + // do nothing + } + } + } + +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessage.java similarity index 99% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessage.java index b39c2229e0..9c579fd0d0 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import com.google.gson.JsonArray; import com.google.gson.JsonObject; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessageSendResult.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessageSendResult.java similarity index 97% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessageSendResult.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessageSendResult.java index d850adebcc..fa1db1065e 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpMessageSendResult.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessageSendResult.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import java.io.Serializable; import java.util.Collections; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessageSendStatistics.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessageSendStatistics.java new file mode 100644 index 0000000000..fa14d15e89 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpMessageSendStatistics.java @@ -0,0 +1,43 @@ +package me.chanjar.weixin.cp.bean.message; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; + +import java.util.List; + +/** + * 应用消息发送统计信息. + * + * @author Binary Wang + * @date 2020-09-13 + */ +@Data +public class WxCpMessageSendStatistics { + public static WxCpMessageSendStatistics fromJson(String json) { + return WxCpGsonBuilder.create().fromJson(json, WxCpMessageSendStatistics.class); + } + + private List statistics; + + @Data + public static class StatisticItem { + /** + * 应用名 + */ + @SerializedName("app_name") + private String appName; + + /** + * 应用id + */ + @SerializedName("agentid") + private Integer agentId; + + /** + * 发消息成功人次 + */ + @SerializedName("count") + private Integer count; + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java new file mode 100644 index 0000000000..c8273e9a98 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java @@ -0,0 +1,420 @@ +package me.chanjar.weixin.cp.bean.message; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import com.thoughtworks.xstream.annotations.XStreamAlias; +import com.thoughtworks.xstream.annotations.XStreamConverter; +import com.thoughtworks.xstream.converters.basic.IntConverter; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.util.XmlUtils; +import me.chanjar.weixin.common.util.xml.IntegerArrayConverter; +import me.chanjar.weixin.common.util.xml.StringArrayConverter; +import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; +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; + + @XStreamAlias("ChangeType") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String changeType; + + @XStreamAlias("UserID") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String userID; + + @XStreamAlias("Department") + @XStreamConverter(value = IntegerArrayConverter.class) + protected Integer[] department; + + @XStreamAlias("MainDepartment") + @XStreamConverter(value = IntConverter.class) + protected Integer mainDepartment; + + @XStreamAlias("IsLeaderInDept") + @XStreamConverter(value = IntegerArrayConverter.class) + protected Integer[] isLeaderInDept; + + @XStreamAlias("Mobile") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String mobile; + + @XStreamAlias("Position") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String position; + + @XStreamAlias("Gender") + @XStreamConverter(value = IntConverter.class) + protected Integer gender; + + @XStreamAlias("Email") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String email; + + @XStreamAlias("Status") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String status; + + @XStreamAlias("Avatar") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String avatar; + + @XStreamAlias("Alias") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String alias; + + @XStreamAlias("Telephone") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String telephone; + + @XStreamAlias("Id") + @XStreamConverter(value = IntConverter.class) + protected Integer id; + + @XStreamAlias("Name") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String name; + + @XStreamAlias("ParentId") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String parentId; + + @XStreamAlias("Order") + @XStreamConverter(value = XStreamCDataConverter.class) + protected Integer order; + + @XStreamAlias("TagId") + @XStreamConverter(value = IntConverter.class) + protected Integer tagId; + + @XStreamAlias("AddUserItems") + @XStreamConverter(value = StringArrayConverter.class) + protected String[] addUserItems; + + @XStreamAlias("DelUserItems") + @XStreamConverter(value = StringArrayConverter.class) + protected String[] delUserItems; + + @XStreamAlias("AddPartyItems") + @XStreamConverter(value = IntegerArrayConverter.class) + protected Integer[] addPartyItems; + + @XStreamAlias("DelPartyItems") + @XStreamConverter(value = IntegerArrayConverter.class) + protected Integer[] delPartyItems; + + //ref: https://work.weixin.qq.com/api/doc/90001/90143/90585 + @XStreamAlias("ServiceCorpId") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String serviceCorpId; + + @XStreamAlias("RegisterCode") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String registerCode; + + @XStreamAlias("ContactSync") + protected ContactSync contactSync; + + @XStreamAlias("AuthUserInfo") + protected AuthUserInfo authUserInfo; + + @XStreamAlias("TemplateId") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String templateId; + + @XStreamAlias("CreateTime") + protected Long createTime; + + @XStreamAlias("ToUserName") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String toUserName; + + @XStreamAlias("FromUserName") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String fromUserName; + + @XStreamAlias("MsgType") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String msgType; + + @XStreamAlias("Event") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String event; + + @XStreamAlias("BatchJob") + protected BatchJob batchJob; + + @XStreamAlias("ExternalUserID") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String externalUserID; + + @XStreamAlias("WelcomeCode") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String welcomeCode; + + @XStreamAlias("FromUser") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String fromUser; + + @XStreamAlias("Content") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String content; + + @XStreamAlias("MsgId") + protected String msgId; + + @XStreamAlias("AgentID") + protected Integer agentID; + + @XStreamAlias("PicUrl") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String picUrl; + + @XStreamAlias("MediaId") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String mediaId; + + @XStreamAlias("Format") + @XStreamConverter(value = XStreamCDataConverter.class) + private String format; + + @XStreamAlias("ThumbMediaId") + @XStreamConverter(value = XStreamCDataConverter.class) + private String thumbMediaId; + + @XStreamAlias("Location_X") + private Double locationX; + + @XStreamAlias("Location_Y") + private Double locationY; + + @XStreamAlias("Scale") + private Double scale; + + @XStreamAlias("Label") + @XStreamConverter(value = XStreamCDataConverter.class) + private String label; + + @XStreamAlias("Title") + @XStreamConverter(value = XStreamCDataConverter.class) + private String title; + + @XStreamAlias("Description") + @XStreamConverter(value = XStreamCDataConverter.class) + private String description; + + @XStreamAlias("Url") + @XStreamConverter(value = XStreamCDataConverter.class) + private String url; + + @XStreamAlias("EventKey") + @XStreamConverter(value = XStreamCDataConverter.class) + private String eventKey; + + @XStreamAlias("Latitude") + private Double latitude; + + @XStreamAlias("Longitude") + private Double longitude; + + @XStreamAlias("Precision") + private Double precision; + + @XStreamAlias("AppType") + @XStreamConverter(value = XStreamCDataConverter.class) + private String appType; + + @XStreamAlias("ScanCodeInfo") + private WxCpXmlMessage.ScanCodeInfo scanCodeInfo = new WxCpXmlMessage.ScanCodeInfo(); + + @XStreamAlias("SendPicsInfo") + private WxCpXmlMessage.SendPicsInfo sendPicsInfo = new WxCpXmlMessage.SendPicsInfo(); + + @XStreamAlias("SendLocationInfo") + private WxCpXmlMessage.SendLocationInfo sendLocationInfo = new WxCpXmlMessage.SendLocationInfo(); + + @XStreamAlias("ApprovalInfo") + private ApprovalInfo approvalInfo = new ApprovalInfo(); + + @XStreamAlias("TaskId") + @XStreamConverter(value = XStreamCDataConverter.class) + private String taskId; + + @Data + @XStreamAlias("ContactSync") + public static class ContactSync { + @XStreamAlias("AccessToken") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String accessToken; + + @XStreamAlias("ExpiresIn") + protected Integer expiresIn; + } + + @Data + @XStreamAlias("AuthUserInfo") + public static class AuthUserInfo { + @XStreamAlias("UserId") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String userId; + } + + @Data + @XStreamAlias("BatchJob") + public static class BatchJob { + @XStreamAlias("JobId") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String JobId; + + @XStreamAlias("JobType") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String jobType; + + @XStreamAlias("ErrCode") + @XStreamConverter(value = IntConverter.class) + protected Integer errCode; + + @XStreamAlias("ErrMsg") + @XStreamConverter(value = XStreamCDataConverter.class) + protected String errMsg; + } + + @Data + @XStreamAlias("ApprovalInfo") + public static class ApprovalInfo { + @XStreamAlias("ThirdNo") + protected Long thirdNo; + + @XStreamAlias("OpenSpName") + protected String openSpName; + + @XStreamAlias("OpenTemplateId") + protected Integer openTemplateId; + + @XStreamAlias("OpenSpStatus") + protected Integer openSpStatus; + + @XStreamAlias("ApplyTime") + protected Long applyTime; + + @XStreamAlias("ApplyUserName") + protected String applyUserName; + + @XStreamAlias("ApplyUserId") + protected Integer applyUserId; + + @XStreamAlias("ApplyUserParty") + protected String applyUserParty; + + @XStreamAlias("ApplyUserImage") + protected String applyUserImage; + + @XStreamAlias("ApprovalNodes") + protected List approvalNodes; + + @XStreamAlias("NotifyNodes") + protected List notifyNodes; + + @XStreamAlias("approverstep") + protected Integer approverstep; + + //自建/第三方应用调用审批流程引擎,状态通知 + //ref: https://work.weixin.qq.com/api/doc/90001/90143/90376#审批状态通知事件 + //1.自建/第三方应用调用审批流程引擎发起申请之后,审批状态发生变化时 + //2.自建/第三方应用调用审批流程引擎发起申请之后,在“审批中”状态,有任意审批人进行审批操作时 + @Data + @XStreamAlias("ApprovalNode") + public static class ApprovalNode { + @XStreamAlias("NodeStatus") + protected Integer nodeStatus; + + @XStreamAlias("NodeAttr") + protected Integer nodeAttr; + + @XStreamAlias("NodeType") + protected Integer nodeType; + + @XStreamAlias("Items") + protected List items; + + @Data + @XStreamAlias("Item") + public static class Item { + @XStreamAlias("ItemName") + protected String itemName; + @XStreamAlias("ItemUserId") + protected Integer itemUserId; + @XStreamAlias("ItemImage") + protected String itemImage; + @XStreamAlias("ItemStatus") + protected Integer itemStatus; + @XStreamAlias("ItemSpeech") + protected String itemSpeech; + @XStreamAlias("ItemOpTime") + protected Long itemOpTime; + } + } + + @Data + @XStreamAlias("NotifyNode") + public static class NotifyNode { + @XStreamAlias("ItemName") + protected String itemName; + @XStreamAlias("ItemUserId") + protected Integer itemUserId; + @XStreamAlias("ItemImage") + protected String itemImage; + } + } + + + 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/WxCpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java similarity index 77% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessage.java index 85c6d99131..40f66df5e0 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/message/WxCpXmlMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamConverter; @@ -6,6 +6,7 @@ import lombok.Data; import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.XmlUtils; import me.chanjar.weixin.common.util.xml.IntegerArrayConverter; import me.chanjar.weixin.common.util.xml.LongArrayConverter; @@ -294,6 +295,20 @@ public class WxCpXmlMessage implements Serializable { @XStreamConverter(value = XStreamCDataConverter.class) private String address; + /** + * 日程ID. + */ + @XStreamAlias("ScheduleId") + @XStreamConverter(value = XStreamCDataConverter.class) + private String scheduleId; + + /** + * 日历ID. + */ + @XStreamAlias("CalId") + @XStreamConverter(value = XStreamCDataConverter.class) + private String calId; + /** * 扩展属性. */ @@ -436,7 +451,7 @@ public static WxCpXmlMessage fromEncryptedXml(InputStream is, WxCpConfigStorage try { return fromEncryptedXml(IOUtils.toString(is, StandardCharsets.UTF_8), wxCpConfigStorage, timestamp, nonce, msgSignature); } catch (IOException e) { - throw new RuntimeException(e); + throw new WxRuntimeException(e); } } @@ -532,6 +547,9 @@ public static class SendLocationInfo implements Serializable { } + /** + * 审批信息 + */ @XStreamAlias("ApprovalInfo") @Data public static class ApprovalInfo implements Serializable { @@ -542,11 +560,14 @@ public static class ApprovalInfo implements Serializable { */ @XStreamAlias("SpNo") private String spNo; + /** * 审批申请类型名称(审批模板名称) */ @XStreamAlias("SpName") + @XStreamConverter(value = XStreamCDataConverter.class) private String spName; + /** * 申请单状态:1-审批中;2-已通过;3-已驳回;4-已撤销;6-通过后撤销;7-已删除;10-已支付 */ @@ -557,35 +578,214 @@ public static class ApprovalInfo implements Serializable { * 审批模板id。 */ @XStreamAlias("TemplateId") + @XStreamConverter(value = XStreamCDataConverter.class) private String templateId; /** * 审批申请提交时间,Unix时间戳 */ @XStreamAlias("ApplyTime") - private Integer applyTime; + private Long applyTime; /** * 申请人信息 */ @XStreamAlias("Applyer") private Applier applier; + + /** + * 审批流程信息,可能有多个审批节点。 + */ + @XStreamImplicit(itemFieldName="SpRecord") + private List spRecords; + + /** + * 抄送信息,可能有多个抄送节点 + * 这回查字典,notifier通知人,Notifyer这不知道是什么 + */ + @XStreamImplicit(itemFieldName="Notifyer") + private List notifier; + + /** + * 审批申请备注信息,可能有多个备注节点 + */ + @XStreamImplicit(itemFieldName="Comments") + private List comments; + /** * 审批申请单变化类型 */ @XStreamAlias("StatuChangeEvent") private Integer statusChangeEvent; + /** + * 申请人信息 + */ @XStreamAlias("Applyer") @Data public static class Applier implements Serializable { private static final long serialVersionUID = -979255011922209018L; - @XStreamAlias("Applyer") + /** + * 申请人userid + */ + @XStreamAlias("UserId") private String userId; + + /** + * 申请人所在部门pid + */ @XStreamAlias("Party") private String party; } + /** + * 审批流程信息 + */ + @XStreamAlias("SpRecord") + @Data + public static class SpRecord implements Serializable{ + + private static final long serialVersionUID = 1247535623941881764L; + + /** + * 审批节点状态:1-审批中;2-已同意;3-已驳回;4-已转审 + */ + @XStreamAlias("SpStatus") + private String spStatus; + + /** + * 节点审批方式:1-或签;2-会签 + */ + @XStreamAlias("ApproverAttr") + private String approverAttr; + + /** + * 审批节点详情。当节点为标签或上级时,一个节点可能有多个分支 + */ + @XStreamImplicit(itemFieldName="Details") + private List details; + + } + + /** + * 审批节点详情 + */ + @XStreamAlias("Details") + @Data + public static class Detail implements Serializable{ + + private static final long serialVersionUID = -8446107461495047603L; + + /** + * 分支审批人 + */ + @XStreamAlias("Approver") + private Approver approver; + + /** + * 审批意见字段 + */ + @XStreamAlias("Speech") + private String speech; + + /** + * 分支审批人审批状态:1-审批中;2-已同意;3-已驳回;4-已转审 + */ + @XStreamAlias("SpStatus") + private String spStatus; + + /** + * 节点分支审批人审批操作时间,0为尚未操作 + */ + @XStreamAlias("SpTime") + private Long spTime; + + /** + * 节点分支审批人审批意见附件,赋值为media_id具体使用请参考:文档-获取临时素材 + */ + @XStreamAlias("Attach") + private String attach; + } + + /** + * 分支审批人 + */ + @Data + @XStreamAlias("Approver") + public static class Approver implements Serializable{ + + private static final long serialVersionUID = 7360442444186683191L; + + /** + * 分支审批人userid + */ + @XStreamAlias("UserId") + private String userId; + } + + /** + * 抄送信息 + */ + @Data + @XStreamAlias("Notifyer") + public static class Notifier implements Serializable{ + + private static final long serialVersionUID = -4524071522890013920L; + + /** + * 节点抄送人userid + */ + @XStreamAlias("UserId") + private String userId; + } + + /** + * 审批申请备注信息 + */ + @Data + @XStreamAlias("Comments") + public static class Comment implements Serializable{ + + private static final long serialVersionUID = 6912156206252719485L; + + /** + * 备注人信息 + */ + @XStreamAlias("CommentUserInfo") + private CommentUserInfo commentUserInfo; + + /** + * 备注提交时间 + */ + @XStreamAlias("CommentTime") + private String commentTime; + + /** + * 备注文本内容 + */ + @XStreamAlias("CommentContent") + private String commentContent; + + /** + * 备注id + */ + @XStreamAlias("CommentId") + private String commentId; + + } + + @Data + @XStreamAlias("CommentUserInfo") + private static class CommentUserInfo implements Serializable{ + + private static final long serialVersionUID = 5031739716823000947L; + + /** + * 备注人userid + */ + @XStreamAlias("UserId") + private String userId; + } } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutImageMessage.java similarity index 94% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutImageMessage.java index cbef3f8766..99792a2bfe 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutImageMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamConverter; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutMessage.java similarity index 98% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutMessage.java index a053a5460e..96991a5403 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import java.io.Serializable; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutNewsMessage.java similarity index 97% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutNewsMessage.java index c8149cabfa..87b0ca9de2 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutNewsMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamConverter; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutTextMessage.java similarity index 94% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutTextMessage.java index 6589b0b3b6..dfae8fef49 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutTextMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamConverter; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVideoMessage.java similarity index 97% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVideoMessage.java index f4aabd182b..c16d682a3b 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVideoMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamConverter; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVoiceMessage.java similarity index 94% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVoiceMessage.java index efe4a86fcf..7a2e0e49cf 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVoiceMessage.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import com.thoughtworks.xstream.annotations.XStreamAlias; import com.thoughtworks.xstream.annotations.XStreamConverter; diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java index 1064f00526..ec312c6fb4 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/BaseBuilder.java @@ -1,7 +1,7 @@ package me.chanjar.weixin.cp.bean.messagebuilder; import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.cp.bean.WxCpMessage; +import me.chanjar.weixin.cp.bean.message.WxCpMessage; import org.apache.commons.lang3.StringUtils; public class BaseBuilder { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/FileBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/FileBuilder.java index f67cf6e50d..6b36cf6cf2 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/FileBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/FileBuilder.java @@ -1,7 +1,7 @@ package me.chanjar.weixin.cp.bean.messagebuilder; import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.cp.bean.WxCpMessage; +import me.chanjar.weixin.cp.bean.message.WxCpMessage; /** * 获得消息builder diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/ImageBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/ImageBuilder.java index ddf3b7373b..6735385c90 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/ImageBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/ImageBuilder.java @@ -1,7 +1,7 @@ package me.chanjar.weixin.cp.bean.messagebuilder; import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.cp.bean.WxCpMessage; +import me.chanjar.weixin.cp.bean.message.WxCpMessage; /** * 获得消息builder diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MarkdownMsgBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MarkdownMsgBuilder.java index 6e0a4a3302..6b6af40ac5 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MarkdownMsgBuilder.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MarkdownMsgBuilder.java @@ -1,7 +1,7 @@ package me.chanjar.weixin.cp.bean.messagebuilder; import me.chanjar.weixin.common.api.WxConsts; -import me.chanjar.weixin.cp.bean.WxCpMessage; +import me.chanjar.weixin.cp.bean.message.WxCpMessage; /** *
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MiniProgramNoticeMsgBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MiniProgramNoticeMsgBuilder.java
index cf44c0f0f7..928ea38634 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MiniProgramNoticeMsgBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MiniProgramNoticeMsgBuilder.java
@@ -1,7 +1,7 @@
 package me.chanjar.weixin.cp.bean.messagebuilder;
 
 import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.cp.bean.WxCpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessage;
 
 import java.util.Map;
 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MpnewsBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MpnewsBuilder.java
index 75739803f4..bc1467e14c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MpnewsBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/MpnewsBuilder.java
@@ -1,7 +1,7 @@
 package me.chanjar.weixin.cp.bean.messagebuilder;
 
 import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.cp.bean.WxCpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessage;
 import me.chanjar.weixin.cp.bean.article.MpnewsArticle;
 
 import java.util.ArrayList;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/NewsBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/NewsBuilder.java
index 9d0d2f603a..ef661e6ed4 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/NewsBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/NewsBuilder.java
@@ -1,7 +1,7 @@
 package me.chanjar.weixin.cp.bean.messagebuilder;
 
 import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.cp.bean.WxCpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessage;
 import me.chanjar.weixin.cp.bean.article.NewArticle;
 
 import java.util.ArrayList;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TaskCardBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TaskCardBuilder.java
index 3c2d77924d..57a77503b6 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TaskCardBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TaskCardBuilder.java
@@ -1,7 +1,7 @@
 package me.chanjar.weixin.cp.bean.messagebuilder;
 
 import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.cp.bean.WxCpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessage;
 import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton;
 
 import java.util.List;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextBuilder.java
index 5079b5f846..e072b9a79d 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextBuilder.java
@@ -1,7 +1,7 @@
 package me.chanjar.weixin.cp.bean.messagebuilder;
 
 import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.cp.bean.WxCpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessage;
 
 /**
  * 文本消息builder
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextCardBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextCardBuilder.java
index 6cae763d19..306187ee40 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextCardBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/TextCardBuilder.java
@@ -1,7 +1,7 @@
 package me.chanjar.weixin.cp.bean.messagebuilder;
 
 import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.cp.bean.WxCpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessage;
 
 /**
  * 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VideoBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VideoBuilder.java
index 8d47399407..2c7fab5c8c 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VideoBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VideoBuilder.java
@@ -1,7 +1,7 @@
 package me.chanjar.weixin.cp.bean.messagebuilder;
 
 import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.cp.bean.WxCpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessage;
 
 /**
  * 视频消息builder
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VoiceBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VoiceBuilder.java
index 33c36abcbe..0e0b9f8286 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VoiceBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/messagebuilder/VoiceBuilder.java
@@ -1,7 +1,7 @@
 package me.chanjar.weixin.cp.bean.messagebuilder;
 
 import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.cp.bean.WxCpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessage;
 
 /**
  * 语音消息builder
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 54e19a4bf9..19c0231921 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
@@ -18,10 +18,10 @@ public class ContentValue implements Serializable {
   private String text;
 
   @SerializedName("new_number")
-  private Double newNumber;
+  private String newNumber;
 
   @SerializedName("new_money")
-  private Double newMoney;
+  private String newMoney;
 
   private ContentValue.Date date;
 
@@ -43,7 +43,7 @@ public static class Date implements Serializable {
     private String type;
 
     @SerializedName("s_timestamp")
-    private Double timestamp;
+    private String timestamp;
   }
 
   @Data
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/calendar/WxCpOaCalendar.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/calendar/WxCpOaCalendar.java
new file mode 100644
index 0000000000..9f8b69ae55
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/oa/calendar/WxCpOaCalendar.java
@@ -0,0 +1,115 @@
+package me.chanjar.weixin.cp.bean.oa.calendar;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import me.chanjar.weixin.common.bean.ToJson;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 日历.
+ *
+ * @author Binary Wang
+ * @date 2020-09-20
+ */
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Accessors(chain = true)
+public class WxCpOaCalendar implements Serializable, ToJson {
+  private static final long serialVersionUID = -817988838579546989L;
+
+  /**
+   * 变量名:cal_id
+   * 是否必须:更新时必须提供
+   * 描述:日历ID
+   */
+  @SerializedName("cal_id")
+  private String calId;
+
+  /**
+   * 变量名:organizer
+   * 是否必须:是
+   * 描述:指定的组织者userid。注意该字段指定后不可更新
+   */
+  @SerializedName("organizer")
+  private String organizer;
+
+  /**
+   * 变量名:readonly
+   * 是否必须:否
+   * 描述:日历组织者对日历是否只读权限(即不可编辑日历,不可在日历上添加日程,仅可作为组织者删除日历)。0-否;1-是。默认为1,即只读
+   */
+  @SerializedName("readonly")
+  private Integer readonly;
+
+  /**
+   * 变量名:set_as_default
+   * 是否必须:否
+   * 描述:是否将该日历设置为组织者的默认日历。0-否;1-是。默认为0,即不设为默认日历
+   */
+  @SerializedName("set_as_default")
+  private Integer setAsDefault;
+
+  /**
+   * 变量名:summary
+   * 是否必须:是
+   * 描述:日历标题。1 ~ 128 字符
+   */
+  @SerializedName("summary")
+  private String summary;
+
+  /**
+   * 变量名:color
+   * 是否必须:是
+   * 描述:日历在终端上显示的颜色,RGB颜色编码16进制表示,例如:”#0000FF” 表示纯蓝色
+   */
+  @SerializedName("color")
+  private String color;
+
+  /**
+   * 变量名:description
+   * 是否必须:否
+   * 描述:日历描述。0 ~ 512 字符
+   */
+  @SerializedName("description")
+  private String description;
+
+  /**
+   * 变量名:shares
+   * 是否必须:否
+   * 描述:日历共享成员列表。最多2000人
+   */
+  @SerializedName("shares")
+  private List shares;
+
+  @Data
+  @AllArgsConstructor
+  public static class ShareInfo implements Serializable {
+    private static final long serialVersionUID = -4882781114860754679L;
+
+    /**
+     * 日历共享成员的id
+     */
+    private String userid;
+
+    /**
+     * 共享成员对日历是否只读权限(即不可编辑日历,不可在日历上添加日程,仅可以退出日历)。
+     * 0-否;1-是。默认为1,即只读
+     */
+    private Integer readonly;
+  }
+
+  @Override
+  public String toJson() {
+    return WxCpGsonBuilder.create().toJson(ImmutableMap.of("calendar", this));
+  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/BaseBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/BaseBuilder.java
index 303ed3c46a..90495a4633 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/BaseBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/BaseBuilder.java
@@ -1,6 +1,6 @@
 package me.chanjar.weixin.cp.bean.outxmlbuilder;
 
-import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
 
 public abstract class BaseBuilder {
 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/ImageBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/ImageBuilder.java
index f8cd25f442..ded7e157e6 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/ImageBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/ImageBuilder.java
@@ -1,6 +1,6 @@
 package me.chanjar.weixin.cp.bean.outxmlbuilder;
 
-import me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutImageMessage;
 
 /**
  * 图片消息builder
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java
index 5a67056ab6..190ab1c971 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/NewsBuilder.java
@@ -1,7 +1,7 @@
 package me.chanjar.weixin.cp.bean.outxmlbuilder;
 
-import me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessage.Item;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutNewsMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutNewsMessage.Item;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/TextBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/TextBuilder.java
index dcdb58ca45..6a70868454 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/TextBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/TextBuilder.java
@@ -1,6 +1,6 @@
 package me.chanjar.weixin.cp.bean.outxmlbuilder;
 
-import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutTextMessage;
 
 /**
  * 文本消息builder
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VideoBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VideoBuilder.java
index 7eb38ec1a5..e823da746e 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VideoBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VideoBuilder.java
@@ -1,6 +1,6 @@
 package me.chanjar.weixin.cp.bean.outxmlbuilder;
 
-import me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutVideoMessage;
 
 /**
  * 视频消息builder
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VoiceBuilder.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VoiceBuilder.java
index 8bc13d9fee..2d14661242 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VoiceBuilder.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/outxmlbuilder/VoiceBuilder.java
@@ -1,6 +1,6 @@
 package me.chanjar.weixin.cp.bean.outxmlbuilder;
 
-import me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutVoiceMessage;
 
 /**
  * 语音消息builder
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/workbench/WorkBenchKeyData.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/workbench/WorkBenchKeyData.java
new file mode 100644
index 0000000000..5bcd9cf133
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/workbench/WorkBenchKeyData.java
@@ -0,0 +1,30 @@
+package me.chanjar.weixin.cp.bean.workbench;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author songshiyu
+ * @date : create in 10:21 2020/9/28
+ * @description: 关键数据型模板类型
+ */
+@Data
+public class WorkBenchKeyData implements Serializable {
+  /*
+   * 关键数据名称
+   * */
+  private String key;
+  /*
+   * 关键数据
+   * */
+  private String data;
+  /*
+   * 点击跳转url,若不填且应用设置了主页url,则跳转到主页url,否则跳到应用会话窗口
+   * */
+  private String jumpUrl;
+  /*
+   * 若应用为小程序类型,该字段填小程序pagepath,若未设置,跳到小程序主页
+   * */
+  private String pagePath;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/workbench/WorkBenchList.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/workbench/WorkBenchList.java
new file mode 100644
index 0000000000..c03e724732
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/workbench/WorkBenchList.java
@@ -0,0 +1,26 @@
+package me.chanjar.weixin.cp.bean.workbench;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * @author songshiyu
+ * @date : create in 10:21 2020/9/28
+ * @description: 列表模板类型
+ */
+@Data
+public class WorkBenchList implements Serializable {
+  /*
+   * 列表显示文字,不超过128个字节
+   * */
+  private String title;
+  /*
+   * 点击跳转url,若不填且应用设置了主页url,则跳转到主页url,否则跳到应用会话窗口
+   * */
+  private String jumpUrl;
+  /*
+   * 若应用为小程序类型,该字段填小程序pagepath,若未设置,跳到小程序主页
+   * */
+  private String pagePath;
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
index 40c29ed0c9..0fda376633 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java
@@ -26,67 +26,76 @@ public interface WxCpTpConfigStorage {
    */
   String getApiUrl(String path);
 
-  String getSuiteAccessToken();
-
-  boolean isSuiteAccessTokenExpired();
-
   /**
-   * 强制将suite access token过期掉.
+   * 第三方应用的suite access token相关
    */
+  String getSuiteAccessToken();
+  boolean isSuiteAccessTokenExpired();
+  //强制将suite access token过期掉.
   void expireSuiteAccessToken();
-
   void updateSuiteAccessToken(WxAccessToken suiteAccessToken);
+  void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds);
 
-  void updateSuiteAccessToken(String suiteAccessToken, int expiresIn);
-
+  /**
+   * 第三方应用的suite ticket相关
+   */
   String getSuiteTicket();
-
   boolean isSuiteTicketExpired();
+  //强制将suite ticket过期掉.
+  void expireSuiteTicket();
+  //应该是线程安全的
+  void updateSuiteTicket(String suiteTicket, int expiresInSeconds);
 
   /**
-   * 强制将suite ticket过期掉.
+   * 第三方应用的其他配置,来自于企微配置
    */
-  void expireSuiteTicket();
+  String getSuiteId();
+  String getSuiteSecret();
+  // 第三方应用的token,用来检查应用的签名
+  String getToken();
+  //第三方应用的EncodingAESKey,用来检查签名
+  String getAesKey();
 
   /**
-   * 应该是线程安全的.
+   * 企微服务商企业ID & 企业secret
    */
-  void updateSuiteTicket(String suiteTicket, int expiresInSeconds);
-
   String getCorpId();
-
   String getCorpSecret();
 
-  String getSuiteId();
-
-  String getSuiteSecret();
-
-  String getToken();
+  /**
+   * 授权企业的access token相关
+   */
+  String getAccessToken(String authCorpId);
+  boolean isAccessTokenExpired(String authCorpId);
+  void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds);
 
-  String getAesKey();
+  /**
+   * 授权企业的js api ticket相关
+   */
+  String getAuthCorpJsApiTicket(String authCorpId);
+  boolean isAuthCorpJsApiTicketExpired(String authCorpId);
+  void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);
 
-  long getExpiresTime();
+  /**
+   * 授权企业的第三方应用js api ticket相关
+   */
+  String getAuthSuiteJsApiTicket(String authCorpId);
+  boolean isAuthSuiteJsApiTicketExpired(String authCorpId);
+  void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds);;
 
+  /**
+   * 网络代理相关
+   */
   String getHttpProxyHost();
-
   int getHttpProxyPort();
-
   String getHttpProxyUsername();
-
   String getHttpProxyPassword();
-
-  File getTmpDirFile();
-
-  /**
-   * http client builder.
-   *
-   * @return ApacheHttpClientBuilder
-   */
   ApacheHttpClientBuilder getApacheHttpClientBuilder();
 
-  /**
-   * 是否自动刷新token
-   * @return .
-   */
   boolean autoRefreshToken();
+
+  // 毫无相关性的代码
+  @Deprecated
+  File getTmpDirFile();
+
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
index a9b449530a..80aca779df 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpDefaultConfigImpl.java
@@ -24,7 +24,7 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
 
   private volatile String token;
   protected volatile String accessToken;
-  protected Lock accessTokenLock = new ReentrantLock();
+  protected transient Lock accessTokenLock = new ReentrantLock();
   private volatile String aesKey;
   protected volatile Integer agentId;
   private volatile long expiresTime;
@@ -37,16 +37,16 @@ public class WxCpDefaultConfigImpl implements WxCpConfigStorage, Serializable {
   private volatile String httpProxyPassword;
 
   private volatile String jsapiTicket;
-  protected Lock jsapiTicketLock = new ReentrantLock();
+  protected transient Lock jsapiTicketLock = new ReentrantLock();
   private volatile long jsapiTicketExpiresTime;
 
   private volatile String agentJsapiTicket;
-  protected Lock agentJsapiTicketLock = new ReentrantLock();
+  protected transient Lock agentJsapiTicketLock = new ReentrantLock();
   private volatile long agentJsapiTicketExpiresTime;
 
   private volatile File tmpDirFile;
 
-  private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
+  private transient volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
 
   private volatile String baseApiUrl;
 
@@ -297,4 +297,9 @@ public String getWebhookKey() {
   public void setApacheHttpClientBuilder(ApacheHttpClientBuilder apacheHttpClientBuilder) {
     this.apacheHttpClientBuilder = apacheHttpClientBuilder;
   }
+
+  public WxCpDefaultConfigImpl setWebhookKey(String webhookKey) {
+    this.webhookKey = webhookKey;
+    return this;
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedissonConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedissonConfigImpl.java
index b178578592..1ba4977b30 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedissonConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpRedissonConfigImpl.java
@@ -41,7 +41,7 @@ public WxCpRedissonConfigImpl(@NonNull RedissonClient redissonClient) {
     this(redissonClient, null);
   }
 
-  private WxCpRedissonConfigImpl(@NonNull WxRedisOps redisOps, String keyPrefix) {
+  public WxCpRedissonConfigImpl(@NonNull WxRedisOps redisOps, String keyPrefix) {
     this.redisOps = redisOps;
     this.keyPrefix = keyPrefix;
   }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
index be4b046a48..a748e301db 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java
@@ -7,6 +7,8 @@
 
 import java.io.File;
 import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化.
@@ -24,25 +26,34 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
 
   private volatile String token;
   private volatile String suiteAccessToken;
+  private volatile long   suiteAccessTokenExpiresTime;
   private volatile String aesKey;
-  private volatile long expiresTime;
 
+  private volatile String suiteTicket;
+  private volatile long suiteTicketExpiresTime;
   private volatile String oauth2redirectUri;
 
+  private volatile Map authCorpAccessTokenMap = new HashMap<>();
+  private volatile Map authCorpAccessTokenExpireTimeMap = new HashMap<>();
+
+  private volatile Map authCorpJsApiTicketMap = new HashMap<>();
+  private volatile Map authCorpJsApiTicketExpireTimeMap = new HashMap<>();
+
+  private volatile Map authSuiteJsApiTicketMap = new HashMap<>();
+  private volatile Map authSuiteJsApiTicketExpireTimeMap = new HashMap<>();
+
   private volatile String httpProxyHost;
   private volatile int httpProxyPort;
   private volatile String httpProxyUsername;
   private volatile String httpProxyPassword;
 
-  private volatile String suiteTicket;
-  private volatile long suiteTicketExpiresTime;
-
   private volatile File tmpDirFile;
 
   private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
 
   private volatile String baseApiUrl;
 
+
   @Override
   public void setBaseApiUrl(String baseUrl) {
     this.baseApiUrl = baseUrl;
@@ -67,12 +78,12 @@ public void setSuiteAccessToken(String suiteAccessToken) {
 
   @Override
   public boolean isSuiteAccessTokenExpired() {
-    return System.currentTimeMillis() > this.expiresTime;
+    return System.currentTimeMillis() > this.suiteAccessTokenExpiresTime;
   }
 
   @Override
   public void expireSuiteAccessToken() {
-    this.expiresTime = 0;
+    this.suiteAccessTokenExpiresTime = 0;
   }
 
   @Override
@@ -83,66 +94,58 @@ public synchronized void updateSuiteAccessToken(WxAccessToken suiteAccessToken)
   @Override
   public synchronized void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) {
     this.suiteAccessToken = suiteAccessToken;
-    this.expiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
+    this.suiteAccessTokenExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
   }
 
-  @Override
-  public String getCorpId() {
-    return this.corpId;
+  @Deprecated
+  public void setSuiteAccessTokenExpiresTime(long suiteAccessTokenExpiresTime) {
+    this.suiteAccessTokenExpiresTime = suiteAccessTokenExpiresTime;
   }
 
-  public void setCorpId(String corpId) {
-    this.corpId = corpId;
+  @Override
+  public String getSuiteTicket() {
+    return this.suiteTicket;
   }
 
   @Override
-  public String getCorpSecret() {
-    return this.corpSecret;
+  public boolean isSuiteTicketExpired() {
+    return System.currentTimeMillis() > this.suiteTicketExpiresTime;
   }
 
-  public void setCorpSecret(String corpSecret) {
-    this.corpSecret = corpSecret;
+  @Override
+  public void expireSuiteTicket() {
+    this.suiteTicketExpiresTime = 0;
   }
 
   @Override
-  public String getSuiteTicket() {
-    return this.suiteTicket;
+  public synchronized void updateSuiteTicket(String suiteTicket, int expiresInSeconds) {
+    this.suiteTicket = suiteTicket;
+    // 预留200秒的时间
+    this.suiteTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
   }
 
+
+  @Deprecated
   public void setSuiteTicket(String suiteTicket) {
     this.suiteTicket = suiteTicket;
   }
 
+  @Deprecated
   public long getSuiteTicketExpiresTime() {
     return this.suiteTicketExpiresTime;
   }
 
+  @Deprecated
   public void setSuiteTicketExpiresTime(long suiteTicketExpiresTime) {
     this.suiteTicketExpiresTime = suiteTicketExpiresTime;
   }
 
-  @Override
-  public boolean isSuiteTicketExpired() {
-    return System.currentTimeMillis() > this.suiteTicketExpiresTime;
-  }
-
-  @Override
-  public synchronized void updateSuiteTicket(String suiteTicket, int expiresInSeconds) {
-    this.suiteTicket = suiteTicket;
-    // 预留200秒的时间
-    this.suiteTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L;
-  }
-
-  @Override
-  public void expireSuiteTicket() {
-    this.suiteTicketExpiresTime = 0;
-  }
-
   @Override
   public String getSuiteId() {
     return this.suiteId;
   }
 
+  @Deprecated
   public void setSuiteId(String corpId) {
     this.suiteId = corpId;
   }
@@ -152,6 +155,7 @@ public String getSuiteSecret() {
     return this.suiteSecret;
   }
 
+  @Deprecated
   public void setSuiteSecret(String corpSecret) {
     this.suiteSecret = corpSecret;
   }
@@ -161,26 +165,107 @@ public String getToken() {
     return this.token;
   }
 
+  @Deprecated
   public void setToken(String token) {
     this.token = token;
   }
 
   @Override
-  public long getExpiresTime() {
-    return this.expiresTime;
+  public String getAesKey() {
+    return this.aesKey;
+  }
+
+  @Deprecated
+  public void setAesKey(String aesKey) {
+    this.aesKey = aesKey;
+  }
+
+
+  @Override
+  public String getCorpId() {
+    return this.corpId;
   }
 
-  public void setExpiresTime(long expiresTime) {
-    this.expiresTime = expiresTime;
+  @Deprecated
+  public void setCorpId(String corpId) {
+    this.corpId = corpId;
   }
 
   @Override
-  public String getAesKey() {
-    return this.aesKey;
+  public String getCorpSecret() {
+    return this.corpSecret;
   }
 
-  public void setAesKey(String aesKey) {
-    this.aesKey = aesKey;
+  @Deprecated
+  public void setCorpSecret(String corpSecret) {
+    this.corpSecret = corpSecret;
+  }
+
+
+  @Override
+  public String getAccessToken(String authCorpId) {
+    return authCorpAccessTokenMap.get(authCorpId);
+  }
+
+  @Override
+  public boolean isAccessTokenExpired(String authCorpId) {
+    return System.currentTimeMillis() > authCorpAccessTokenExpireTimeMap.get(authCorpId);
+  }
+
+  @Override
+  public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
+    authCorpAccessTokenMap.put(authCorpId, accessToken);
+    // 预留200秒的时间
+    authCorpAccessTokenExpireTimeMap.put(authCorpId, System.currentTimeMillis() + (expiredInSeconds - 200) * 1000L);
+  }
+
+
+  @Override
+  public String getAuthCorpJsApiTicket(String authCorpId) {
+    return this.authCorpJsApiTicketMap.get(authCorpId);
+  }
+
+  @Override
+  public boolean isAuthCorpJsApiTicketExpired(String authCorpId) {
+    Long t = this.authCorpJsApiTicketExpireTimeMap.get(authCorpId);
+    if (t == null) {
+      return System.currentTimeMillis() > t;
+    }
+    else {
+      return true;
+    }
+  }
+
+  @Override
+  public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
+    // 应该根据不同的授权企业做区分
+    authCorpJsApiTicketMap.put(authCorpId, jsApiTicket);
+    // 预留200秒的时间
+    authCorpJsApiTicketExpireTimeMap.put(authCorpId, System.currentTimeMillis() + (expiredInSeconds - 200) * 1000L);
+  }
+
+  @Override
+  public String getAuthSuiteJsApiTicket(String authCorpId) {
+    return authSuiteJsApiTicketMap.get(authCorpId);
+  }
+
+  @Override
+  public boolean isAuthSuiteJsApiTicketExpired(String authCorpId) {
+    Long t = authSuiteJsApiTicketExpireTimeMap.get(authCorpId);
+    if (t == null) {
+      return System.currentTimeMillis() > t;
+    }
+    else {
+      return true;
+    }
+  }
+
+  @Override
+  public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
+    // 应该根据不同的授权企业做区分
+    authSuiteJsApiTicketMap.put(authCorpId, jsApiTicket);
+    // 预留200秒的时间
+    authSuiteJsApiTicketExpireTimeMap.put(authCorpId, System.currentTimeMillis() + (expiredInSeconds - 200) * 1000L);
   }
 
   public void setOauth2redirectUri(String oauth2redirectUri) {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java
new file mode 100644
index 0000000000..3b1414d9b4
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java
@@ -0,0 +1,278 @@
+package me.chanjar.weixin.cp.config.impl;
+
+
+import lombok.Builder;
+import lombok.NonNull;
+import me.chanjar.weixin.common.bean.WxAccessToken;
+import me.chanjar.weixin.common.redis.WxRedisOps;
+import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
+import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
+import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
+
+import java.io.File;
+import java.io.Serializable;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 企业微信各种固定、授权配置的Redisson存储实现
+ */
+@Builder
+public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializable {
+
+  @NonNull
+  private final WxRedisOps wxRedisOps;
+
+  //redis里面key的统一前缀
+  private final String keyPrefix = "";
+
+  private final String suiteAccessTokenKey = ":suiteAccessTokenKey:";
+
+  private final String suiteTicketKey = ":suiteTicketKey:";
+
+  private final String accessTokenKey = ":accessTokenKey:";
+
+  private final String authCorpJsApiTicketKey = ":authCorpJsApiTicketKey:";
+
+  private final String authSuiteJsApiTicketKey = ":authSuiteJsApiTicketKey:";
+
+  private volatile String baseApiUrl;
+  private volatile String httpProxyHost;
+  private volatile int httpProxyPort;
+  private volatile String httpProxyUsername;
+  private volatile String httpProxyPassword;
+  private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
+  private volatile File tmpDirFile;
+
+  /**
+   * 第三方应用的其他配置,来自于企微配置
+   */
+  private volatile String suiteId;
+  private volatile String suiteSecret;
+  // 第三方应用的token,用来检查应用的签名
+  private volatile String token;
+  //第三方应用的EncodingAESKey,用来检查签名
+  private volatile String aesKey;
+
+  /**
+   * 企微服务商企业ID & 企业secret,来自于企微配置
+   */
+  private volatile String corpId;
+  private volatile String corpSecret;
+
+  @Override
+  public void setBaseApiUrl(String baseUrl) {
+    this.baseApiUrl = baseUrl;
+  }
+
+  @Override
+  public String getApiUrl(String path) {
+    if (baseApiUrl == null) {
+      baseApiUrl = "https://qyapi.weixin.qq.com";
+    }
+    return baseApiUrl + path;  }
+
+
+  /**
+   * 第三方应用的suite access token相关
+   */
+  @Override
+  public String getSuiteAccessToken() {
+    return wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
+  }
+
+  @Override
+  public boolean isSuiteAccessTokenExpired() {
+    //remain time to live in seconds, or key not exist
+    return wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == -2;
+  }
+
+  @Override
+  public void expireSuiteAccessToken() {
+    wxRedisOps.expire(keyWithPrefix(suiteAccessTokenKey), 0, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public void updateSuiteAccessToken(WxAccessToken suiteAccessToken) {
+    updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn());
+  }
+
+  @Override
+  public void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) {
+    wxRedisOps.setValue(keyWithPrefix(suiteAccessTokenKey), suiteAccessToken, expiresInSeconds, TimeUnit.SECONDS);
+  }
+
+  /**
+   * 第三方应用的suite ticket相关
+   */
+  @Override
+  public String getSuiteTicket() {
+    return wxRedisOps.getValue(keyWithPrefix(suiteTicketKey));
+  }
+
+  @Override
+  public boolean isSuiteTicketExpired() {
+    //remain time to live in seconds, or key not exist
+    return wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == -2;
+  }
+
+  @Override
+  public void expireSuiteTicket() {
+    wxRedisOps.expire(keyWithPrefix(suiteTicketKey), 0, TimeUnit.SECONDS);
+  }
+
+  @Override
+  public void updateSuiteTicket(String suiteTicket, int expiresInSeconds) {
+    wxRedisOps.setValue(keyWithPrefix(suiteTicketKey), suiteTicket, expiresInSeconds, TimeUnit.SECONDS);
+  }
+
+  /**
+   * 第三方应用的其他配置,来自于企微配置
+   */
+  @Override
+  public String getSuiteId() {
+    return suiteId;
+  }
+
+  @Override
+  public String getSuiteSecret() {
+    return suiteSecret;
+  }
+
+  // 第三方应用的token,用来检查应用的签名
+  @Override
+  public String getToken() {
+    return token;
+  }
+
+  //第三方应用的EncodingAESKey,用来检查签名
+  @Override
+  public String getAesKey() {
+    return aesKey;
+  }
+
+
+  /**
+   * 企微服务商企业ID & 企业secret, 来自于企微配置
+   */
+  @Override
+  public String getCorpId() {
+    return corpId;
+  }
+
+  @Override
+  public String getCorpSecret() {
+    return corpSecret;
+  }
+
+
+  /**
+   * 授权企业的access token相关
+   */
+  @Override
+  public String getAccessToken(String authCorpId) {
+    return wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
+  }
+
+  @Override
+  public boolean isAccessTokenExpired(String authCorpId) {
+    //没有设置或者TTL为0,都是过期
+    return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == 0L
+      || wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == -2;
+  }
+
+  @Override
+  public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
+    wxRedisOps.setValue(keyWithPrefix(authCorpId) + accessTokenKey, accessToken, expiredInSeconds, TimeUnit.SECONDS);
+  }
+
+
+  /**
+   * 授权企业的js api ticket相关
+   */
+  @Override
+  public String getAuthCorpJsApiTicket(String authCorpId) {
+    return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey);
+  }
+
+  @Override
+  public boolean isAuthCorpJsApiTicketExpired(String authCorpId) {
+    //没有设置或TTL为0,都是过期
+    return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == 0L
+      || wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == -2;
+  }
+
+  @Override
+  public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
+    wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds, TimeUnit.SECONDS);
+  }
+
+
+  /**
+   * 授权企业的第三方应用js api ticket相关
+   */
+  @Override
+  public String getAuthSuiteJsApiTicket(String authCorpId) {
+    return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey);
+  }
+
+  @Override
+  public boolean isAuthSuiteJsApiTicketExpired(String authCorpId) {
+    //没有设置或者TTL为0,都是过期
+    return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == 0L
+      || wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == -2;
+  }
+
+  @Override
+  public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
+    wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds, TimeUnit.SECONDS);
+  }
+
+
+  /**
+   * 网络代理相关
+   */
+  @Override
+  public String getHttpProxyHost() {
+    return this.httpProxyHost;
+  }
+
+  @Override
+  public int getHttpProxyPort() {
+    return this.httpProxyPort;
+  }
+
+  @Override
+  public String getHttpProxyUsername() {
+    return this.httpProxyUsername;
+  }
+
+  @Override
+  public String getHttpProxyPassword() {
+    return this.httpProxyPassword;
+  }
+
+  @Override
+  public File getTmpDirFile() {
+    return tmpDirFile;
+  }
+
+  @Override
+  public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
+    return this.apacheHttpClientBuilder;
+  }
+
+  @Override
+  public boolean autoRefreshToken() {
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    //TODO:
+    return WxCpGsonBuilder.create().toJson(this);
+  }
+
+  private String keyWithPrefix(String key) {
+    return keyPrefix + key;
+  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
index 00e7616d17..f1e1902e05 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java
@@ -1,6 +1,8 @@
 package me.chanjar.weixin.cp.constant;
 
 
+import lombok.experimental.UtilityClass;
+
 /**
  * 
  *  企业微信api地址常量类
@@ -9,12 +11,12 @@
  *
  * @author Binary Wang
  */
+@UtilityClass
 public final class WxCpApiPathConsts {
   public static final String DEFAULT_CP_BASE_URL = "https://qyapi.weixin.qq.com";
 
   public static final String GET_JSAPI_TICKET = "/cgi-bin/get_jsapi_ticket";
   public static final String GET_AGENT_CONFIG_TICKET = "/cgi-bin/ticket/get?&type=agent_config";
-  public static final String MESSAGE_SEND = "/cgi-bin/message/send";
   public static final String GET_CALLBACK_IP = "/cgi-bin/getcallbackip";
   public static final String BATCH_REPLACE_PARTY = "/cgi-bin/batch/replaceparty";
   public static final String BATCH_REPLACE_USER = "/cgi-bin/batch/replaceuser";
@@ -23,18 +25,50 @@ public final class WxCpApiPathConsts {
   public static final String GET_TOKEN = "/cgi-bin/gettoken?corpid=%s&corpsecret=%s";
   public static final String WEBHOOK_SEND = "/cgi-bin/webhook/send?key=";
 
+  /**
+   * 消息推送相关接口
+   * https://work.weixin.qq.com/api/doc/90000/90135/90235
+   */
+  @UtilityClass
+  public static class Message {
+    /**
+     * 发送应用消息
+     */
+    public static final String MESSAGE_SEND = "/cgi-bin/message/send";
+
+    /**
+     * 查询应用消息发送统计
+     */
+    public static final String GET_STATISTICS = "/cgi-bin/message/get_statistics";
+
+    /**
+     * 互联企业发送应用消息
+     */
+    public static final String LINKEDCORP_MESSAGE_SEND = "/cgi-bin/linkedcorp/message/send";
+  }
+
+  @UtilityClass
   public static class Agent {
     public static final String AGENT_GET = "/cgi-bin/agent/get?agentid=%d";
     public static final String AGENT_SET = "/cgi-bin/agent/set";
     public static final String AGENT_LIST = "/cgi-bin/agent/list";
   }
 
+  @UtilityClass
+  public static class WorkBench {
+    public static final String WORKBENCH_TEMPLATE_SET = "/cgi-bin/agent/set_workbench_template";
+    public static final String WORKBENCH_TEMPLATE_GET = "/cgi-bin/agent/get_workbench_template";
+    public static final String WORKBENCH_DATA_SET = "/cgi-bin/agent/set_workbench_data";
+  }
+
+  @UtilityClass
   public static class OAuth2 {
     public static final String GET_USER_INFO = "/cgi-bin/user/getuserinfo?code=%s&agentid=%d";
     public static final String GET_USER_DETAIL = "/cgi-bin/user/getuserdetail";
     public static final String URL_OAUTH2_AUTHORIZE = "https://open.weixin.qq.com/connect/oauth2/authorize";
   }
 
+  @UtilityClass
   public static class Chat {
     public static final String APPCHAT_CREATE = "/cgi-bin/appchat/create";
     public static final String APPCHAT_UPDATE = "/cgi-bin/appchat/update";
@@ -42,6 +76,7 @@ public static class Chat {
     public static final String APPCHAT_SEND = "/cgi-bin/appchat/send";
   }
 
+  @UtilityClass
   public static class Department {
     public static final String DEPARTMENT_CREATE = "/cgi-bin/department/create";
     public static final String DEPARTMENT_UPDATE = "/cgi-bin/department/update";
@@ -49,6 +84,7 @@ public static class Department {
     public static final String DEPARTMENT_LIST = "/cgi-bin/department/list";
   }
 
+  @UtilityClass
   public static class Media {
     public static final String MEDIA_GET = "/cgi-bin/media/get";
     public static final String MEDIA_UPLOAD = "/cgi-bin/media/upload?type=";
@@ -56,12 +92,14 @@ public static class Media {
     public static final String JSSDK_MEDIA_GET = "/cgi-bin/media/get/jssdk";
   }
 
+  @UtilityClass
   public static class Menu {
     public static final String MENU_CREATE = "/cgi-bin/menu/create?agentid=%d";
     public static final String MENU_DELETE = "/cgi-bin/menu/delete?agentid=%d";
     public static final String MENU_GET = "/cgi-bin/menu/get?agentid=%d";
   }
 
+  @UtilityClass
   public static class Oa {
     public static final String GET_CHECKIN_DATA = "/cgi-bin/checkin/getcheckindata";
     public static final String GET_CHECKIN_OPTION = "/cgi-bin/checkin/getcheckinoption";
@@ -70,8 +108,14 @@ public static class Oa {
     public static final String GET_DIAL_RECORD = "/cgi-bin/dial/get_dial_record";
     public static final String GET_TEMPLATE_DETAIL = "/cgi-bin/oa/gettemplatedetail";
     public static final String APPLY_EVENT = "/cgi-bin/oa/applyevent";
+
+    public static final String CALENDAR_ADD = "/cgi-bin/oa/calendar/add";
+    public static final String CALENDAR_UPDATE = "/cgi-bin/oa/calendar/update";
+    public static final String CALENDAR_GET = "/cgi-bin/oa/calendar/get";
+    public static final String CALENDAR_DEL = "/cgi-bin/oa/calendar/del";
   }
 
+  @UtilityClass
   public static class Tag {
     public static final String TAG_CREATE = "/cgi-bin/tag/create";
     public static final String TAG_UPDATE = "/cgi-bin/tag/update";
@@ -82,10 +126,12 @@ public static class Tag {
     public static final String TAG_DEL_TAG_USERS = "/cgi-bin/tag/deltagusers";
   }
 
+  @UtilityClass
   public static class TaskCard {
     public static final String UPDATE_TASK_CARD = "/cgi-bin/message/update_taskcard";
   }
 
+  @UtilityClass
   public static class Tp {
     public static final String JSCODE_TO_SESSION = "/cgi-bin/service/miniprogram/jscode2session";
     public static final String GET_CORP_TOKEN = "/cgi-bin/service/get_corp_token";
@@ -94,8 +140,13 @@ public static class Tp {
     public static final String GET_PROVIDER_TOKEN = "/cgi-bin/service/get_provider_token";
     public static final String GET_PREAUTH_CODE = "/cgi-bin/service/get_pre_auth_code";
     public static final String GET_AUTH_INFO = "/cgi-bin/service/get_auth_info";
+    public static final String GET_AUTH_CORP_JSAPI_TICKET = "/cgi-bin/get_jsapi_ticket";
+    public static final String GET_SUITE_JSAPI_TICKET = "/cgi-bin/ticket/get";
+    public static final String GET_USERINFO3RD = "/cgi-bin/service/getuserinfo3rd";
+    public static final String GET_USERDETAIL3RD = "/cgi-bin/service/getuserdetail3rd";
   }
 
+  @UtilityClass
   public static class User {
     public static final String USER_AUTHENTICATE = "/cgi-bin/user/authsucc?userid=";
     public static final String USER_CREATE = "/cgi-bin/user/create";
@@ -112,6 +163,7 @@ public static class User {
     public static final String GET_EXTERNAL_CONTACT = "/cgi-bin/crm/get_external_contact?external_userid=";
   }
 
+  @UtilityClass
   public static class ExternalContact {
     @Deprecated
     public static final String GET_EXTERNAL_CONTACT = "/cgi-bin/crm/get_external_contact?external_userid=";
@@ -123,6 +175,8 @@ public static class ExternalContact {
     public static final String CLOSE_TEMP_CHAT = "/cgi-bin/externalcontact/close_temp_chat";
     public static final String GET_FOLLOW_USER_LIST = "/cgi-bin/externalcontact/get_follow_user_list";
     public static final String GET_CONTACT_DETAIL = "/cgi-bin/externalcontact/get?external_userid=";
+    public static final String GET_CONTACT_DETAIL_BATCH = "/cgi-bin/externalcontact/batch/get_by_user?";
+    public static final String UPDATE_REMARK = "/cgi-bin/externalcontact/remark";
     public static final String LIST_EXTERNAL_CONTACT = "/cgi-bin/externalcontact/list?userid=";
     public static final String LIST_UNASSIGNED_CONTACT = "/cgi-bin/externalcontact/get_unassigned_list";
     public static final String TRANSFER_UNASSIGNED_CONTACT = "/cgi-bin/externalcontact/transfer";
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java
index a69b8ea2ef..4a41fa8f71 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpConsts.java
@@ -1,5 +1,7 @@
 package me.chanjar.weixin.cp.constant;
 
+import lombok.experimental.UtilityClass;
+
 /**
  * 
  * 企业微信常量
@@ -8,11 +10,13 @@
  *
  * @author Binary Wang
  */
+@UtilityClass
 public class WxCpConsts {
   /**
    * 企业微信端推送过来的事件类型.
    * 参考文档:https://work.weixin.qq.com/api/doc#12974
    */
+  @UtilityClass
   public static class EventType {
     /**
      * 成员关注事件.
@@ -95,16 +99,46 @@ public static class EventType {
     public static final String CHANGE_EXTERNAL_CONTACT = "change_external_contact";
 
     /**
-     * 企业微信审批事件推送
+     * 企业微信审批事件推送(自建应用审批)
      */
     public static final String OPEN_APPROVAL_CHANGE = "open_approval_change";
 
+    /**
+     * 企业微信审批事件推送(系统审批)
+     */
+    public static final String SYS_APPROVAL_CHANGE = "sys_approval_change";
+
+    /**
+     * 修改日历事件
+     */
+    public static final String MODIFY_CALENDAR = "modify_calendar";
+
+    /**
+     * 删除日历事件
+     */
+    public static final String DELETE_CALENDAR = "delete_calendar";
+
+    /**
+     * 添加日程事件
+     */
+    public static final String ADD_SCHEDULE = "add_schedule";
+
+    /**
+     * 修改日程事件
+     */
+    public static final String MODIFY_SCHEDULE = "modify_schedule";
+
+    /**
+     * 删除日程事件
+     */
+    public static final String DELETE_SCHEDULE = "delete_schedule";
 
   }
 
   /**
    * 企业外部联系人变更事件的CHANGE_TYPE
    */
+  @UtilityClass
   public static class ExternalContactChangeType {
     /**
      * 新增外部联系人
@@ -128,6 +162,7 @@ public static class ExternalContactChangeType {
   /**
    * 企业微信通讯录变更事件.
    */
+  @UtilityClass
   public static class ContactChangeType {
     /**
      * 新增成员事件.
@@ -166,9 +201,81 @@ public static class ContactChangeType {
 
   }
 
+  /**
+   * 互联企业发送应用消息的消息类型.
+   */
+  @UtilityClass
+  public static class LinkedCorpMsgType {
+    /**
+     * 文本消息.
+     */
+    public static final String TEXT = "text";
+    /**
+     * 图片消息.
+     */
+    public static final String IMAGE = "image";
+    /**
+     * 视频消息.
+     */
+    public static final String VIDEO = "video";
+    /**
+     * 图文消息(点击跳转到外链).
+     */
+    public static final String NEWS = "news";
+    /**
+     * 图文消息(点击跳转到图文消息页面).
+     */
+    public static final String MPNEWS = "mpnews";
+    /**
+     * markdown消息.
+     * (目前仅支持markdown语法的子集,微工作台(原企业号)不支持展示markdown消息)
+     */
+    public static final String MARKDOWN = "markdown";
+    /**
+     * 发送文件.
+     */
+    public static final String FILE = "file";
+    /**
+     * 文本卡片消息.
+     */
+    public static final String TEXTCARD = "textcard";
+
+    /**
+     * 小程序通知消息.
+     */
+    public static final String MINIPROGRAM_NOTICE = "miniprogram_notice";
+  }
+
+  /**
+   * 群机器人的消息类型.
+   */
+  @UtilityClass
+  public static class GroupRobotMsgType {
+    /**
+     * 文本消息.
+     */
+    public static final String TEXT = "text";
+
+    /**
+     * 图片消息.
+     */
+    public static final String IMAGE = "image";
+
+    /**
+     * markdown消息.
+     */
+    public static final String MARKDOWN = "markdown";
+
+    /**
+     * 图文消息(点击跳转到外链).
+     */
+    public static final String NEWS = "news";
+  }
+
   /**
    * 应用推送消息的消息类型.
    */
+  @UtilityClass
   public static class AppChatMsgType {
     /**
      * 文本消息.
@@ -207,4 +314,24 @@ public static class AppChatMsgType {
      */
     public static final String MARKDOWN = "markdown";
   }
+
+  @UtilityClass
+  public static class WorkBenchType {
+    /*
+    * 关键数据型
+    * */
+    public static final String KEYDATA = "keydata";
+    /*
+    * 图片型
+    * */
+    public static final String IMAGE = "image";
+    /*
+    * 列表型
+    * */
+    public static final String LIST = "list";
+    /*
+    * webview型
+    * */
+    public static final String WEBVIEW = "webview";
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpTpConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpTpConsts.java
new file mode 100644
index 0000000000..40270270cf
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpTpConsts.java
@@ -0,0 +1,52 @@
+package me.chanjar.weixin.cp.constant;
+
+import lombok.experimental.UtilityClass;
+
+public class WxCpTpConsts {
+
+
+  @UtilityClass
+  public static class InfoType {
+    /**
+     * 推送更新suite_ticket
+     */
+    public static final String SUITE_TICKET = "suite_ticket";
+
+    /**
+     * 从企业微信应用市场发起授权时,授权成功通知
+     */
+    public static final String CREATE_AUTH = "create_auth";
+
+    /**
+     * 从企业微信应用市场发起授权时,变更授权通知
+     */
+    public static final String CHANGE_AUTH = "change_auth";
+
+    /**
+     * 从企业微信应用市场发起授权时,取消授权通知
+     */
+    public static final String CANCEL_AUTH = "cancel_auth";
+
+    /**
+     * 通讯录变更通知
+     */
+    public static final String CHANGE_CONTACT = "change_contact";
+
+    /**
+     * 用户进行企业微信的注册,注册完成回调通知
+     */
+    public static final String REGISTER_CORP = "register_corp";
+
+    /**
+     * 异步任务回调通知
+     */
+    public static final String BATCH_JOB_RESULT = "batch_job_result";
+
+    /**
+     * 外部联系人变更通知
+     */
+    public static final String CHANGE_EXTERNAL_CONTACT = "change_external_contact";
+
+  }
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageHandler.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageHandler.java
index 22074d6e70..5d77444dd2 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageHandler.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageHandler.java
@@ -3,8 +3,8 @@
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.session.WxSessionManager;
 import me.chanjar.weixin.cp.api.WxCpService;
-import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
 
 import java.util.Map;
 
@@ -16,11 +16,14 @@
 public interface WxCpMessageHandler {
 
   /**
-   * @param wxMessage
+   * Handle wx cp xml out message.
+   *
+   * @param wxMessage      the wx message
    * @param context        上下文,如果handler或interceptor之间有信息要传递,可以用这个
-   * @param wxCpService
-   * @param sessionManager
-   * @return xml格式的消息,如果在异步规则里处理的话,可以返回null
+   * @param wxCpService    the wx cp service
+   * @param sessionManager the session manager
+   * @return xml格式的消息 ,如果在异步规则里处理的话,可以返回null
+   * @throws WxErrorException the wx error exception
    */
   WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage,
                            Map context,
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageInterceptor.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageInterceptor.java
index ab4c658e4f..45d3976b79 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageInterceptor.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageInterceptor.java
@@ -3,7 +3,7 @@
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.session.WxSessionManager;
 import me.chanjar.weixin.cp.api.WxCpService;
-import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
 
 import java.util.Map;
 
@@ -17,11 +17,12 @@ public interface WxCpMessageInterceptor {
   /**
    * 拦截微信消息
    *
-   * @param wxMessage
+   * @param wxMessage      the wx message
    * @param context        上下文,如果handler或interceptor之间有信息要传递,可以用这个
-   * @param wxCpService
-   * @param sessionManager
-   * @return true代表OK,false代表不OK
+   * @param wxCpService    the wx cp service
+   * @param sessionManager the session manager
+   * @return true代表OK ,false代表不OK
+   * @throws WxErrorException the wx error exception
    */
   boolean intercept(WxCpXmlMessage wxMessage,
                     Map context,
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageMatcher.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageMatcher.java
index 1bf36705b7..7fc7581171 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageMatcher.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageMatcher.java
@@ -1,14 +1,19 @@
 package me.chanjar.weixin.cp.message;
 
-import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
 
 /**
  * 消息匹配器,用在消息路由的时候
+ *
+ * @author Daniel Qian
  */
 public interface WxCpMessageMatcher {
 
   /**
    * 消息是否匹配某种模式
+   *
+   * @param message the message
+   * @return the boolean
    */
   boolean match(WxCpXmlMessage message);
 
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java
index b5424be03e..92de0c238a 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouter.java
@@ -1,15 +1,7 @@
 package me.chanjar.weixin.cp.message;
 
-import java.util.*;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-
-import org.apache.commons.lang3.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.api.WxErrorExceptionHandler;
 import me.chanjar.weixin.common.api.WxMessageDuplicateChecker;
 import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker;
@@ -18,8 +10,15 @@
 import me.chanjar.weixin.common.session.WxSessionManager;
 import me.chanjar.weixin.common.util.LogExceptionHandler;
 import me.chanjar.weixin.cp.api.WxCpService;
-import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
 
 /**
  * 
@@ -49,9 +48,9 @@
  *
  * @author Daniel Qian
  */
+@Slf4j
 public class WxCpMessageRouter {
   private static final int DEFAULT_THREAD_POOL_SIZE = 100;
-  private final Logger log = LoggerFactory.getLogger(WxCpMessageRouter.class);
   private final List rules = new ArrayList<>();
 
   private final WxCpService wxCpService;
@@ -69,7 +68,9 @@ public class WxCpMessageRouter {
    */
   public WxCpMessageRouter(WxCpService wxCpService) {
     this.wxCpService = wxCpService;
-    this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE);
+    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("WxCpMessageRouter-pool-%d").build();
+    this.executorService = new ThreadPoolExecutor(DEFAULT_THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_SIZE,
+      0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), namedThreadFactory);
     this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker();
     this.sessionManager = wxCpService.getSessionManager();
     this.exceptionHandler = new LogExceptionHandler();
@@ -156,37 +157,31 @@ public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage, final Map {
+            rule.service(wxMessage, context, WxCpMessageRouter.this.wxCpService, WxCpMessageRouter.this.sessionManager, WxCpMessageRouter.this.exceptionHandler);
           })
         );
       } else {
         res = rule.service(wxMessage, context, this.wxCpService, this.sessionManager, this.exceptionHandler);
         // 在同步操作结束,session访问结束
-        this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUserName());
+        log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUserName());
         sessionEndAccess(wxMessage);
       }
     }
 
     if (futures.size() > 0) {
-      this.executorService.submit(new Runnable() {
-        @Override
-        public void run() {
-          for (Future future : futures) {
-            try {
-              future.get();
-              WxCpMessageRouter.this.log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUserName());
-              // 异步操作结束,session访问结束
-              sessionEndAccess(wxMessage);
-            } catch (InterruptedException e) {
-              WxCpMessageRouter.this.log.error("Error happened when wait task finish", e);
-              Thread.currentThread().interrupt();
-            } catch (ExecutionException e) {
-              WxCpMessageRouter.this.log.error("Error happened when wait task finish", e);
-            }
+      this.executorService.submit(() -> {
+        for (Future future : futures) {
+          try {
+            future.get();
+            log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUserName());
+            // 异步操作结束,session访问结束
+            sessionEndAccess(wxMessage);
+          } catch (InterruptedException e) {
+            log.error("Error happened when wait task finish", e);
+            Thread.currentThread().interrupt();
+          } catch (ExecutionException e) {
+            log.error("Error happened when wait task finish", e);
           }
         }
       });
@@ -198,7 +193,7 @@ public void run() {
    * 处理微信消息.
    */
   public WxCpXmlOutMessage route(final WxCpXmlMessage wxMessage) {
-    return this.route(wxMessage, new HashMap(2));
+    return this.route(wxMessage, new HashMap<>(2));
   }
 
   private boolean isMsgDuplicated(WxCpXmlMessage wxMessage) {
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouterRule.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouterRule.java
index 8f3766a160..739bb0330f 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouterRule.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/message/WxCpMessageRouterRule.java
@@ -1,21 +1,24 @@
 package me.chanjar.weixin.cp.message;
 
+import lombok.Data;
 import me.chanjar.weixin.common.api.WxErrorExceptionHandler;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.session.WxSessionManager;
 import me.chanjar.weixin.cp.api.WxCpService;
-import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
 import org.apache.commons.lang3.StringUtils;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.regex.Pattern;
 
+/**
+ * The type Wx cp message router rule.
+ *
+ * @author Daniel Qian
+ */
+@Data
 public class WxCpMessageRouterRule {
-
   private final WxCpMessageRouter routerBuilder;
 
   private boolean async = true;
@@ -44,6 +47,11 @@ public class WxCpMessageRouterRule {
 
   private List interceptors = new ArrayList<>();
 
+  /**
+   * Instantiates a new Wx cp message router rule.
+   *
+   * @param routerBuilder the router builder
+   */
   protected WxCpMessageRouterRule(WxCpMessageRouter routerBuilder) {
     this.routerBuilder = routerBuilder;
   }
@@ -51,7 +59,8 @@ protected WxCpMessageRouterRule(WxCpMessageRouter routerBuilder) {
   /**
    * 设置是否异步执行,默认是true
    *
-   * @param async
+   * @param async the async
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule async(boolean async) {
     this.async = async;
@@ -61,7 +70,8 @@ public WxCpMessageRouterRule async(boolean async) {
   /**
    * 如果agentId匹配
    *
-   * @param agentId
+   * @param agentId the agent id
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule agentId(Integer agentId) {
     this.agentId = agentId;
@@ -71,7 +81,8 @@ public WxCpMessageRouterRule agentId(Integer agentId) {
   /**
    * 如果msgType等于某值
    *
-   * @param msgType
+   * @param msgType the msg type
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule msgType(String msgType) {
     this.msgType = msgType;
@@ -81,7 +92,8 @@ public WxCpMessageRouterRule msgType(String msgType) {
   /**
    * 如果event等于某值
    *
-   * @param event
+   * @param event the event
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule event(String event) {
     this.event = event;
@@ -91,7 +103,8 @@ public WxCpMessageRouterRule event(String event) {
   /**
    * 如果eventKey等于某值
    *
-   * @param eventKey
+   * @param eventKey the event key
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule eventKey(String eventKey) {
     this.eventKey = eventKey;
@@ -100,6 +113,9 @@ public WxCpMessageRouterRule eventKey(String eventKey) {
 
   /**
    * 如果eventKey匹配该正则表达式
+   *
+   * @param regex the regex
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule eventKeyRegex(String regex) {
     this.eventKeyRegex = regex;
@@ -109,7 +125,8 @@ public WxCpMessageRouterRule eventKeyRegex(String regex) {
   /**
    * 如果content等于某值
    *
-   * @param content
+   * @param content the content
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule content(String content) {
     this.content = content;
@@ -119,7 +136,8 @@ public WxCpMessageRouterRule content(String content) {
   /**
    * 如果content匹配该正则表达式
    *
-   * @param regex
+   * @param regex the regex
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule rContent(String regex) {
     this.rContent = regex;
@@ -129,7 +147,8 @@ public WxCpMessageRouterRule rContent(String regex) {
   /**
    * 如果fromUser等于某值
    *
-   * @param fromUser
+   * @param fromUser the from user
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule fromUser(String fromUser) {
     this.fromUser = fromUser;
@@ -139,7 +158,8 @@ public WxCpMessageRouterRule fromUser(String fromUser) {
   /**
    * 如果消息匹配某个matcher,用在用户需要自定义更复杂的匹配规则的时候
    *
-   * @param matcher
+   * @param matcher the matcher
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule matcher(WxCpMessageMatcher matcher) {
     this.matcher = matcher;
@@ -149,7 +169,8 @@ public WxCpMessageRouterRule matcher(WxCpMessageMatcher matcher) {
   /**
    * 设置微信消息拦截器
    *
-   * @param interceptor
+   * @param interceptor the interceptor
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule interceptor(WxCpMessageInterceptor interceptor) {
     return interceptor(interceptor, (WxCpMessageInterceptor[]) null);
@@ -158,15 +179,14 @@ public WxCpMessageRouterRule interceptor(WxCpMessageInterceptor interceptor) {
   /**
    * 设置微信消息拦截器
    *
-   * @param interceptor
-   * @param otherInterceptors
+   * @param interceptor       the interceptor
+   * @param otherInterceptors the other interceptors
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule interceptor(WxCpMessageInterceptor interceptor, WxCpMessageInterceptor... otherInterceptors) {
     this.interceptors.add(interceptor);
     if (otherInterceptors != null && otherInterceptors.length > 0) {
-      for (WxCpMessageInterceptor i : otherInterceptors) {
-        this.interceptors.add(i);
-      }
+      Collections.addAll(this.interceptors, otherInterceptors);
     }
     return this;
   }
@@ -174,7 +194,8 @@ public WxCpMessageRouterRule interceptor(WxCpMessageInterceptor interceptor, WxC
   /**
    * 设置微信消息处理器
    *
-   * @param handler
+   * @param handler the handler
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule handler(WxCpMessageHandler handler) {
     return handler(handler, (WxCpMessageHandler[]) null);
@@ -183,21 +204,22 @@ public WxCpMessageRouterRule handler(WxCpMessageHandler handler) {
   /**
    * 设置微信消息处理器
    *
-   * @param handler
-   * @param otherHandlers
+   * @param handler       the handler
+   * @param otherHandlers the other handlers
+   * @return the wx cp message router rule
    */
   public WxCpMessageRouterRule handler(WxCpMessageHandler handler, WxCpMessageHandler... otherHandlers) {
     this.handlers.add(handler);
     if (otherHandlers != null && otherHandlers.length > 0) {
-      for (WxCpMessageHandler i : otherHandlers) {
-        this.handlers.add(i);
-      }
+      Collections.addAll(this.handlers, otherHandlers);
     }
     return this;
   }
 
   /**
    * 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则
+   *
+   * @return the wx cp message router
    */
   public WxCpMessageRouter end() {
     this.routerBuilder.getRules().add(this);
@@ -206,12 +228,20 @@ public WxCpMessageRouter end() {
 
   /**
    * 规则结束,但是消息还会进入其他规则
+   *
+   * @return the wx cp message router
    */
   public WxCpMessageRouter next() {
     this.reEnter = true;
     return end();
   }
 
+  /**
+   * Test boolean.
+   *
+   * @param wxMessage the wx message
+   * @return the boolean
+   */
   protected boolean test(WxCpXmlMessage wxMessage) {
     return
       (this.fromUser == null || this.fromUser.equals(wxMessage.getFromUserName()))
@@ -237,7 +267,11 @@ protected boolean test(WxCpXmlMessage wxMessage) {
   /**
    * 处理微信推送过来的消息
    *
-   * @param wxMessage
+   * @param wxMessage        the wx message
+   * @param context          the context
+   * @param wxCpService      the wx cp service
+   * @param sessionManager   the session manager
+   * @param exceptionHandler the exception handler
    * @return true 代表继续执行别的router,false 代表停止执行别的router
    */
   protected WxCpXmlOutMessage service(WxCpXmlMessage wxMessage,
@@ -274,60 +308,5 @@ protected WxCpXmlOutMessage service(WxCpXmlMessage wxMessage,
 
   }
 
-  public void setFromUser(String fromUser) {
-    this.fromUser = fromUser;
-  }
-
-  public void setMsgType(String msgType) {
-    this.msgType = msgType;
-  }
-
-  public void setEvent(String event) {
-    this.event = event;
-  }
-
-  public void setEventKey(String eventKey) {
-    this.eventKey = eventKey;
-  }
-
-  public void setContent(String content) {
-    this.content = content;
-  }
-
-  public void setrContent(String rContent) {
-    this.rContent = rContent;
-  }
-
-  public void setMatcher(WxCpMessageMatcher matcher) {
-    this.matcher = matcher;
-  }
-
-  public void setAgentId(Integer agentId) {
-    this.agentId = agentId;
-  }
-
-  public void setHandlers(List handlers) {
-    this.handlers = handlers;
-  }
-
-  public void setInterceptors(List interceptors) {
-    this.interceptors = interceptors;
-  }
-
-  public boolean isAsync() {
-    return this.async;
-  }
-
-  public void setAsync(boolean async) {
-    this.async = async;
-  }
-
-  public boolean isReEnter() {
-    return this.reEnter;
-  }
-
-  public void setReEnter(boolean reEnter) {
-    this.reEnter = reEnter;
-  }
 
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageHandler.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageHandler.java
new file mode 100644
index 0000000000..639a743350
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageHandler.java
@@ -0,0 +1,33 @@
+package me.chanjar.weixin.cp.tp.message;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
+import me.chanjar.weixin.cp.tp.service.WxCpTpService;
+
+import java.util.Map;
+
+/**
+ * 处理微信推送消息的处理器接口
+ *
+ * @author Daniel Qian
+ */
+public interface WxCpTpMessageHandler {
+
+  /**
+   * Handle wx cp xml out message.
+   *
+   * @param wxMessage      the wx message
+   * @param context        上下文,如果handler或interceptor之间有信息要传递,可以用这个
+   * @param wxCpService    the wx cp service
+   * @param sessionManager the session manager
+   * @return xml格式的消息 ,如果在异步规则里处理的话,可以返回null
+   * @throws WxErrorException the wx error exception
+   */
+  WxCpXmlOutMessage handle(WxCpTpXmlMessage wxMessage,
+                           Map context,
+                           WxCpTpService wxCpService,
+                           WxSessionManager sessionManager) throws WxErrorException;
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageInterceptor.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageInterceptor.java
new file mode 100644
index 0000000000..feac10dbb6
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageInterceptor.java
@@ -0,0 +1,32 @@
+package me.chanjar.weixin.cp.tp.message;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
+import me.chanjar.weixin.cp.tp.service.WxCpTpService;
+
+import java.util.Map;
+
+/**
+ * 微信消息拦截器,可以用来做验证
+ *
+ * @author Daniel Qian
+ */
+public interface WxCpTpMessageInterceptor {
+
+  /**
+   * 拦截微信消息
+   *
+   * @param wxMessage      the wx message
+   * @param context        上下文,如果handler或interceptor之间有信息要传递,可以用这个
+   * @param wxCpService    the wx cp service
+   * @param sessionManager the session manager
+   * @return true代表OK ,false代表不OK
+   * @throws WxErrorException the wx error exception
+   */
+  boolean intercept(WxCpTpXmlMessage wxMessage,
+                    Map context,
+                    WxCpTpService wxCpService,
+                    WxSessionManager sessionManager) throws WxErrorException;
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageMatcher.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageMatcher.java
new file mode 100644
index 0000000000..57e35f1946
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageMatcher.java
@@ -0,0 +1,21 @@
+package me.chanjar.weixin.cp.tp.message;
+
+import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
+
+/**
+ * 消息匹配器,用在消息路由的时候
+ *
+ * @author Daniel Qian
+ */
+public interface WxCpTpMessageMatcher {
+
+  /**
+   * 消息是否匹配某种模式
+   *
+   * @param message the message
+   * @return the boolean
+   */
+  boolean match(WxCpTpXmlMessage message);
+
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageRouter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageRouter.java
new file mode 100644
index 0000000000..5b045082a8
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageRouter.java
@@ -0,0 +1,241 @@
+package me.chanjar.weixin.cp.tp.message;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.api.WxErrorExceptionHandler;
+import me.chanjar.weixin.common.api.WxMessageDuplicateChecker;
+import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker;
+import me.chanjar.weixin.common.session.InternalSession;
+import me.chanjar.weixin.common.session.InternalSessionManager;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.common.util.LogExceptionHandler;
+import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
+import me.chanjar.weixin.cp.tp.service.WxCpTpService;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
+
+/**
+ * 
+ * 微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理
+ * 和WxCpMessageRouter的rule相比,多了infoType和changeType维度的匹配
+ *
+ * 说明:
+ * 1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理
+ * 2. 默认情况下消息只会被处理一次,除非使用 {@link WxCpTpMessageRouterRule#next()}
+ * 3. 规则的结束必须用{@link WxCpTpMessageRouterRule#end()}或者{@link WxCpTpMessageRouterRule#next()},否则不会生效
+ *
+ * 使用方法:
+ * WxCpTpMessageRouter router = new WxCpTpMessageRouter();
+ * router
+ *   .rule()
+ *       .msgType("MSG_TYPE").event("EVENT").eventKey("EVENT_KEY").content("CONTENT")
+ *       .interceptor(interceptor, ...).handler(handler, ...)
+ *   .end()
+ *   .rule()
+ *       .infoType("INFO_TYPE").changeType("CHANGE_TYPE")
+ *       // 另外一个匹配规则
+ *   .end()
+ * ;
+ *
+ * // 将WxXmlMessage交给消息路由器
+ * router.route(message);
+ *
+ * 
+ * + * @author Daniel Qian + */ +@Slf4j +public class WxCpTpMessageRouter { + private static final int DEFAULT_THREAD_POOL_SIZE = 100; + private final List rules = new ArrayList<>(); + + private final WxCpTpService wxCpTpService; + + private ExecutorService executorService; + + private WxMessageDuplicateChecker messageDuplicateChecker; + + private WxSessionManager sessionManager; + + private WxErrorExceptionHandler exceptionHandler; + + /** + * 构造方法. + */ + public WxCpTpMessageRouter(WxCpTpService wxCpTpService) { + this.wxCpTpService = wxCpTpService; + ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("WxCpTpMessageRouter-pool-%d").build(); + this.executorService = new ThreadPoolExecutor(DEFAULT_THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_SIZE, + 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), namedThreadFactory); + this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); + this.sessionManager = wxCpTpService.getSessionManager(); + this.exceptionHandler = new LogExceptionHandler(); + } + + /** + *
+   * 设置自定义的 {@link ExecutorService}
+   * 如果不调用该方法,默认使用 Executors.newFixedThreadPool(100)
+   * 
+ */ + public void setExecutorService(ExecutorService executorService) { + this.executorService = executorService; + } + + /** + *
+   * 设置自定义的 {@link WxMessageDuplicateChecker}
+   * 如果不调用该方法,默认使用 {@link WxMessageInMemoryDuplicateChecker}
+   * 
+ */ + public void setMessageDuplicateChecker(WxMessageDuplicateChecker messageDuplicateChecker) { + this.messageDuplicateChecker = messageDuplicateChecker; + } + + /** + *
+   * 设置自定义的{@link WxSessionManager}
+   * 如果不调用该方法,默认使用 {@link me.chanjar.weixin.common.session.StandardSessionManager}
+   * 
+ */ + public void setSessionManager(WxSessionManager sessionManager) { + this.sessionManager = sessionManager; + } + + /** + *
+   * 设置自定义的{@link WxErrorExceptionHandler}
+   * 如果不调用该方法,默认使用 {@link LogExceptionHandler}
+   * 
+ */ + public void setExceptionHandler(WxErrorExceptionHandler exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + List getRules() { + return this.rules; + } + + /** + * 开始一个新的Route规则. + */ + public WxCpTpMessageRouterRule rule() { + return new WxCpTpMessageRouterRule(this); + } + + /** + * 处理微信消息. + */ + public WxCpXmlOutMessage route(final WxCpTpXmlMessage wxMessage, final Map context) { + if (isMsgDuplicated(wxMessage)) { + // 如果是重复消息,那么就不做处理 + return null; + } + + final List matchRules = new ArrayList<>(); + // 收集匹配的规则 + for (final WxCpTpMessageRouterRule rule : this.rules) { + if (rule.test(wxMessage)) { + matchRules.add(rule); + if (!rule.isReEnter()) { + break; + } + } + } + + if (matchRules.size() == 0) { + return null; + } + + WxCpXmlOutMessage res = null; + final List futures = new ArrayList<>(); + for (final WxCpTpMessageRouterRule rule : matchRules) { + // 返回最后一个非异步的rule的执行结果 + if (rule.isAsync()) { + futures.add( + this.executorService.submit(() -> { + rule.service(wxMessage, context, WxCpTpMessageRouter.this.wxCpTpService, WxCpTpMessageRouter.this.sessionManager, WxCpTpMessageRouter.this.exceptionHandler); + }) + ); + } else { + res = rule.service(wxMessage, context, this.wxCpTpService, this.sessionManager, this.exceptionHandler); + // 在同步操作结束,session访问结束 + log.debug("End session access: async=false, sessionId={}", wxMessage.getSuiteId()); + sessionEndAccess(wxMessage); + } + } + + if (futures.size() > 0) { + this.executorService.submit(() -> { + for (Future future : futures) { + try { + future.get(); + log.debug("End session access: async=true, sessionId={}", wxMessage.getSuiteId()); + // 异步操作结束,session访问结束 + sessionEndAccess(wxMessage); + } catch (InterruptedException e) { + log.error("Error happened when wait task finish", e); + Thread.currentThread().interrupt(); + } catch (ExecutionException e) { + log.error("Error happened when wait task finish", e); + } + } + }); + } + return res; + } + + /** + * 处理微信消息. + */ + public WxCpXmlOutMessage route(final WxCpTpXmlMessage wxMessage) { + return this.route(wxMessage, new HashMap<>(2)); + } + + private boolean isMsgDuplicated(WxCpTpXmlMessage wxMessage) { + StringBuilder messageId = new StringBuilder(); + if (wxMessage.getInfoType() != null) { + messageId.append(wxMessage.getInfoType()) + .append("-").append(StringUtils.trimToEmpty(wxMessage.getSuiteId())) + .append("-").append(wxMessage.getTimeStamp()) + .append("-").append(StringUtils.trimToEmpty(wxMessage.getAuthCorpId())) + .append("-").append(StringUtils.trimToEmpty(wxMessage.getUserID())) + .append("-").append(StringUtils.trimToEmpty(wxMessage.getChangeType())) + .append("-").append(StringUtils.trimToEmpty(wxMessage.getServiceCorpId())); + } + + if (wxMessage.getMsgType() != null) { + if (wxMessage.getMsgId() != null) { + messageId.append(wxMessage.getMsgId()) + .append("-").append(wxMessage.getCreateTime()) + .append("-").append(wxMessage.getFromUserName()); + } + else { + messageId.append(wxMessage.getMsgType()) + .append("-").append(wxMessage.getCreateTime()) + .append("-").append(wxMessage.getFromUserName()) + .append("-").append(StringUtils.trimToEmpty(wxMessage.getEvent())) + .append("-").append(StringUtils.trimToEmpty(wxMessage.getEventKey())); + } + } + + return this.messageDuplicateChecker.isDuplicate(messageId.toString()); + } + + /** + * 对session的访问结束. + */ + private void sessionEndAccess(WxCpTpXmlMessage wxMessage) { + InternalSession session = ((InternalSessionManager) this.sessionManager).findSession(wxMessage.getSuiteId()); + if (session != null) { + session.endAccess(); + } + + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageRouterRule.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageRouterRule.java new file mode 100644 index 0000000000..1b7d7fbf77 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/message/WxCpTpMessageRouterRule.java @@ -0,0 +1,258 @@ +package me.chanjar.weixin.cp.tp.message; + +import lombok.Data; +import me.chanjar.weixin.common.api.WxErrorExceptionHandler; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; +import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; +import org.apache.commons.lang3.StringUtils; + +import java.util.*; +import java.util.regex.Pattern; + +/** + * The type Wx cp message router rule. + * + * @author Daniel Qian + */ +@Data +public class WxCpTpMessageRouterRule { + private final WxCpTpMessageRouter routerBuilder; + + private boolean async = true; + + private String fromUser; + + private String msgType; + + private String event; + + private String eventKey; + + private String eventKeyRegex; + + private String content; + + private String rContent; + + private WxCpTpMessageMatcher matcher; + + private boolean reEnter = false; + + private Integer agentId; + + private String infoType; + + private String changeType; + + private List handlers = new ArrayList<>(); + + private List interceptors = new ArrayList<>(); + private String suiteId; + private String authCode; + private String suiteTicket; + + /** + * Instantiates a new Wx cp message router rule. + * + * @param routerBuilder the router builder + */ + protected WxCpTpMessageRouterRule(WxCpTpMessageRouter routerBuilder) { + this.routerBuilder = routerBuilder; + } + + /** + * 设置是否异步执行,默认是true + * + * @param async the async + * @return the wx cp message router rule + */ + public WxCpTpMessageRouterRule async(boolean async) { + this.async = async; + return this; + } + + /** + * 匹配 Message infoType + * + * @param infoType info + */ + public WxCpTpMessageRouterRule infoType(String infoType) { + this.infoType = infoType; + return this; + } + + /** + * 如果changeType等于这个type,符合rule的条件之一 + * @param changeType + * @return + */ + public WxCpTpMessageRouterRule changeType(String changeType) { + this.changeType = changeType; + return this; + } + + + /** + * 如果消息匹配某个matcher,用在用户需要自定义更复杂的匹配规则的时候 + * + * @param matcher the matcher + * @return the wx cp message router rule + */ + public WxCpTpMessageRouterRule matcher(WxCpTpMessageMatcher matcher) { + this.matcher = matcher; + return this; + } + + /** + * 设置微信消息拦截器 + * + * @param interceptor the interceptor + * @return the wx cp message router rule + */ + public WxCpTpMessageRouterRule interceptor(WxCpTpMessageInterceptor interceptor) { + return interceptor(interceptor, (WxCpTpMessageInterceptor[]) null); + } + + /** + * 设置微信消息拦截器 + * + * @param interceptor the interceptor + * @param otherInterceptors the other interceptors + * @return the wx cp message router rule + */ + public WxCpTpMessageRouterRule interceptor(WxCpTpMessageInterceptor interceptor, WxCpTpMessageInterceptor... otherInterceptors) { + this.interceptors.add(interceptor); + if (otherInterceptors != null && otherInterceptors.length > 0) { + Collections.addAll(this.interceptors, otherInterceptors); + } + return this; + } + + /** + * 设置微信消息处理器 + * + * @param handler the handler + * @return the wx cp message router rule + */ + public WxCpTpMessageRouterRule handler(WxCpTpMessageHandler handler) { + return handler(handler, (WxCpTpMessageHandler[]) null); + } + + /** + * 设置微信消息处理器 + * + * @param handler the handler + * @param otherHandlers the other handlers + * @return the wx cp message router rule + */ + public WxCpTpMessageRouterRule handler(WxCpTpMessageHandler handler, WxCpTpMessageHandler... otherHandlers) { + this.handlers.add(handler); + if (otherHandlers != null && otherHandlers.length > 0) { + Collections.addAll(this.handlers, otherHandlers); + } + return this; + } + + /** + * 规则结束,代表如果一个消息匹配该规则,那么它将不再会进入其他规则 + * + * @return the wx cp message router + */ + public WxCpTpMessageRouter end() { + this.routerBuilder.getRules().add(this); + return this.routerBuilder; + } + + /** + * 规则结束,但是消息还会进入其他规则 + * + * @return the wx cp message router + */ + public WxCpTpMessageRouter next() { + this.reEnter = true; + return end(); + } + + /** + * Test boolean. + * + * @param wxMessage the wx message + * @return the boolean + */ + protected boolean test(WxCpTpXmlMessage wxMessage) { + return + (this.suiteId == null || this.suiteId.equals(wxMessage.getSuiteId())) + && + (this.fromUser == null || this.fromUser.equals(wxMessage.getFromUserName())) + && + (this.agentId == null || this.agentId.equals(wxMessage.getAgentID())) + && + (this.msgType == null || this.msgType.equalsIgnoreCase(wxMessage.getMsgType())) + && + (this.infoType == null || this.infoType.equals(wxMessage.getInfoType())) + && + (this.suiteTicket == null || this.suiteTicket.equalsIgnoreCase(wxMessage.getSuiteTicket())) + && + (this.eventKeyRegex == null || Pattern.matches(this.eventKeyRegex, StringUtils.trimToEmpty(wxMessage.getEventKey()))) + && + (this.content == null || this.content.equals(StringUtils.trimToNull(wxMessage.getContent()))) + && + (this.rContent == null || Pattern.matches(this.rContent, StringUtils.trimToEmpty(wxMessage.getContent()))) + && + (this.infoType == null || this.infoType.equals(wxMessage.getInfoType())) + && + (this.changeType == null || this.changeType.equals(wxMessage.getChangeType())) + && + (this.matcher == null || this.matcher.match(wxMessage)) + && + (this.authCode == null || this.authCode.equalsIgnoreCase(wxMessage.getAuthCode())); + } + + /** + * 处理微信推送过来的消息 + * + * @param wxMessage the wx message + * @param context the context + * @param wxCpService the wx cp service + * @param sessionManager the session manager + * @param exceptionHandler the exception handler + * @return true 代表继续执行别的router,false 代表停止执行别的router + */ + protected WxCpXmlOutMessage service(WxCpTpXmlMessage wxMessage, + Map context, + WxCpTpService wxCpService, + WxSessionManager sessionManager, + WxErrorExceptionHandler exceptionHandler) { + if (context == null) { + context = new HashMap<>(2); + } + + try { + // 如果拦截器不通过 + for (WxCpTpMessageInterceptor interceptor : this.interceptors) { + if (!interceptor.intercept(wxMessage, context, wxCpService, sessionManager)) { + return null; + } + } + + // 交给handler处理 + WxCpXmlOutMessage res = null; + for (WxCpTpMessageHandler handler : this.handlers) { + // 返回最后handler的结果 + res = handler.handle(wxMessage, context, wxCpService, sessionManager); + } + return res; + + } catch (WxErrorException e) { + exceptionHandler.handle(e); + } + + return null; + + } + + +} 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/tp/service/WxCpTpService.java similarity index 56% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpTpService.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java index ad2f403af6..1047368832 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/tp/service/WxCpTpService.java @@ -1,18 +1,16 @@ -package me.chanjar.weixin.cp.api; +package me.chanjar.weixin.cp.tp.service; import me.chanjar.weixin.common.bean.WxAccessToken; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.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.bean.*; import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; /** - * 微信第三方应用API的Service. + * 企业微信第三方应用API的Service. * * @author zhenjun cai */ @@ -27,13 +25,16 @@ public interface WxCpTpService { * @param timestamp 时间戳 * @param nonce 随机数 * @param data 微信传输过来的数据,有可能是echoStr,有可能是xml消息 + * @return the boolean */ boolean checkSignature(String msgSignature, String timestamp, String nonce, String data); /** * 获取suite_access_token, 不强制刷新suite_access_token * - * @see #getSuiteAccessToken(boolean) + * @return the suite access token + * @throws WxErrorException the wx error exception + * @see #getSuiteAccessToken(boolean) #getSuiteAccessToken(boolean) */ String getSuiteAccessToken() throws WxErrorException; @@ -47,13 +48,17 @@ public interface WxCpTpService { *
* * @param forceRefresh 强制刷新 + * @return the suite access token + * @throws WxErrorException the wx error exception */ String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException; /** * 获得suite_ticket,不强制刷新suite_ticket * - * @see #getSuiteTicket(boolean) + * @return the suite ticket + * @throws WxErrorException the wx error exception + * @see #getSuiteTicket(boolean) #getSuiteTicket(boolean) */ String getSuiteTicket() throws WxErrorException; @@ -65,14 +70,41 @@ public interface WxCpTpService { * 详情请见:https://work.weixin.qq.com/api/doc#90001/90143/90628 *
* + * @Deprecated 由于无法主动刷新,所以这个接口实际已经没有意义,需要在接收企业微信的主动推送后,保存这个ticket + * @see #setSuiteTicket(String) + * * @param forceRefresh 强制刷新 + * @return the suite ticket + * @throws WxErrorException the wx error exception */ + @Deprecated String getSuiteTicket(boolean forceRefresh) throws WxErrorException; + /** + *
+   * 保存企业微信定时推送的suite_ticket,(每10分钟)
+   * 详情请见:https://work.weixin.qq.com/api/doc#90001/90143/90628
+   * 
+ * + * @param suiteTicket + * @throws WxErrorException + */ + void setSuiteTicket(String suiteTicket) throws WxErrorException; + + /** + * 获取应用的 jsapi ticket + * + * @param authCorpId 授权企业的cropId + * @return jsapi ticket + */ + String getSuiteJsApiTicket(String authCorpId) throws WxErrorException; + /** * 小程序登录凭证校验 * * @param jsCode 登录时获取的 code + * @return the wx cp ma js code 2 session result + * @throws WxErrorException the wx error exception */ WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException; @@ -81,6 +113,8 @@ public interface WxCpTpService { * * @param authCorpid 授权方corpid * @param permanentCode 永久授权码,通过get_permanent_code获取 + * @return the corp token + * @throws WxErrorException the wx error exception */ WxAccessToken getCorpToken(String authCorpid, String permanentCode) throws WxErrorException; @@ -88,7 +122,8 @@ public interface WxCpTpService { * 获取企业永久授权码 . * * @param authCode . - * @return . + * @return . permanent code + * @throws WxErrorException the wx error exception */ @Deprecated WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException; @@ -99,13 +134,11 @@ public interface WxCpTpService { * 原来的方法实现不全 *
* - * @param authCode - * @return - * + * @param authCode the auth code + * @return permanent code info + * @throws WxErrorException the wx error exception * @author yuan - * @since 2020-03-18 - * - * @throws WxErrorException + * @since 2020 -03-18 */ WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException; @@ -113,28 +146,53 @@ public interface WxCpTpService { *
    *   获取预授权链接
    * 
+ * * @param redirectUri 授权完成后的回调网址 - * @param state a-zA-Z0-9的参数值(不超过128个字节),用于第三方自行校验session,防止跨域攻击 - * @return - * @throws WxErrorException + * @param state a-zA-Z0-9的参数值(不超过128个字节),用于第三方自行校验session,防止跨域攻击 + * @return pre auth url + * @throws WxErrorException the wx error exception + */ + String getPreAuthUrl(String redirectUri, String state) throws WxErrorException; + + /** + *
+   *   获取预授权链接,测试环境下使用
+   *   @Link https://work.weixin.qq.com/api/doc/90001/90143/90602
+   * 
+ * + * @param redirectUri 授权完成后的回调网址 + * @param state a-zA-Z0-9的参数值(不超过128个字节),用于第三方自行校验session,防止跨域攻击 + * @param authType 授权类型:0 正式授权, 1 测试授权。 + * @return pre auth url + * @throws WxErrorException the wx error exception */ - String getPreAuthUrl(String redirectUri,String state) throws WxErrorException; + String getPreAuthUrl(String redirectUri, String state, int authType) throws WxErrorException; /** * 获取企业的授权信息 * - * @param authCorpId 授权企业的corpId + * @param authCorpId 授权企业的corpId * @param permanentCode 授权企业的永久授权码 - * @return - * @throws WxErrorException + * @return auth info + * @throws WxErrorException the wx error exception */ - WxCpTpAuthInfo getAuthInfo(String authCorpId,String permanentCode) throws WxErrorException; + WxCpTpAuthInfo getAuthInfo(String authCorpId, String permanentCode) throws WxErrorException; + + /** + * 获取授权企业的 jsapi ticket + * + * @param authCorpId 授权企业的cropId + * @return jsapi ticket + */ + String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException; /** * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. * * @param url 接口地址 * @param queryParam 请求参数 + * @return the string + * @throws WxErrorException the wx error exception */ String get(String url, String queryParam) throws WxErrorException; @@ -143,6 +201,8 @@ public interface WxCpTpService { * * @param url 接口地址 * @param postData 请求body字符串 + * @return the string + * @throws WxErrorException the wx error exception */ String post(String url, String postData) throws WxErrorException; @@ -153,11 +213,13 @@ public interface WxCpTpService { * 可以参考,{@link MediaUploadRequestExecutor}的实现方法 *
* + * @param 请求值类型 + * @param 返回值类型 * @param executor 执行器 * @param uri 请求地址 * @param data 参数 - * @param 请求值类型 - * @param 返回值类型 + * @return the t + * @throws WxErrorException the wx error exception */ T execute(RequestExecutor executor, String uri, E data) throws WxErrorException; @@ -189,8 +251,10 @@ public interface WxCpTpService { /** * 获取WxMpConfigStorage 对象. * - * @return WxMpConfigStorage + * @Deprecated storage应该在service内部使用,提供这个接口,容易破坏这个封装 + * @return WxMpConfigStorage wx cp tp config storage */ + @Deprecated WxCpTpConfigStorage getWxCpTpConfigStorage(); /** @@ -202,7 +266,35 @@ public interface WxCpTpService { /** * http请求对象. + * + * @return the request http */ RequestHttp getRequestHttp(); + /** + * 获取WxSessionManager 对象 + * + * @return WxSessionManager session manager + */ + WxSessionManager getSessionManager(); + + /** + *
+   * 获取访问用户身份
+   * 
+ * + * @param code + * @return + */ + WxCpTpUserInfo getUserInfo3rd(String code) throws WxErrorException; + + /** + *
+   * 获取访问用户敏感信息
+   * 
+ * + * @param userTicket + * @return + */ + WxCpTpUserDetail getUserDetail3rd(String userTicket) throws WxErrorException; } 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/tp/service/impl/BaseWxCpTpServiceImpl.java similarity index 65% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImpl.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java index 191bfec0d8..5726204fbd 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/tp/service/impl/BaseWxCpTpServiceImpl.java @@ -1,14 +1,18 @@ -package me.chanjar.weixin.cp.api.impl; +package me.chanjar.weixin.cp.tp.service.impl; import com.google.common.base.Joiner; +import com.google.gson.Gson; import com.google.gson.JsonObject; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.bean.WxAccessToken; +import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxCpErrorMsgEnum; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; +import me.chanjar.weixin.common.session.StandardSessionManager; +import me.chanjar.weixin.common.session.WxSessionManager; import me.chanjar.weixin.common.util.DataUtils; import me.chanjar.weixin.common.util.crypto.SHA1; import me.chanjar.weixin.common.util.http.RequestExecutor; @@ -16,9 +20,9 @@ import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; import me.chanjar.weixin.common.util.json.GsonParser; -import me.chanjar.weixin.cp.api.WxCpTpService; import me.chanjar.weixin.cp.bean.*; import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; import org.apache.commons.lang3.StringUtils; import java.io.File; @@ -42,13 +46,21 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ */ protected final Object globalSuiteAccessTokenRefreshLock = new Object(); + /** - * 全局的是否正在刷新jsapi_ticket的锁. + * 全局刷新suite ticket的锁 */ protected final Object globalSuiteTicketRefreshLock = new Object(); + /** + * 全局的是否正在刷新jsapi_ticket的锁. + */ + protected final Object globalJsApiTicketRefreshLock = new Object(); + protected WxCpTpConfigStorage configStorage; + private WxSessionManager sessionManager = new StandardSessionManager(); + /** * 临时文件目录. */ @@ -74,7 +86,12 @@ public String getSuiteAccessToken() throws WxErrorException { @Override public String getSuiteTicket() throws WxErrorException { - return getSuiteTicket(false); + if (this.configStorage.isSuiteTicketExpired()) { + // 本地suite ticket 不存在或者过期 + WxError wxError = WxError.fromJson("{\"errcode\":40085, \"errmsg\":\"invaild suite ticket\"}", WxType.CP); + throw new WxErrorException(wxError); + } + return this.configStorage.getSuiteTicket(); } @Override @@ -83,15 +100,62 @@ public String getSuiteTicket(boolean forceRefresh) throws WxErrorException { // if (forceRefresh) { // this.configStorage.expireSuiteTicket(); // } + return getSuiteTicket(); + } - if (this.configStorage.isSuiteTicketExpired()) { - // 本地suite ticket 不存在或者过期 - WxError wxError = WxError.fromJson("{\"errcode\":40085, \"errmsg\":\"invaild suite ticket\"}", WxType.CP); - throw new WxErrorException(wxError); + @Override + public void setSuiteTicket(String suiteTicket) throws WxErrorException { + synchronized (globalSuiteTicketRefreshLock) { + this.configStorage.updateSuiteTicket(suiteTicket, 10 * 60); } - return this.configStorage.getSuiteTicket(); } + @Override + public String getSuiteJsApiTicket(String authCorpId) throws WxErrorException { + if (this.configStorage.isSuiteAccessTokenExpired()) { + + String resp = get(configStorage.getApiUrl(GET_SUITE_JSAPI_TICKET), + "type=agent_config&access_token=" + this.configStorage.getAccessToken(authCorpId)); + + JsonObject jsonObject = GsonParser.parse(resp); + if (jsonObject.get("errcode").getAsInt() == 0) { + String jsApiTicket = jsonObject.get("ticket").getAsString(); + int expiredInSeconds = jsonObject.get("expires_in").getAsInt(); + synchronized (globalJsApiTicketRefreshLock) { + configStorage.updateAuthSuiteJsApiTicket(authCorpId, jsApiTicket, expiredInSeconds); + } + } + else { + throw new WxErrorException(WxError.fromJson(resp)); + } + } + + return configStorage.getSuiteAccessToken(); + } + + @Override + public String getAuthCorpJsApiTicket(String authCorpId) throws WxErrorException { + if (this.configStorage.isSuiteAccessTokenExpired()) { + + String resp = get(configStorage.getApiUrl(GET_AUTH_CORP_JSAPI_TICKET), + "access_token=" + this.configStorage.getAccessToken(authCorpId)); + + JsonObject jsonObject = GsonParser.parse(resp); + if (jsonObject.get("errcode").getAsInt() == 0) { + String jsApiTicket = jsonObject.get("ticket").getAsString(); + int expiredInSeconds = jsonObject.get("expires_in").getAsInt(); + + synchronized (globalJsApiTicketRefreshLock) { + configStorage.updateAuthCorpJsApiTicket(authCorpId, jsApiTicket, expiredInSeconds); + } + } + else { + throw new WxErrorException(WxError.fromJson(resp)); + } + } + + return configStorage.getSuiteAccessToken(); + } @Override public WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException { @@ -127,7 +191,7 @@ public WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException { } @Override - public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException{ + 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()); @@ -136,18 +200,43 @@ public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxEr @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; + 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 + @SneakyThrows + public String getPreAuthUrl(String redirectUri, String state, int authType) throws WxErrorException { + String result = get(configStorage.getApiUrl(GET_PREAUTH_CODE), null); + WxCpTpPreauthCode preAuthCode = WxCpTpPreauthCode.fromJson(result); + String setSessionUrl = "https://qyapi.weixin.qq.com/cgi-bin/service/set_session_info"; + + Map sessionInfo = new HashMap<>(1); + sessionInfo.put("auth_type", authType); + Map param = new HashMap<>(2); + param.put("pre_auth_code", preAuthCode.getPreAuthCode()); + param.put("session_info", sessionInfo); + String postData = new Gson().toJson(param); + + post(setSessionUrl, postData); + + 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{ + public WxCpTpAuthInfo getAuthInfo(String authCorpId, String permanentCode) throws WxErrorException { JsonObject jsonObject = new JsonObject(); jsonObject.addProperty("auth_corpid", authCorpId); jsonObject.addProperty("permanent_code", permanentCode); @@ -178,7 +267,7 @@ public T execute(RequestExecutor executor, String uri, E data) thro if (retryTimes + 1 > this.maxRetryTimes) { log.warn("重试达到最大次数【{}】", this.maxRetryTimes); //最后一次重试失败后,直接抛出异常,不再等待 - throw new RuntimeException("微信服务端异常,超出重试次数"); + throw new WxRuntimeException("微信服务端异常,超出重试次数"); } WxError error = e.getError(); @@ -200,7 +289,7 @@ public T execute(RequestExecutor executor, String uri, E data) thro } while (retryTimes++ < this.maxRetryTimes); log.warn("重试达到最大次数【{}】", this.maxRetryTimes); - throw new RuntimeException("微信服务端异常,超出重试次数"); + throw new WxRuntimeException("微信服务端异常,超出重试次数"); } protected T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException { @@ -239,7 +328,7 @@ protected T executeInternal(RequestExecutor executor, String uri, E return null; } catch (IOException e) { log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage()); - throw new RuntimeException(e); + throw new WxRuntimeException(e); } } @@ -273,4 +362,24 @@ public void setTmpDirFile(File tmpDirFile) { return this; } + @Override + public WxSessionManager getSessionManager() { + return this.sessionManager; + } + + @Override + public WxCpTpUserInfo getUserInfo3rd(String code) throws WxErrorException{ + String url = configStorage.getApiUrl(GET_USERINFO3RD); + String result = get(url+"?code="+code,null); + return WxCpTpUserInfo.fromJson(result); + } + + @Override + public WxCpTpUserDetail getUserDetail3rd(String userTicket) throws WxErrorException{ + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("user_ticket", userTicket); + String result = post(configStorage.getApiUrl(GET_USERDETAIL3RD), jsonObject.toString()); + return WxCpTpUserDetail.fromJson(result); + } + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceApacheHttpClientImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java similarity index 96% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceApacheHttpClientImpl.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java index cdc6b2cfc0..22962d933b 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceApacheHttpClientImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java @@ -1,10 +1,11 @@ -package me.chanjar.weixin.cp.api.impl; +package me.chanjar.weixin.cp.tp.service.impl; import com.google.gson.JsonObject; import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.http.HttpType; import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; @@ -81,7 +82,7 @@ public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException Integer expiresIn = jsonObject.get("expires_in").getAsInt(); this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn); } catch (IOException e) { - throw new RuntimeException(e); + throw new WxRuntimeException(e); } } return this.configStorage.getSuiteAccessToken(); diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceImpl.java similarity index 82% rename from weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceImpl.java rename to weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceImpl.java index f5582021e7..58fb09cf97 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/impl/WxCpTpServiceImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceImpl.java @@ -1,4 +1,4 @@ -package me.chanjar.weixin.cp.api.impl; +package me.chanjar.weixin.cp.tp.service.impl; /** *
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
index e721b33acf..7df4cd78fa 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapter.java
@@ -16,6 +16,8 @@
 
 import java.lang.reflect.Type;
 
+import static me.chanjar.weixin.cp.bean.WxCpUser.*;
+
 /**
  * cp user gson adapter.
  *
@@ -84,6 +86,7 @@ public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationC
     user.setTelephone(GsonHelper.getString(o, "telephone"));
     user.setQrCode(GsonHelper.getString(o, "qr_code"));
     user.setToInvite(GsonHelper.getBoolean(o, "to_invite"));
+    user.setOpenUserId(GsonHelper.getString(o, "open_userid"));
     user.setMainDepartment(GsonHelper.getString(o, "main_department"));
 
     if (GsonHelper.isNotNull(o.get(EXTRA_ATTR))) {
@@ -104,7 +107,7 @@ private void buildExtraAttrs(JsonObject o, WxCpUser user) {
     JsonArray attrJsonElements = o.get(EXTRA_ATTR).getAsJsonObject().get("attrs").getAsJsonArray();
     for (JsonElement attrJsonElement : attrJsonElements) {
       final Integer type = GsonHelper.getInteger(attrJsonElement.getAsJsonObject(), "type");
-      final WxCpUser.Attr attr = new WxCpUser.Attr().setType(type)
+      final Attr attr = new Attr().setType(type)
         .setName(GsonHelper.getString(attrJsonElement.getAsJsonObject(), "name"));
       user.getExtAttrs().add(attr);
 
@@ -142,7 +145,7 @@ private void buildExternalAttrs(JsonObject o, WxCpUser user) {
       switch (type) {
         case 0: {
           user.getExternalAttrs()
-            .add(WxCpUser.ExternalAttribute.builder()
+            .add(ExternalAttribute.builder()
               .type(type)
               .name(name)
               .value(GsonHelper.getString(element.getAsJsonObject().get("text").getAsJsonObject(), "value"))
@@ -153,7 +156,7 @@ private void buildExternalAttrs(JsonObject o, WxCpUser user) {
         case 1: {
           final JsonObject web = element.getAsJsonObject().get("web").getAsJsonObject();
           user.getExternalAttrs()
-            .add(WxCpUser.ExternalAttribute.builder()
+            .add(ExternalAttribute.builder()
               .type(type)
               .name(name)
               .url(GsonHelper.getString(web, "url"))
@@ -165,7 +168,7 @@ private void buildExternalAttrs(JsonObject o, WxCpUser user) {
         case 2: {
           final JsonObject miniprogram = element.getAsJsonObject().get("miniprogram").getAsJsonObject();
           user.getExternalAttrs()
-            .add(WxCpUser.ExternalAttribute.builder()
+            .add(ExternalAttribute.builder()
               .type(type)
               .name(name)
               .appid(GsonHelper.getString(miniprogram, "appid"))
@@ -276,11 +279,11 @@ public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationCon
       o.addProperty("main_department", user.getMainDepartment());
     }
 
-    if (user.getExtAttrs().size() > 0) {
+    if (!user.getExtAttrs().isEmpty()) {
       JsonArray attrsJsonArray = new JsonArray();
-      for (WxCpUser.Attr attr : user.getExtAttrs()) {
-        JsonObject attrJson = new JsonObject();
-
+      for (Attr attr : user.getExtAttrs()) {
+        JsonObject attrJson = GsonHelper.buildJsonObject("type", attr.getType(),
+          "name", attr.getName());
         attrsJsonArray.add(attrJson);
 
         if (attr.getType() == null) {
@@ -290,19 +293,12 @@ public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationCon
         }
 
         switch (attr.getType()) {
-          case 0: {
-            JsonObject text = new JsonObject();
-            text.addProperty("value", attr.getTextValue());
-            attrJson.add("text", text);
+          case 0:
+            attrJson.add("text", GsonHelper.buildJsonObject("value", attr.getTextValue()));
             break;
-          }
-          case 1: {
-            JsonObject web = new JsonObject();
-            web.addProperty("url", attr.getWebUrl());
-            web.addProperty("title", attr.getWebTitle());
-            attrJson.add("web", web);
+          case 1:
+            attrJson.add("web", GsonHelper.buildJsonObject("url", attr.getWebUrl(), "title", attr.getWebTitle()));
             break;
-          }
           default: //ignored
         }
       }
@@ -322,12 +318,11 @@ public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationCon
       attrsJson.addProperty(EXTERNAL_CORP_NAME, user.getExternalCorpName());
     }
 
-    if (user.getExternalAttrs().size() > 0) {
+    if (!user.getExternalAttrs().isEmpty()) {
       JsonArray attrsJsonArray = new JsonArray();
-      for (WxCpUser.ExternalAttribute attr : user.getExternalAttrs()) {
-        JsonObject attrJson = new JsonObject();
-        attrJson.addProperty("type", attr.getType());
-        attrJson.addProperty("name", attr.getName());
+      for (ExternalAttribute attr : user.getExternalAttrs()) {
+        JsonObject attrJson = GsonHelper.buildJsonObject("type", attr.getType(),
+          "name", attr.getName());
 
         attrsJsonArray.add(attrJson);
 
@@ -336,27 +331,16 @@ public JsonElement serialize(WxCpUser user, Type typeOfSrc, JsonSerializationCon
         }
 
         switch (attr.getType()) {
-          case 0: {
-            JsonObject text = new JsonObject();
-            text.addProperty("value", attr.getValue());
-            attrJson.add("text", text);
+          case 0:
+            attrJson.add("text", GsonHelper.buildJsonObject("value", attr.getValue()));
             break;
-          }
-          case 1: {
-            JsonObject web = new JsonObject();
-            web.addProperty("url", attr.getUrl());
-            web.addProperty("title", attr.getTitle());
-            attrJson.add("web", web);
+          case 1:
+            attrJson.add("web", GsonHelper.buildJsonObject("url", attr.getUrl(), "title", attr.getTitle()));
             break;
-          }
-          case 2: {
-            JsonObject miniprogram = new JsonObject();
-            miniprogram.addProperty("appid", attr.getAppid());
-            miniprogram.addProperty("pagepath", attr.getPagePath());
-            miniprogram.addProperty("title", attr.getTitle());
-            attrJson.add("miniprogram", miniprogram);
+          case 2:
+            attrJson.add("miniprogram", GsonHelper.buildJsonObject("appid", attr.getAppid(),
+              "pagepath", attr.getPagePath(), "title", attr.getTitle()));
             break;
-          }
           default://忽略
         }
       }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
index 3c6174c9d8..ea90231112 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/xml/XStreamTransformer.java
@@ -1,135 +1,135 @@
-package me.chanjar.weixin.cp.util.xml;
-
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import com.thoughtworks.xstream.XStream;
-import me.chanjar.weixin.common.util.xml.XStreamInitializer;
-import me.chanjar.weixin.cp.bean.WxCpTpXmlMessage;
-import me.chanjar.weixin.cp.bean.WxCpTpXmlPackage;
-import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutImageMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutNewsMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutVideoMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutVoiceMessage;
-
-public class XStreamTransformer {
-
-  protected static final Map CLASS_2_XSTREAM_INSTANCE = configXStreamInstance();
-
-  /**
-   * xml -> pojo
-   */
-  @SuppressWarnings("unchecked")
-  public static  T fromXml(Class clazz, String xml) {
-    T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(xml);
-    return object;
-  }
-
-  @SuppressWarnings("unchecked")
-  public static  T fromXml(Class clazz, InputStream is) {
-    T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is);
-    return object;
-  }
-
-  /**
-   * 注册扩展消息的解析器.
-   *
-   * @param clz     类型
-   * @param xStream xml解析器
-   */
-  public static void register(Class clz, XStream xStream) {
-    CLASS_2_XSTREAM_INSTANCE.put(clz, xStream);
-  }
-
-  /**
-   * pojo -> xml.
-   */
-  public static  String toXml(Class clazz, T object) {
-    return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object);
-  }
-
-  private static Map configXStreamInstance() {
-    Map map = new HashMap<>();
-    map.put(WxCpXmlMessage.class, configWxCpXmlMessage());
-    map.put(WxCpXmlOutNewsMessage.class, configWxCpXmlOutNewsMessage());
-    map.put(WxCpXmlOutTextMessage.class, configWxCpXmlOutTextMessage());
-    map.put(WxCpXmlOutImageMessage.class, configWxCpXmlOutImageMessage());
-    map.put(WxCpXmlOutVideoMessage.class, configWxCpXmlOutVideoMessage());
-    map.put(WxCpXmlOutVoiceMessage.class, configWxCpXmlOutVoiceMessage());
-    map.put(WxCpTpXmlPackage.class, configWxCpTpXmlPackage());
-    map.put(WxCpTpXmlMessage.class, configWxCpTpXmlMessage());
-    return map;
-  }
-
-  private static XStream configWxCpXmlMessage() {
-    XStream xstream = XStreamInitializer.getInstance();
-
-    xstream.processAnnotations(WxCpXmlMessage.class);
-    xstream.processAnnotations(WxCpXmlMessage.ScanCodeInfo.class);
-    xstream.processAnnotations(WxCpXmlMessage.SendPicsInfo.class);
-    xstream.processAnnotations(WxCpXmlMessage.SendPicsInfo.Item.class);
-    xstream.processAnnotations(WxCpXmlMessage.SendLocationInfo.class);
-    return xstream;
-  }
-
-  private static XStream configWxCpXmlOutImageMessage() {
-    XStream xstream = XStreamInitializer.getInstance();
-
-    xstream.processAnnotations(WxCpXmlOutMessage.class);
-    xstream.processAnnotations(WxCpXmlOutImageMessage.class);
-    return xstream;
-  }
-
-  private static XStream configWxCpXmlOutNewsMessage() {
-    XStream xstream = XStreamInitializer.getInstance();
-
-    xstream.processAnnotations(WxCpXmlOutMessage.class);
-    xstream.processAnnotations(WxCpXmlOutNewsMessage.class);
-    xstream.processAnnotations(WxCpXmlOutNewsMessage.Item.class);
-    return xstream;
-  }
-
-  private static XStream configWxCpXmlOutTextMessage() {
-    XStream xstream = XStreamInitializer.getInstance();
-
-    xstream.processAnnotations(WxCpXmlOutMessage.class);
-    xstream.processAnnotations(WxCpXmlOutTextMessage.class);
-    return xstream;
-  }
-
-  private static XStream configWxCpXmlOutVideoMessage() {
-    XStream xstream = XStreamInitializer.getInstance();
-
-    xstream.processAnnotations(WxCpXmlOutMessage.class);
-    xstream.processAnnotations(WxCpXmlOutVideoMessage.class);
-    xstream.processAnnotations(WxCpXmlOutVideoMessage.Video.class);
-    return xstream;
-  }
-
-  private static XStream configWxCpXmlOutVoiceMessage() {
-    XStream xstream = XStreamInitializer.getInstance();
-
-    xstream.processAnnotations(WxCpXmlOutMessage.class);
-    xstream.processAnnotations(WxCpXmlOutVoiceMessage.class);
-    return xstream;
-  }
-  
-  private static XStream configWxCpTpXmlPackage() {
-    XStream xstream = XStreamInitializer.getInstance();
-    xstream.processAnnotations(WxCpTpXmlPackage.class);
-    
-    return xstream;
-  }
-  
-  private static XStream configWxCpTpXmlMessage() {
-    XStream xstream = XStreamInitializer.getInstance();
-    xstream.processAnnotations(WxCpTpXmlMessage.class);
-    
-    return xstream;
-  }
-
-}
+package me.chanjar.weixin.cp.util.xml;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.thoughtworks.xstream.XStream;
+import me.chanjar.weixin.common.util.xml.XStreamInitializer;
+import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
+import me.chanjar.weixin.cp.bean.WxCpTpXmlPackage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutImageMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutNewsMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutTextMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutVideoMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutVoiceMessage;
+
+public class XStreamTransformer {
+
+  protected static final Map CLASS_2_XSTREAM_INSTANCE = configXStreamInstance();
+
+  /**
+   * xml -> pojo
+   */
+  @SuppressWarnings("unchecked")
+  public static  T fromXml(Class clazz, String xml) {
+    T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(xml);
+    return object;
+  }
+
+  @SuppressWarnings("unchecked")
+  public static  T fromXml(Class clazz, InputStream is) {
+    T object = (T) CLASS_2_XSTREAM_INSTANCE.get(clazz).fromXML(is);
+    return object;
+  }
+
+  /**
+   * 注册扩展消息的解析器.
+   *
+   * @param clz     类型
+   * @param xStream xml解析器
+   */
+  public static void register(Class clz, XStream xStream) {
+    CLASS_2_XSTREAM_INSTANCE.put(clz, xStream);
+  }
+
+  /**
+   * pojo -> xml.
+   */
+  public static  String toXml(Class clazz, T object) {
+    return CLASS_2_XSTREAM_INSTANCE.get(clazz).toXML(object);
+  }
+
+  private static Map configXStreamInstance() {
+    Map map = new HashMap<>();
+    map.put(WxCpXmlMessage.class, configWxCpXmlMessage());
+    map.put(WxCpXmlOutNewsMessage.class, configWxCpXmlOutNewsMessage());
+    map.put(WxCpXmlOutTextMessage.class, configWxCpXmlOutTextMessage());
+    map.put(WxCpXmlOutImageMessage.class, configWxCpXmlOutImageMessage());
+    map.put(WxCpXmlOutVideoMessage.class, configWxCpXmlOutVideoMessage());
+    map.put(WxCpXmlOutVoiceMessage.class, configWxCpXmlOutVoiceMessage());
+    map.put(WxCpTpXmlPackage.class, configWxCpTpXmlPackage());
+    map.put(WxCpTpXmlMessage.class, configWxCpTpXmlMessage());
+    return map;
+  }
+
+  private static XStream configWxCpXmlMessage() {
+    XStream xstream = XStreamInitializer.getInstance();
+
+    xstream.processAnnotations(WxCpXmlMessage.class);
+    xstream.processAnnotations(WxCpXmlMessage.ScanCodeInfo.class);
+    xstream.processAnnotations(WxCpXmlMessage.SendPicsInfo.class);
+    xstream.processAnnotations(WxCpXmlMessage.SendPicsInfo.Item.class);
+    xstream.processAnnotations(WxCpXmlMessage.SendLocationInfo.class);
+    return xstream;
+  }
+
+  private static XStream configWxCpXmlOutImageMessage() {
+    XStream xstream = XStreamInitializer.getInstance();
+
+    xstream.processAnnotations(WxCpXmlOutMessage.class);
+    xstream.processAnnotations(WxCpXmlOutImageMessage.class);
+    return xstream;
+  }
+
+  private static XStream configWxCpXmlOutNewsMessage() {
+    XStream xstream = XStreamInitializer.getInstance();
+
+    xstream.processAnnotations(WxCpXmlOutMessage.class);
+    xstream.processAnnotations(WxCpXmlOutNewsMessage.class);
+    xstream.processAnnotations(WxCpXmlOutNewsMessage.Item.class);
+    return xstream;
+  }
+
+  private static XStream configWxCpXmlOutTextMessage() {
+    XStream xstream = XStreamInitializer.getInstance();
+
+    xstream.processAnnotations(WxCpXmlOutMessage.class);
+    xstream.processAnnotations(WxCpXmlOutTextMessage.class);
+    return xstream;
+  }
+
+  private static XStream configWxCpXmlOutVideoMessage() {
+    XStream xstream = XStreamInitializer.getInstance();
+
+    xstream.processAnnotations(WxCpXmlOutMessage.class);
+    xstream.processAnnotations(WxCpXmlOutVideoMessage.class);
+    xstream.processAnnotations(WxCpXmlOutVideoMessage.Video.class);
+    return xstream;
+  }
+
+  private static XStream configWxCpXmlOutVoiceMessage() {
+    XStream xstream = XStreamInitializer.getInstance();
+
+    xstream.processAnnotations(WxCpXmlOutMessage.class);
+    xstream.processAnnotations(WxCpXmlOutVoiceMessage.class);
+    return xstream;
+  }
+
+  private static XStream configWxCpTpXmlPackage() {
+    XStream xstream = XStreamInitializer.getInstance();
+    xstream.processAnnotations(WxCpTpXmlPackage.class);
+
+    return xstream;
+  }
+
+  private static XStream configWxCpTpXmlMessage() {
+    XStream xstream = XStreamInitializer.getInstance();
+    xstream.processAnnotations(WxCpTpXmlMessage.class);
+
+    return xstream;
+  }
+
+}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java
index c15e3af4d1..9d89892f4f 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModule.java
@@ -1,22 +1,24 @@
 package me.chanjar.weixin.cp.api;
 
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import com.google.inject.Binder;
 import com.google.inject.Module;
 import com.thoughtworks.xstream.XStream;
 import com.thoughtworks.xstream.annotations.XStreamAlias;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.xml.XStreamInitializer;
 import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
 import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
 
+import java.io.IOException;
+import java.io.InputStream;
+
+@Slf4j
 public class ApiTestModule implements Module {
-  private final Logger log = LoggerFactory.getLogger(this.getClass());
   private static final String TEST_CONFIG_XML = "test-config.xml";
+  protected WxXmlCpInMemoryConfigStorage config;
 
   private static  T fromXml(Class clazz, InputStream is) {
     XStream xstream = XStreamInitializer.getInstance();
@@ -29,73 +31,30 @@ private static  T fromXml(Class clazz, InputStream is) {
   public void configure(Binder binder) {
     try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(TEST_CONFIG_XML)) {
       if (inputStream == null) {
-        throw new RuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成");
+        throw new WxRuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成");
       }
 
-      WxXmlCpInMemoryConfigStorage config = fromXml(WxXmlCpInMemoryConfigStorage.class, inputStream);
+      config = fromXml(WxXmlCpInMemoryConfigStorage.class, inputStream);
       WxCpService wxService = new WxCpServiceImpl();
       wxService.setWxCpConfigStorage(config);
 
       binder.bind(WxCpService.class).toInstance(wxService);
       binder.bind(WxXmlCpInMemoryConfigStorage.class).toInstance(config);
     } catch (IOException e) {
-      this.log.error(e.getMessage(), e);
+      log.error(e.getMessage(), e);
     }
   }
 
+  @Data
+  @EqualsAndHashCode(callSuper = true)
   @XStreamAlias("xml")
   public static class WxXmlCpInMemoryConfigStorage extends WxCpDefaultConfigImpl {
+    private static final long serialVersionUID = -4521839921547374822L;
 
     protected String userId;
-
     protected String departmentId;
-
     protected String tagId;
-
     protected String externalUserId;
-
-    public String getUserId() {
-      return this.userId;
-    }
-
-    public void setUserId(String userId) {
-      this.userId = userId;
-    }
-
-    public String getDepartmentId() {
-      return this.departmentId;
-    }
-
-    public void setDepartmentId(String departmentId) {
-      this.departmentId = departmentId;
-    }
-
-    public String getTagId() {
-      return this.tagId;
-    }
-
-    public void setTagId(String tagId) {
-      this.tagId = tagId;
-    }
-
-    public String getExternalUserId() {
-      return externalUserId;
-    }
-
-    public void setExternalUserId(String externalUserId) {
-      this.externalUserId = externalUserId;
-    }
-
-    @Override
-    public String toString() {
-      return super.toString() + " > WxXmlCpConfigStorage{" +
-        "userId='" + this.userId + '\'' +
-        ", departmentId='" + this.departmentId + '\'' +
-        ", tagId='" + this.tagId + '\'' +
-        ", externalUserId='" + this.externalUserId + '\'' +
-
-        '}';
-    }
   }
 
 }
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModuleWithMockServer.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModuleWithMockServer.java
new file mode 100644
index 0000000000..83f38612d9
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/ApiTestModuleWithMockServer.java
@@ -0,0 +1,19 @@
+package me.chanjar.weixin.cp.api;
+
+import com.google.inject.Binder;
+
+/**
+ * 带mock server 的test module.
+ *
+ * @author Binary Wang
+ * @date 2020-08-30
+ */
+public class ApiTestModuleWithMockServer extends ApiTestModule {
+  public static final int mockServerPort = 8080;
+
+  @Override
+  public void configure(Binder binder) {
+    super.configure(binder);
+    super.config.setBaseApiUrl("http://localhost:" + mockServerPort);
+  }
+}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java
index 0bd8a24de3..b8a72add12 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpBusyRetryTest.java
@@ -1,8 +1,8 @@
 package me.chanjar.weixin.cp.api;
 
 import lombok.extern.slf4j.Slf4j;
-import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
 import org.testng.annotations.DataProvider;
@@ -25,7 +25,7 @@ public synchronized  T executeInternal(
         RequestExecutor executor, String uri, E data)
         throws WxErrorException {
         log.info("Executed");
-        throw new WxErrorException(WxError.builder().errorCode(-1).build());
+        throw new WxErrorException("something");
       }
     };
 
@@ -45,18 +45,15 @@ public void testRetry(WxCpService service) throws WxErrorException {
   public void testRetryInThreadPool(final WxCpService service) throws InterruptedException, ExecutionException {
     // 当线程池中的线程复用的时候,还是能保证相同的重试次数
     ExecutorService executorService = Executors.newFixedThreadPool(1);
-    Runnable runnable = new Runnable() {
-      @Override
-      public void run() {
-        try {
-          System.out.println("=====================");
-          System.out.println(Thread.currentThread().getName() + ": testRetry");
-          service.execute(null, null, null);
-        } catch (WxErrorException e) {
-          throw new RuntimeException(e);
-        } catch (RuntimeException e) {
-          // OK
-        }
+    Runnable runnable = () -> {
+      try {
+        System.out.println("=====================");
+        System.out.println(Thread.currentThread().getName() + ": testRetry");
+        service.execute(null, null, null);
+      } catch (WxErrorException e) {
+        throw new WxRuntimeException(e);
+      } catch (RuntimeException e) {
+        // OK
       }
     };
     Future submit1 = executorService.submit(runnable);
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java
index a213488953..5e3f665c35 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageRouterTest.java
@@ -6,8 +6,8 @@
 import me.chanjar.weixin.cp.message.WxCpMessageHandler;
 import me.chanjar.weixin.cp.message.WxCpMessageMatcher;
 import me.chanjar.weixin.cp.message.WxCpMessageRouter;
-import me.chanjar.weixin.cp.bean.WxCpXmlMessage;
-import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
 import org.testng.Assert;
 import org.testng.annotations.DataProvider;
 import org.testng.annotations.Test;
@@ -84,14 +84,11 @@ public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map co
     }).end();
 
     final WxCpXmlMessage m = new WxCpXmlMessage();
-    Runnable r = new Runnable() {
-      @Override
-      public void run() {
-        router.route(m);
-        try {
-          Thread.sleep(1000);
-        } catch (InterruptedException e) {
-        }
+    Runnable r = () -> {
+      router.route(m);
+      try {
+        Thread.sleep(1000);
+      } catch (InterruptedException e) {
       }
     };
     for (int i = 0; i < 10; i++) {
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpTpMessageRouterTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpTpMessageRouterTest.java
new file mode 100644
index 0000000000..f6ab29df7f
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpTpMessageRouterTest.java
@@ -0,0 +1,57 @@
+package me.chanjar.weixin.cp.api;
+
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.session.WxSessionManager;
+import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
+import me.chanjar.weixin.cp.tp.message.WxCpTpMessageHandler;
+import me.chanjar.weixin.cp.tp.message.WxCpTpMessageRouter;
+import me.chanjar.weixin.cp.tp.service.WxCpTpService;
+import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceImpl;
+import org.testng.annotations.Test;
+
+import java.util.Map;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNull;
+
+public class WxCpTpMessageRouterTest {
+
+
+  @Test
+  public void testMessageRouter() {
+    WxCpTpService service = new WxCpTpServiceImpl();
+    WxCpTpMessageRouter router = new WxCpTpMessageRouter(service);
+
+    String xml = "\n" +
+      "    \n" +
+      "    \n" +
+      "    \n" +
+      "    1403610513\n" +
+      "    \n" +
+      "    1\n" +
+      "    \n" +
+      "    \n" +
+      "    \n" +
+      "    \n" +
+      "";
+
+    WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml);
+
+    router.rule().infoType("change_contact").changeType("update_tag").handler(new WxCpTpMessageHandler() {
+      @Override
+      public WxCpXmlOutMessage handle(WxCpTpXmlMessage wxMessage, Map context, WxCpTpService wxCpService, WxSessionManager sessionManager) throws WxErrorException {
+        System.out.println("handler enter");
+        assertNotNull(wxCpService);
+        return null;
+      }
+    }).end();
+
+    assertNull(router.route(wxXmlMessage));
+
+
+    System.out.println("over");
+  }
+
+}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchImplTest.java
new file mode 100644
index 0000000000..eca3a1df9f
--- /dev/null
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpAgentWorkBenchImplTest.java
@@ -0,0 +1,130 @@
+package me.chanjar.weixin.cp.api.impl;
+
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.cp.api.ApiTestModule;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.WxCpAgentWorkBench;
+import me.chanjar.weixin.cp.bean.workbench.WorkBenchKeyData;
+import me.chanjar.weixin.cp.bean.workbench.WorkBenchList;
+import me.chanjar.weixin.cp.constant.WxCpConsts;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author songshiyu
+ * @date : create in 10:31 2020/9/29
+ * @description: 测试工作台服务
+ */
+@Guice(modules = ApiTestModule.class)
+public class WxCpAgentWorkBenchImplTest {
+
+  @Inject
+  private WxCpService wxCpService;
+
+  @Test(dataProvider = "template")
+  public void testTemplateSet(WxCpAgentWorkBench template) throws WxErrorException {
+    this.wxCpService.getWorkBenchService().setWorkBenchTemplate(template);
+  }
+
+  @Test
+  public void testTemplateGet() throws WxErrorException {
+    String result = this.wxCpService.getWorkBenchService().getWorkBenchTemplate(1000011L);
+    System.out.println("获取工作台模板设置:"+result);
+  }
+
+  @Test(dataProvider = "userDatas")
+  public void testUserDataSet(WxCpAgentWorkBench userDatas) throws WxErrorException {
+    this.wxCpService.getWorkBenchService().setWorkBenchData(userDatas);
+  }
+
+  @DataProvider
+  public Object[][] template() {
+    return new Object[][]{
+      {WxCpAgentWorkBench.builder()
+        .agentId(1000011L)
+        .replaceUserData(true)
+        .type(WxCpConsts.WorkBenchType.IMAGE)
+        .url("http://www.qq.com")
+        .jumpUrl("http://www.qq.com")
+        .pagePath("pages/index")
+        .build()
+      },
+    };
+  }
+
+  @DataProvider
+  public Object[][] userDatas() {
+    return new Object[][]{
+      {WxCpAgentWorkBench.builder()
+        .agentId(1000011L)
+        .userId("HaHa")
+        .type(WxCpConsts.WorkBenchType.IMAGE)
+        .url("http://www.qq.com")
+        .jumpUrl("http://www.qq.com")
+        .pagePath("pages/index")
+        .build()
+      },
+    };
+  }
+
+  @Test
+  public void testKeyDataTemplateSet() throws WxErrorException {
+    WxCpAgentWorkBench template = new WxCpAgentWorkBench();
+    template.setAgentId(1000011L);
+    template.setType(WxCpConsts.WorkBenchType.KEYDATA);
+    List workBenchKeyDataList = new ArrayList<>();
+    for(int i = 1;i < 4;i++){
+      WorkBenchKeyData workBenchKeyData = new WorkBenchKeyData();
+      workBenchKeyData.setKey("审批"+i);
+      workBenchKeyData.setData(String.valueOf(i));
+      workBenchKeyData.setJumpUrl("http://www.qq.com");
+      workBenchKeyData.setPagePath("pages/index");
+      workBenchKeyDataList.add(workBenchKeyData);
+    }
+    template.setKeyDataList(workBenchKeyDataList);
+    template.setReplaceUserData(true);
+    this.wxCpService.getWorkBenchService().setWorkBenchTemplate(template);
+  }
+
+  @Test
+  public void testKeyDataUserDataSet() throws WxErrorException {
+    WxCpAgentWorkBench template = new WxCpAgentWorkBench();
+    template.setAgentId(1000011L);
+    template.setUserId("HaHa");
+    template.setType(WxCpConsts.WorkBenchType.KEYDATA);
+    List workBenchKeyDataList = new ArrayList<>();
+    WorkBenchKeyData workBenchKeyData = new WorkBenchKeyData();
+    workBenchKeyData.setKey("待审批");
+    workBenchKeyData.setData("2");
+    workBenchKeyData.setJumpUrl("http://www.qq.com");
+    workBenchKeyData.setPagePath("pages/index");
+    workBenchKeyDataList.add(workBenchKeyData);
+    template.setKeyDataList(workBenchKeyDataList);
+    this.wxCpService.getWorkBenchService().setWorkBenchTemplate(template);
+  }
+
+  @Test
+  public void testListTemplateSet() throws WxErrorException {
+    WxCpAgentWorkBench template = new WxCpAgentWorkBench();
+    template.setAgentId(1000011L);
+    template.setType(WxCpConsts.WorkBenchType.LIST);
+    List workBenchListArray = new ArrayList<>();
+    for(int i = 0;i < 2;i++){
+      WorkBenchList workBenchlist = new WorkBenchList();
+      workBenchlist.setTitle("测试"+i);
+      workBenchlist.setJumpUrl("http://www.qq.com");
+      workBenchlist.setPagePath("pages/index");
+      workBenchListArray.add(workBenchlist);
+    }
+    template.setLists(workBenchListArray);
+    template.setReplaceUserData(true);
+    this.wxCpService.getWorkBenchService().setWorkBenchTemplate(template);
+  }
+
+
+}
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImplTest.java
index 0056b88f77..56df47e36a 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpChatServiceImplTest.java
@@ -11,7 +11,7 @@
 import me.chanjar.weixin.cp.constant.WxCpConsts.AppChatMsgType;
 import me.chanjar.weixin.cp.api.ApiTestModule;
 import me.chanjar.weixin.cp.api.WxCpService;
-import me.chanjar.weixin.cp.bean.WxCpAppChatMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpAppChatMessage;
 import me.chanjar.weixin.cp.bean.WxCpChat;
 import me.chanjar.weixin.cp.bean.article.MpnewsArticle;
 import me.chanjar.weixin.cp.bean.article.NewArticle;
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java
index 9a0fbdbd3d..29089d478d 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpExternalContactServiceImplTest.java
@@ -7,6 +7,7 @@
 import me.chanjar.weixin.cp.api.WxCpService;
 import me.chanjar.weixin.cp.bean.WxCpBaseResp;
 import me.chanjar.weixin.cp.bean.external.*;
+import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo;
 import org.apache.commons.lang3.time.DateFormatUtils;
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
@@ -28,7 +29,7 @@ public class WxCpExternalContactServiceImplTest {
   @Test
   public void testGetExternalContact() throws WxErrorException {
     String externalUserId = this.configStorage.getExternalUserId();
-    WxCpUserExternalContactInfo result = this.wxCpService.getExternalContactService().getExternalContact(externalUserId);
+    WxCpExternalContactInfo result = this.wxCpService.getExternalContactService().getExternalContact(externalUserId);
     System.out.println(result);
     assertNotNull(result);
   }
@@ -105,7 +106,7 @@ public void testListExternalWithPermission() throws WxErrorException {
   @Test
   public void testGetContactDetail() throws WxErrorException {
     String externalUserId = this.configStorage.getExternalUserId();
-    WxCpUserExternalContactInfo result = this.wxCpService.getExternalContactService().getContactDetail(externalUserId);
+    WxCpExternalContactInfo result = this.wxCpService.getExternalContactService().getContactDetail(externalUserId);
     System.out.println(result);
     assertNotNull(result);
   }
@@ -217,4 +218,17 @@ public void testSendWelcomeMsg() throws WxErrorException {
       .welcomeCode("abc")
       .build());
   }
+
+  @Test
+  public void testUpdateRemark() throws WxErrorException {
+    this.wxCpService.getExternalContactService().updateRemark(WxCpUpdateRemarkRequest.builder()
+      .description("abc")
+      .userId("aaa")
+      .externalUserId("aaa")
+      .remark("aa")
+      .remarkCompany("aaa")
+      .remarkMobiles(new String[]{"111","222"})
+      .remarkPicMediaId("aaa")
+      .build());
+  }
 }
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java
index b697efd53b..57bd9b750d 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpGroupRobotServiceImplTest.java
@@ -16,8 +16,6 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import static org.testng.Assert.*;
-
 /**
  * 微信群机器人消息发送api 单元测试
  *
@@ -48,7 +46,7 @@ public void testSendMarkDown() throws WxErrorException {
       ">类型:用户反馈 \n" +
       ">普通用户反馈:117例 \n" +
       ">VIP用户反馈:15例";
-    robotService.sendMarkDown(content);
+    robotService.sendMarkdown(content);
   }
 
   @Test
@@ -62,7 +60,8 @@ public void testSendImage() throws WxErrorException {
 
   @Test
   public void testSendNews() throws WxErrorException {
-    NewArticle article = new NewArticle("图文消息测试","hello world","http://www.baidu.com","http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png");
+    NewArticle article = new NewArticle("图文消息测试", "hello world", "http://www.baidu.com",
+      "http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png", null);
     robotService.sendNews(Stream.of(article).collect(Collectors.toList()));
   }
 }
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImplTest.java
similarity index 56%
rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java
rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImplTest.java
index d0984565aa..3a1e5460fa 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/WxCpMessageAPITest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpMessageServiceImplTest.java
@@ -1,33 +1,56 @@
-package me.chanjar.weixin.cp.api;
+package me.chanjar.weixin.cp.api.impl;
 
+import com.github.dreamhead.moco.HttpServer;
+import com.github.dreamhead.moco.Runner;
 import com.google.common.collect.ImmutableMap;
-import org.testng.annotations.*;
-
 import com.google.inject.Inject;
 import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.cp.bean.WxCpMessage;
-import me.chanjar.weixin.cp.bean.WxCpMessageSendResult;
-
-import static org.testng.Assert.*;
-
-/***
- * 测试发送消息
- * @author Daniel Qian
+import me.chanjar.weixin.cp.api.ApiTestModule;
+import me.chanjar.weixin.cp.api.ApiTestModuleWithMockServer;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.bean.message.WxCpLinkedCorpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessage;
+import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult;
+import me.chanjar.weixin.cp.bean.message.WxCpMessageSendStatistics;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import static com.github.dreamhead.moco.Moco.file;
+import static com.github.dreamhead.moco.MocoJsonRunner.jsonHttpServer;
+import static me.chanjar.weixin.cp.api.ApiTestModuleWithMockServer.mockServerPort;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.testng.Assert.assertNotNull;
+
+/**
+ * 测试类.
  *
+ * @author Binary Wang
+ * @date 2020-08-30
  */
 @Test
+//@Guice(modules = ApiTestModuleWithMockServer.class)
 @Guice(modules = ApiTestModule.class)
-public class WxCpMessageAPITest {
-
+public class WxCpMessageServiceImplTest {
   @Inject
   protected WxCpService wxService;
 
+  private Runner mockRunner;
   private ApiTestModule.WxXmlCpInMemoryConfigStorage configStorage;
 
   @BeforeTest
   public void setup() {
-    configStorage = (ApiTestModule.WxXmlCpInMemoryConfigStorage) this.wxService.getWxCpConfigStorage();
+    HttpServer mockServer = jsonHttpServer(mockServerPort, file("src/test/resources/moco/message.json"));
+    this.mockRunner = Runner.runner(mockServer);
+    this.mockRunner.start();
+    this.configStorage = (ApiTestModule.WxXmlCpInMemoryConfigStorage) this.wxService.getWxCpConfigStorage();
+  }
+
+  @AfterTest
+  public void stopMockServer() {
+    this.mockRunner.stop();
   }
 
   public void testSendMessage() throws WxErrorException {
@@ -37,7 +60,7 @@ public void testSendMessage() throws WxErrorException {
     message.setToUser(configStorage.getUserId());
     message.setContent("欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World");
 
-    WxCpMessageSendResult messageSendResult = this.wxService.messageSend(message);
+    WxCpMessageSendResult messageSendResult = this.wxService.getMessageService().send(message);
     assertNotNull(messageSendResult);
     System.out.println(messageSendResult);
     System.out.println(messageSendResult.getInvalidPartyList());
@@ -54,7 +77,7 @@ public void testSendMessage1() throws WxErrorException {
       .content("欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World")
       .build();
 
-    WxCpMessageSendResult messageSendResult = this.wxService.messageSend(message);
+    WxCpMessageSendResult messageSendResult = this.wxService.getMessageService().send(message);
     assertNotNull(messageSendResult);
     System.out.println(messageSendResult);
     System.out.println(messageSendResult.getInvalidPartyList());
@@ -82,7 +105,7 @@ public void testSendMessage_markdown() throws WxErrorException {
         "                >如需修改会议信息,请点击:[修改会议信息](https://work.weixin.qq.com)")
       .build();
 
-    WxCpMessageSendResult messageSendResult = this.wxService.messageSend(message);
+    WxCpMessageSendResult messageSendResult = this.wxService.getMessageService().send(message);
     assertNotNull(messageSendResult);
     System.out.println(messageSendResult);
     System.out.println(messageSendResult.getInvalidPartyList());
@@ -96,12 +119,12 @@ public void testSendMessage_textCard() throws WxErrorException {
       .TEXTCARD()
       .toUser(configStorage.getUserId())
       .btnTxt("更多")
-      .description( "
2016年9月26日
恭喜你抽中iPhone 7一台,领奖码:xxxx
请于2016年10月10日前联系行政同事领取
") + .description("
2016年9月26日
恭喜你抽中iPhone 7一台,领奖码:xxxx
请于2016年10月10日前联系行政同事领取
") .url("URL") .title("领奖通知") .build(); - WxCpMessageSendResult messageSendResult = this.wxService.messageSend(message); + WxCpMessageSendResult messageSendResult = this.wxService.getMessageService().send(message); assertNotNull(messageSendResult); System.out.println(messageSendResult); System.out.println(messageSendResult.getInvalidPartyList()); @@ -110,7 +133,7 @@ public void testSendMessage_textCard() throws WxErrorException { } @Test - public void testSendMessage_miniprogram_notice() throws WxErrorException { + public void testSendMessage_miniProgram_notice() throws WxErrorException { WxCpMessage message = WxCpMessage .newMiniProgramNoticeBuilder() .toUser(configStorage.getUserId()) @@ -119,16 +142,38 @@ public void testSendMessage_miniprogram_notice() throws WxErrorException { .title("会议室预订成功通知") .description("4月27日 16:16") .emphasisFirstItem(true) - .contentItems(ImmutableMap.of("会议室","402", - "会议地点","广州TIT-402会议室", - "会议时间","2018年8月1日 09:00-09:30")) + .contentItems(ImmutableMap.of("会议室", "402", + "会议地点", "广州TIT-402会议室", + "会议时间", "2018年8月1日 09:00-09:30")) .build(); - WxCpMessageSendResult messageSendResult = this.wxService.messageSend(message); + WxCpMessageSendResult messageSendResult = this.wxService.getMessageService().send(message); assertNotNull(messageSendResult); System.out.println(messageSendResult); System.out.println(messageSendResult.getInvalidPartyList()); System.out.println(messageSendResult.getInvalidUserList()); System.out.println(messageSendResult.getInvalidTagList()); } + + @Test + public void testSendLinkedCorpMessage() throws WxErrorException { + this.wxService.getMessageService().sendLinkedCorpMessage(WxCpLinkedCorpMessage.builder() + .msgType(WxConsts.KefuMsgType.TEXT) + .toUsers(new String[]{configStorage.getUserId()}) + .content("欢迎欢迎,热烈欢迎\n换行测试\n超链接:Hello World") + .build()); + } + + @Test + public void testSend() { + // see other test methods + } + + @Test + public void testGetStatistics() throws WxErrorException { + final WxCpMessageSendStatistics statistics = this.wxService.getMessageService().getStatistics(1); + assertNotNull(statistics); + assertThat(statistics.getStatistics()).isNotNull(); + } + } diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaCalendarServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaCalendarServiceImplTest.java new file mode 100644 index 0000000000..f8f43cb816 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaCalendarServiceImplTest.java @@ -0,0 +1,69 @@ +package me.chanjar.weixin.cp.api.impl; + +import com.google.inject.Inject; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.cp.api.ApiTestModule; +import me.chanjar.weixin.cp.api.WxCpService; +import me.chanjar.weixin.cp.bean.oa.calendar.WxCpOaCalendar; +import org.testng.annotations.Guice; +import org.testng.annotations.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * 单元测试. + * + * @author Binary Wang + * @date 2020-09-20 + */ + +@Test +@Guice(modules = ApiTestModule.class) +public class WxCpOaCalendarServiceImplTest { + @Inject + protected WxCpService wxService; + private final String calId = "wcbBJNCQAARipW967iE8DKPAp5Kb96qQ"; + + @Test + public void testAdd() throws WxErrorException { + this.wxService.getOaCalendarService().add(WxCpOaCalendar.builder() + .organizer("binary") + .readonly(1) + .setAsDefault(1) + .summary("test_summary") + .color("#FF3030") + .description("test_describe") + .shares(Arrays.asList(new WxCpOaCalendar.ShareInfo("binary", null), + new WxCpOaCalendar.ShareInfo("binary", 1))) + .build()); + } + + @Test + public void testUpdate() throws WxErrorException { + this.wxService.getOaCalendarService().update(WxCpOaCalendar.builder() + .calId(calId) + .organizer("binary") + .readonly(1) + .setAsDefault(1) + .summary("test_summary") + .color("#FF3030") + .description("test_describe") + .shares(Arrays.asList(new WxCpOaCalendar.ShareInfo("binary", null), + new WxCpOaCalendar.ShareInfo("binary", 1))) + .build()); + } + + @Test + public void testGet() throws WxErrorException { + final List calendars = this.wxService.getOaCalendarService().get(Arrays.asList(calId)); + assertThat(calendars).isNotEmpty(); + } + + @Test + public void testDelete() throws WxErrorException { + this.wxService.getOaCalendarService().delete(calId); + } +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImplTest.java index 6f3a76b0ab..758f77970e 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImplTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpOaServiceImplTest.java @@ -37,7 +37,7 @@ public void testGetCheckinData() throws ParseException, WxErrorException { Date startTime = DateFormatUtils.ISO_8601_EXTENDED_DATE_FORMAT.parse("2019-04-11"); Date endTime = DateFormatUtils.ISO_8601_EXTENDED_DATE_FORMAT.parse("2019-05-10"); - List results = wxService.getOAService() + List results = wxService.getOaService() .getCheckinData(1, startTime, endTime, Lists.newArrayList("binary")); assertThat(results).isNotNull(); @@ -51,7 +51,7 @@ public void testGetCheckinData() throws ParseException, WxErrorException { public void testGetCheckinOption() throws WxErrorException { Date now = new Date(); - List results = wxService.getOAService().getCheckinOption(now, Lists.newArrayList("binary")); + List results = wxService.getOaService().getCheckinOption(now, Lists.newArrayList("binary")); assertThat(results).isNotNull(); System.out.println("results "); System.out.println(gson.toJson(results)); @@ -61,7 +61,7 @@ public void testGetCheckinOption() throws WxErrorException { public void testGetApprovalInfo() throws WxErrorException, ParseException { Date startTime = DateFormatUtils.ISO_8601_EXTENDED_DATE_FORMAT.parse("2019-12-01"); Date endTime = DateFormatUtils.ISO_8601_EXTENDED_DATE_FORMAT.parse("2019-12-31"); - WxCpApprovalInfo result = wxService.getOAService().getApprovalInfo(startTime, endTime); + WxCpApprovalInfo result = wxService.getOaService().getApprovalInfo(startTime, endTime); assertThat(result).isNotNull(); @@ -72,7 +72,7 @@ public void testGetApprovalInfo() throws WxErrorException, ParseException { @Test public void testGetApprovalDetail() throws WxErrorException { String spNo = "201912020001"; - WxCpApprovalDetailResult result = wxService.getOAService().getApprovalDetail(spNo); + WxCpApprovalDetailResult result = wxService.getOaService().getApprovalDetail(spNo); assertThat(result).isNotNull(); @@ -83,7 +83,7 @@ public void testGetApprovalDetail() throws WxErrorException { @Test public void testGetTemplateDetail() throws WxErrorException { String templateId = "3TkZjxugodbqpEMk9j7X6h6zKqYkc7MxQrrFmT7H"; - WxCpTemplateResult result = wxService.getOAService().getTemplateDetail(templateId); + WxCpTemplateResult result = wxService.getOaService().getTemplateDetail(templateId); assertThat(result).isNotNull(); System.out.println("result "); System.out.println(gson.toJson(result)); @@ -91,7 +91,7 @@ public void testGetTemplateDetail() throws WxErrorException { @Test public void testApply() throws WxErrorException { - this.wxService.getOAService().apply(new WxCpOaApplyEventRequest().setCreatorUserId("123")); + this.wxService.getOaService().apply(new WxCpOaApplyEventRequest().setCreatorUserId("123")); } @Test diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImplTest.java index be387548b9..1bdcb9e244 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImplTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/WxCpTaskCardServiceImplTest.java @@ -4,8 +4,8 @@ import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.cp.api.ApiTestModule; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.WxCpMessage; -import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; +import me.chanjar.weixin.cp.bean.message.WxCpMessage; +import me.chanjar.weixin.cp.bean.message.WxCpMessageSendResult; import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton; import org.testng.annotations.Guice; import org.testng.annotations.Test; @@ -49,7 +49,7 @@ public void testSendTaskCard() throws WxErrorException { .buttons(Arrays.asList(btn1, btn2)) .build(); - WxCpMessageSendResult messageSendResult = this.wxCpService.messageSend(message); + WxCpMessageSendResult messageSendResult = this.wxCpService.getMessageService().send(message); assertNotNull(messageSendResult); System.out.println(messageSendResult); System.out.println(messageSendResult.getInvalidPartyList()); diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/external/WxCpUpdateRemarkRequestTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/external/WxCpUpdateRemarkRequestTest.java new file mode 100644 index 0000000000..9564cdf9bc --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/external/WxCpUpdateRemarkRequestTest.java @@ -0,0 +1,42 @@ +package me.chanjar.weixin.cp.bean.external; + +import me.chanjar.weixin.common.util.json.GsonParser; +import org.testng.annotations.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * 单元测试. + * + * @author Binary Wang + * @date 2020-09-20 + */ +public class WxCpUpdateRemarkRequestTest { + + @Test + public void testToJson() { + String json = "{\n" + + " \"userid\":\"zhangsan\",\n" + + " \"external_userid\":\"woAJ2GCAAAd1asdasdjO4wKmE8Aabj9AAA\",\n" + + " \"remark\":\"备注信息\",\n" + + " \"description\":\"描述信息\",\n" + + " \"remark_company\":\"腾讯科技\",\n" + + " \"remark_mobiles\":[\n" + + " \"13800000001\",\n" + + " \"13800000002\"\n" + + " ],\n" + + " \"remark_pic_mediaid\":\"MEDIAID\"\n" + + "}\n"; + + WxCpUpdateRemarkRequest request = WxCpUpdateRemarkRequest.builder() + .description("描述信息") + .userId("zhangsan") + .externalUserId("woAJ2GCAAAd1asdasdjO4wKmE8Aabj9AAA") + .remark("备注信息") + .remarkCompany("腾讯科技") + .remarkMobiles(new String[]{"13800000001","13800000002"}) + .remarkPicMediaId("MEDIAID") + .build(); + assertThat(request.toJson()).isEqualTo(GsonParser.parse(json).toString()); + } +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfoTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalContactInfoTest.java similarity index 86% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfoTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalContactInfoTest.java index 77b25f9198..c666c1b94d 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpUserExternalContactInfoTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/external/WxCpUserExternalContactInfoTest.java @@ -1,10 +1,12 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.external; -import java.util.List; - -import me.chanjar.weixin.cp.bean.external.WxCpUserExternalContactInfo; +import me.chanjar.weixin.cp.bean.external.contact.ExternalContact; +import me.chanjar.weixin.cp.bean.external.contact.FollowedUser; +import me.chanjar.weixin.cp.bean.external.contact.WxCpExternalContactInfo; import org.testng.annotations.*; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; /** @@ -77,7 +79,7 @@ public void testFromJson() { " ]\n" + "}"; - final WxCpUserExternalContactInfo contactInfo = WxCpUserExternalContactInfo.fromJson(json); + final WxCpExternalContactInfo contactInfo = WxCpExternalContactInfo.fromJson(json); assertThat(contactInfo).isNotNull(); assertThat(contactInfo.getExternalContact()).isNotNull(); @@ -93,21 +95,21 @@ public void testFromJson() { assertThat(contactInfo.getExternalContact().getExternalProfile()).isNotNull(); - final List externalAttrs = contactInfo.getExternalContact().getExternalProfile().getExternalAttrs(); + final List externalAttrs = contactInfo.getExternalContact().getExternalProfile().getExternalAttrs(); assertThat(externalAttrs).isNotEmpty(); - final WxCpUserExternalContactInfo.ExternalAttribute externalAttr1 = externalAttrs.get(0); + final ExternalContact.ExternalAttribute externalAttr1 = externalAttrs.get(0); assertThat(externalAttr1.getType()).isEqualTo(0); assertThat(externalAttr1.getName()).isEqualTo("文本名称"); assertThat(externalAttr1.getText().getValue()).isEqualTo("文本"); - final WxCpUserExternalContactInfo.ExternalAttribute externalAttr2 = externalAttrs.get(1); + final ExternalContact.ExternalAttribute externalAttr2 = externalAttrs.get(1); assertThat(externalAttr2.getType()).isEqualTo(1); assertThat(externalAttr2.getName()).isEqualTo("网页名称"); assertThat(externalAttr2.getWeb().getUrl()).isEqualTo("http://www.test.com"); assertThat(externalAttr2.getWeb().getTitle()).isEqualTo("标题"); - final WxCpUserExternalContactInfo.ExternalAttribute externalAttr3 = externalAttrs.get(2); + final ExternalContact.ExternalAttribute externalAttr3 = externalAttrs.get(2); assertThat(externalAttr3.getType()).isEqualTo(2); assertThat(externalAttr3.getName()).isEqualTo("测试app"); assertThat(externalAttr3.getMiniProgram().getAppid()).isEqualTo("wx8bd80126147df384"); @@ -115,7 +117,7 @@ public void testFromJson() { assertThat(externalAttr3.getMiniProgram().getTitle()).isEqualTo("my miniprogram"); - List followedUsers = contactInfo.getFollowedUsers(); + List followedUsers = contactInfo.getFollowedUsers(); assertThat(followedUsers).isNotEmpty(); assertThat(followedUsers.get(0).getUserId()).isEqualTo("rocky"); assertThat(followedUsers.get(0).getRemark()).isEqualTo("李部长"); diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessageTest.java new file mode 100644 index 0000000000..d692d0fc99 --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpLinkedCorpMessageTest.java @@ -0,0 +1,374 @@ +package me.chanjar.weixin.cp.bean.message; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.common.util.json.GsonParser; +import me.chanjar.weixin.cp.bean.article.MpnewsArticle; +import me.chanjar.weixin.cp.bean.article.NewArticle; +import org.testng.annotations.Test; + +import static me.chanjar.weixin.common.api.WxConsts.*; +import static org.assertj.core.api.Assertions.assertThat; + +/** + * 测试用例中的json参考 https://work.weixin.qq.com/api/doc/90000/90135/90250 + * + * @author Binary Wang + * @date 2020-08-30 + */ +public class WxCpLinkedCorpMessageTest { + + @Test + public void testToJson_text() { + WxCpLinkedCorpMessage message = WxCpLinkedCorpMessage.builder() + .msgType(KefuMsgType.TEXT) + .toUsers(new String[]{"userid1", "userid2", "CorpId1/userid1", "CorpId2/userid2"}) + .toParties(new String[]{"partyid1", "partyid2", "LinkedId1/partyid1", "LinkedId2/partyid2"}) + .toTags(new String[]{"tagid1", "tagid2"}) + .agentId(1) + .isToAll(false) + .isSafe(false) + .content("你的快递已到,请携带工卡前往邮件中心领取。\n出发前可查看邮件中心视频实况,聪明避开排队。") + .build(); + + final String json = message.toJson(); + String expectedJson = "{\n" + + " \"touser\" : [\"userid1\",\"userid2\",\"CorpId1/userid1\",\"CorpId2/userid2\"],\n" + + " \"toparty\" : [\"partyid1\",\"partyid2\",\"LinkedId1/partyid1\",\"LinkedId2/partyid2\"],\n" + + " \"totag\" : [\"tagid1\",\"tagid2\"],\n" + + " \"toall\" : 0,\n" + + " \"msgtype\" : \"text\",\n" + + " \"agentid\" : 1,\n" + + " \"text\" : {\n" + + " \"content\" : \"你的快递已到,请携带工卡前往邮件中心领取。\\n出发前可查看邮件中心视频实况,聪明避开排队。\"\n" + + " },\n" + + " \"safe\":0\n" + + "}"; + + assertThat(json).isEqualTo(GsonParser.parse(expectedJson).toString()); + } + + @Test + public void testToJson_image() { + WxCpLinkedCorpMessage message = WxCpLinkedCorpMessage.builder() + .msgType(KefuMsgType.IMAGE) + .toUsers(new String[]{"userid1", "userid2", "CorpId1/userid1", "CorpId2/userid2"}) + .toParties(new String[]{"partyid1", "partyid2", "LinkedId1/partyid1", "LinkedId2/partyid2"}) + .toTags(new String[]{"tagid1", "tagid2"}) + .agentId(1) + .isToAll(false) + .isSafe(false) + .mediaId("MEDIA_ID") + .build(); + + final String json = message.toJson(); + String expectedJson = "{\n" + + " \"touser\" : [\"userid1\",\"userid2\",\"CorpId1/userid1\",\"CorpId2/userid2\"],\n" + + " \"toparty\" : [\"partyid1\",\"partyid2\",\"LinkedId1/partyid1\",\"LinkedId2/partyid2\"],\n" + + " \"totag\" : [\"tagid1\",\"tagid2\"],\n" + + " \"toall\" : 0,\n" + + " \"msgtype\" : \"image\",\n" + + " \"agentid\" : 1,\n" + + " \"image\" : {\n" + + " \"media_id\" : \"MEDIA_ID\"\n" + + " },\n" + + " \"safe\":0\n" + + "}"; + + assertThat(json).isEqualTo(GsonParser.parse(expectedJson).toString()); + } + + @Test + public void testToJson_video() { + WxCpLinkedCorpMessage message = WxCpLinkedCorpMessage.builder() + .msgType(KefuMsgType.VIDEO) + .toUsers(new String[]{"userid1", "userid2", "CorpId1/userid1", "CorpId2/userid2"}) + .toParties(new String[]{"partyid1", "partyid2", "LinkedId1/partyid1", "LinkedId2/partyid2"}) + .toTags(new String[]{"tagid1", "tagid2"}) + .agentId(1) + .isToAll(false) + .isSafe(false) + .mediaId("MEDIA_ID") + .title("Title") + .description("Description") + .build(); + + final String json = message.toJson(); + String expectedJson = "{\n" + + " \"touser\" : [\"userid1\",\"userid2\",\"CorpId1/userid1\",\"CorpId2/userid2\"],\n" + + " \"toparty\" : [\"partyid1\",\"partyid2\",\"LinkedId1/partyid1\",\"LinkedId2/partyid2\"],\n" + + " \"totag\" : [\"tagid1\",\"tagid2\"],\n" + + " \"toall\" : 0,\n" + + " \"msgtype\" : \"video\",\n" + + " \"agentid\" : 1,\n" + + " \"video\" : {\n" + + " \"media_id\" : \"MEDIA_ID\",\n" + + " \"title\" : \"Title\",\n" + + " \"description\" : \"Description\"\n" + + " },\n" + + " \"safe\":0\n" + + "}\n"; + + assertThat(json).isEqualTo(GsonParser.parse(expectedJson).toString()); + } + + @Test + public void testToJson_file() { + WxCpLinkedCorpMessage message = WxCpLinkedCorpMessage.builder() + .msgType(KefuMsgType.FILE) + .toUsers(new String[]{"userid1", "userid2", "CorpId1/userid1", "CorpId2/userid2"}) + .toParties(new String[]{"partyid1", "partyid2", "LinkedId1/partyid1", "LinkedId2/partyid2"}) + .toTags(new String[]{"tagid1", "tagid2"}) + .agentId(1) + .isToAll(false) + .isSafe(false) + .mediaId("1Yv-zXfHjSjU-7LH-GwtYqDGS-zz6w22KmWAT5COgP7o") + .build(); + + final String json = message.toJson(); + String expectedJson = "{\n" + + " \"touser\" : [\"userid1\",\"userid2\",\"CorpId1/userid1\",\"CorpId2/userid2\"],\n" + + " \"toparty\" : [\"partyid1\",\"partyid2\",\"LinkedId1/partyid1\",\"LinkedId2/partyid2\"],\n" + + " \"totag\" : [\"tagid1\",\"tagid2\"],\n" + + " \"toall\" : 0,\n" + + " \"msgtype\" : \"file\",\n" + + " \"agentid\" : 1,\n" + + " \"file\" : {\n" + + " \"media_id\" : \"1Yv-zXfHjSjU-7LH-GwtYqDGS-zz6w22KmWAT5COgP7o\"\n" + + " },\n" + + " \"safe\":0\n" + + "}\n"; + + assertThat(json).isEqualTo(GsonParser.parse(expectedJson).toString()); + } + + @Test + public void testToJson_textCard() { + WxCpLinkedCorpMessage message = WxCpLinkedCorpMessage.builder() + .msgType(KefuMsgType.TEXTCARD) + .toUsers(new String[]{"userid1", "userid2", "CorpId1/userid1", "CorpId2/userid2"}) + .toParties(new String[]{"partyid1", "partyid2", "LinkedId1/partyid1", "LinkedId2/partyid2"}) + .toTags(new String[]{"tagid1", "tagid2"}) + .agentId(1) + .isToAll(false) + .title("领奖通知") + .description("
2016年9月26日
恭喜你抽中iPhone 7一台,领奖码:xxxx
请于2016年10月10日前联系行政同事领取
") + .url("URL") + .btnTxt("更多") + .build(); + + final String json = message.toJson(); + String expectedJson = "{\n" + + " \"touser\" : [\"userid1\",\"userid2\",\"CorpId1/userid1\",\"CorpId2/userid2\"],\n" + + " \"toparty\" : [\"partyid1\",\"partyid2\",\"LinkedId1/partyid1\",\"LinkedId2/partyid2\"],\n" + + " \"totag\" : [\"tagid1\",\"tagid2\"],\n" + + " \"toall\" : 0,\n" + + " \"msgtype\" : \"textcard\",\n" + + " \"agentid\" : 1,\n" + + " \"textcard\" : {\n" + + " \"title\" : \"领奖通知\",\n" + + " \"description\" : \"
2016年9月26日
恭喜你抽中iPhone 7一台,领奖码:xxxx
请于2016年10月10日前联系行政同事领取
\",\n" + + " \"url\" : \"URL\",\n" + + " \"btntxt\":\"更多\"\n" + + " }\n" + + "}\n"; + + assertThat(json).isEqualTo(GsonParser.parse(expectedJson).toString()); + } + + @Test + public void testToJson_news() { + WxCpLinkedCorpMessage message = WxCpLinkedCorpMessage.builder() + .msgType(KefuMsgType.NEWS) + .toUsers(new String[]{"userid1", "userid2", "CorpId1/userid1", "CorpId2/userid2"}) + .toParties(new String[]{"partyid1", "partyid2", "LinkedId1/partyid1", "LinkedId2/partyid2"}) + .toTags(new String[]{"tagid1", "tagid2"}) + .agentId(1) + .isToAll(false) + .articles(Lists.newArrayList(NewArticle.builder() + .title("中秋节礼品领取") + .description("今年中秋节公司有豪礼相送") + .url("URL") + .picUrl("http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png") + .btnText("更多") + .build())) + .build(); + + final String json = message.toJson(); + String expectedJson = "{\n" + + " \"touser\" : [\"userid1\",\"userid2\",\"CorpId1/userid1\",\"CorpId2/userid2\"],\n" + + " \"toparty\" : [\"partyid1\",\"partyid2\",\"LinkedId1/partyid1\",\"LinkedId2/partyid2\"],\n" + + " \"totag\" : [\"tagid1\",\"tagid2\"],\n" + + " \"toall\" : 0,\n" + + " \"msgtype\" : \"news\",\n" + + " \"agentid\" : 1,\n" + + " \"news\" : {\n" + + " \"articles\" : [\n" + + " {\n" + + " \"title\" : \"中秋节礼品领取\",\n" + + " \"description\" : \"今年中秋节公司有豪礼相送\",\n" + + " \"url\" : \"URL\",\n" + + " \"picurl\" : \"http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png\",\n" + + " \"btntxt\":\"更多\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "}\n"; + + assertThat(json).isEqualTo(GsonParser.parse(expectedJson).toString()); + } + + + @Test + public void testToJson_mpnews() { + WxCpLinkedCorpMessage message = WxCpLinkedCorpMessage.builder() + .msgType(KefuMsgType.MPNEWS) + .toUsers(new String[]{"userid1", "userid2", "CorpId1/userid1", "CorpId2/userid2"}) + .toParties(new String[]{"partyid1", "partyid2", "LinkedId1/partyid1", "LinkedId2/partyid2"}) + .toTags(new String[]{"tagid1", "tagid2"}) + .agentId(1) + .isToAll(false) + .isSafe(false) + .mpNewsArticles(Lists.newArrayList(MpnewsArticle.newBuilder() + .title("Title") + .thumbMediaId("MEDIA_ID") + .author("Author") + .contentSourceUrl("URL") + .content("Content") + .digest("Digest description") + .build())) + .build(); + + final String json = message.toJson(); + String expectedJson = "{\n" + + " \"touser\" : [\"userid1\",\"userid2\",\"CorpId1/userid1\",\"CorpId2/userid2\"],\n" + + " \"toparty\" : [\"partyid1\",\"partyid2\",\"LinkedId1/partyid1\",\"LinkedId2/partyid2\"],\n" + + " \"totag\" : [\"tagid1\",\"tagid2\"],\n" + + " \"toall\" : 0,\n" + + " \"msgtype\" : \"mpnews\",\n" + + " \"agentid\" : 1,\n" + + " \"mpnews\" : {\n" + + " \"articles\":[\n" + + " {\n" + + " \"title\": \"Title\", \n" + + " \"thumb_media_id\": \"MEDIA_ID\",\n" + + " \"author\": \"Author\",\n" + + " \"content_source_url\": \"URL\",\n" + + " \"content\": \"Content\",\n" + + " \"digest\": \"Digest description\"\n" + + " }\n" + + " ]\n" + + " },\n" + + " \"safe\":0\n" + + "}\n"; + + assertThat(json).isEqualTo(GsonParser.parse(expectedJson).toString()); + } + + @Test + public void testToJson_markdown() { + WxCpLinkedCorpMessage message = WxCpLinkedCorpMessage.builder() + .msgType(KefuMsgType.MARKDOWN) + .toUsers(new String[]{"userid1", "userid2", "CorpId1/userid1", "CorpId2/userid2"}) + .toParties(new String[]{"partyid1", "partyid2", "LinkedId1/partyid1", "LinkedId2/partyid2"}) + .toTags(new String[]{"tagid1", "tagid2"}) + .agentId(1) + .isToAll(false) + .content("您的会议室已经预定,稍后会同步到`邮箱`\n" + + " >**事项详情**\n" + + " >事 项:开会\n" + + " >组织者:@miglioguan\n" + + " >参与者:@miglioguan、@kunliu、@jamdeezhou、@kanexiong、@kisonwang\n" + + " >\n" + + " >会议室:广州TIT 1楼 301\n" + + " >日 期:2018年5月18日\n" + + " >时 间:上午9:00-11:00\n" + + " >\n" + + " >请准时参加会议。\n" + + " >\n" + + " >如需修改会议信息,请点击:[修改会议信息](https://work.weixin.qq.com)") + .build(); + + final String json = message.toJson(); + String expectedJson = "{\n" + + " \"touser\" : [\"userid1\",\"userid2\",\"CorpId1/userid1\",\"CorpId2/userid2\"],\n" + + " \"toparty\" : [\"partyid1\",\"partyid2\",\"LinkedId1/partyid1\",\"LinkedId2/partyid2\"],\n" + + " \"totag\" : [\"tagid1\",\"tagid2\"],\n" + + " \"toall\" : 0,\n" + + " \"msgtype\" : \"markdown\",\n" + + " \"agentid\" : 1,\n" + + " \"markdown\": {\n" + + " \"content\": \"您的会议室已经预定,稍后会同步到`邮箱`\n" + + " >**事项详情**\n" + + " >事 项:开会\n" + + " >组织者:@miglioguan\n" + + " >参与者:@miglioguan、@kunliu、@jamdeezhou、@kanexiong、@kisonwang\n" + + " >\n" + + " >会议室:广州TIT 1楼 301\n" + + " >日 期:2018年5月18日\n" + + " >时 间:上午9:00-11:00\n" + + " >\n" + + " >请准时参加会议。\n" + + " >\n" + + " >如需修改会议信息,请点击:[修改会议信息](https://work.weixin.qq.com)\"\n" + + " }\n" + + "}\n"; + + assertThat(json).isEqualTo(GsonParser.parse(expectedJson).toString()); + } + + @Test + public void testToJson_miniProgramNotice() { + WxCpLinkedCorpMessage message = WxCpLinkedCorpMessage.builder() + .msgType(KefuMsgType.MINIPROGRAM_NOTICE) + .toUsers(new String[]{"userid1", "userid2", "CorpId1/userid1", "CorpId2/userid2"}) + .toParties(new String[]{"partyid1", "partyid2", "LinkedId1/partyid1", "LinkedId2/partyid2"}) + .toTags(new String[]{"tagid1", "tagid2"}) + .emphasisFirstItem(true) + .description("4月27日 16:16") + .title("会议室预订成功通知") + .appId("wx123123123123123") + .page("pages/index?userid=zhangsan&orderid=123123123") + .contentItems(ImmutableMap.of("会议室","402", + "会议地点","广州TIT-402会议室", + "会议时间","2018年8月1日 09:00-09:30", + "参与人员","周剑轩")) + .build(); + + final String json = message.toJson(); + String expectedJson = "{\n" + + " \"touser\" : [\"userid1\",\"userid2\",\"CorpId1/userid1\",\"CorpId2/userid2\"],\n" + + " \"toparty\" : [\"partyid1\",\"partyid2\",\"LinkedId1/partyid1\",\"LinkedId2/partyid2\"],\n" + + " \"totag\" : [\"tagid1\",\"tagid2\"],\n" + + " \"msgtype\" : \"miniprogram_notice\",\n" + + " \"miniprogram_notice\" : {\n" + + " \"appid\": \"wx123123123123123\",\n" + + " \"page\": \"pages/index?userid=zhangsan&orderid=123123123\",\n" + + " \"title\": \"会议室预订成功通知\",\n" + + " \"description\": \"4月27日 16:16\",\n" + + " \"emphasis_first_item\": true,\n" + + " \"content_item\": [\n" + + " {\n" + + " \"key\": \"会议室\",\n" + + " \"value\": \"402\"\n" + + " },\n" + + " {\n" + + " \"key\": \"会议地点\",\n" + + " \"value\": \"广州TIT-402会议室\"\n" + + " },\n" + + " {\n" + + " \"key\": \"会议时间\",\n" + + " \"value\": \"2018年8月1日 09:00-09:30\"\n" + + " },\n" + + " {\n" + + " \"key\": \"参与人员\",\n" + + " \"value\": \"周剑轩\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "}\n"; + + assertThat(json).isEqualTo(GsonParser.parse(expectedJson).toString()); + } +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpMessageTest.java similarity index 98% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpMessageTest.java index c54211758b..3f7859116e 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpMessageTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpMessageTest.java @@ -1,7 +1,8 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import me.chanjar.weixin.cp.bean.article.MpnewsArticle; import me.chanjar.weixin.cp.bean.article.NewArticle; +import me.chanjar.weixin.cp.bean.message.WxCpMessage; import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton; import org.testng.annotations.Test; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessageTest.java new file mode 100644 index 0000000000..1e4a1450ac --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessageTest.java @@ -0,0 +1,234 @@ +package me.chanjar.weixin.cp.bean.message; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + +public class WxCpTpXmlMessageTest { + + @Test + public void testUserNotifyXML() { + String xml = "\n" + + " \n" + + " \n" + + " \n" + + " 1403610513\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 1\n" + + " \n" + + " \n" + + " \n" + + " 1\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 0\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 1\n" + + " \n" + + " <![CDATA[企业微信]]>\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml); + assertEquals(wxXmlMessage.getSuiteId(), "ww4asffe99e54c0f4c"); + assertEquals(wxXmlMessage.getPosition(), "产品经理"); + assertEquals(wxXmlMessage.getGender(), Integer.valueOf(1)); + assertEquals(wxXmlMessage.getTelephone(), "020-111111"); + } + + + @Test + public void testRegisterCorp() { + String xml = "\n" + + " \n" + + " \n" + + " 1502682173\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " 1800\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml); + assertEquals(wxXmlMessage.getServiceCorpId(), "wwddddccc7775555aab"); + assertEquals(wxXmlMessage.getInfoType(), "register_corp"); + assertEquals(wxXmlMessage.getRegisterCode(), "pIKi3wRPNWCGF-pyP-YU5KWjDDD"); + assertNotNull(wxXmlMessage.getContactSync()); + assertEquals(wxXmlMessage.getContactSync().getAccessToken(), "accesstoken000001"); + assertEquals(wxXmlMessage.getContactSync().getExpiresIn(), Integer.valueOf(1800)); + assertNotNull(wxXmlMessage.getAuthUserInfo()); + assertEquals(wxXmlMessage.getAuthUserInfo().getUserId(), "zhangshan"); + assertEquals(wxXmlMessage.getTemplateId(), "tpl1test"); + } + + @Test + public void tagNotifyTest() { + String xml = "\n" + + " \n" + + " \n" + + " \n" + + " 1403610513\n" + + " \n" + + " 1\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml); + + assertEquals(wxXmlMessage.getTagId(), Integer.valueOf(1)); + assertNotNull(wxXmlMessage.getAddUserItems()); + assertEquals(wxXmlMessage.getAddUserItems()[0], "zhangsan"); + assertEquals(wxXmlMessage.getAddUserItems()[1], "lisi"); + + assertNotNull(wxXmlMessage.getDelUserItems()); + assertNotNull(wxXmlMessage.getDelUserItems()[0], "zhangsan1"); + assertNotNull(wxXmlMessage.getDelUserItems()[0], "lisi1"); + + assertNotNull(wxXmlMessage.getAddPartyItems()); + assertEquals(wxXmlMessage.getAddPartyItems()[0], Integer.valueOf(1)); + assertEquals(wxXmlMessage.getAddPartyItems()[1], Integer.valueOf(2)); + + } + + + @Test + public void enterAppTest() { + String xml = "\n" + + "\n" + + "1408091189\n" + + "\n" + + "\n" + + "\n" + + "1\n" + + ""; + + WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml); + assertEquals(wxXmlMessage.getToUserName(), "toUser"); + assertEquals(wxXmlMessage.getFromUserName(), "FromUser"); + assertEquals(wxXmlMessage.getCreateTime(), Long.valueOf(1408091189)); + assertEquals(wxXmlMessage.getEvent(), "enter_agent"); + assertEquals(wxXmlMessage.getEventKey(), ""); + assertEquals(wxXmlMessage.getAgentID(), Integer.valueOf(1)); + } + + @Test + public void textMessageTest() { + String xml = "\n" + + " \n" + + " \n" + + " 1348831860\n" + + " \n" + + " \n" + + " 1234567890123456\n" + + " 1\n" + + ""; + + WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml); + assertEquals(wxXmlMessage.getToUserName(), "toUser"); + assertEquals(wxXmlMessage.getFromUserName(), "fromUser"); + assertEquals(wxXmlMessage.getCreateTime(), Long.valueOf(1348831860)); + assertEquals(wxXmlMessage.getMsgType(), "text"); + assertEquals(wxXmlMessage.getMsgId(), "1234567890123456"); + } + + @Test + public void ApprovalInfoTest() { + String xml = "\n" + + " wwddddccc7775555aaa \n" + + " sys \n" + + " 1527838022 \n" + + " event \n" + + " open_approval_change\n" + + " 1\n" + + " \n" + + " 201806010001 \n" + + " 付款 \n" + + " 1234567890 \n" + + " 1 \n" + + " 1527837645 \n" + + " xiaoming \n" + + " 1 \n" + + " 产品部 \n" + + " http://www.qq.com/xxx.png \n" + + " \n" + + " \n" + + " 1 \n" + + " 1 \n" + + " 1 \n" + + " \n" + + " \n" + + " xiaohong \n" + + " 2 \n" + + " http://www.qq.com/xxx.png \n" + + " 1 \n" + + " \n" + + " 0 \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " xiaogang \n" + + " 3 \n" + + " http://www.qq.com/xxx.png \n" + + " \n" + + " \n" + + " 0 \n" + + " \n" + + ""; + + WxCpTpXmlMessage wxXmlMessage = WxCpTpXmlMessage.fromXml(xml); + assertEquals(wxXmlMessage.getToUserName(), "wwddddccc7775555aaa"); + assertEquals(wxXmlMessage.getFromUserName(), "sys"); + assertEquals(wxXmlMessage.getCreateTime(), Long.valueOf(1527838022)); + assertEquals(wxXmlMessage.getEvent(), "open_approval_change"); + + assertNotNull(wxXmlMessage.getApprovalInfo()); + assertEquals(wxXmlMessage.getApprovalInfo().getThirdNo(), Long.valueOf(201806010001L)); + assertEquals(wxXmlMessage.getApprovalInfo().getOpenSpName(), "付款"); + assertEquals(wxXmlMessage.getApprovalInfo().getThirdNo(), Long.valueOf(201806010001L)); + assertEquals(wxXmlMessage.getApprovalInfo().getApplyTime(), Long.valueOf(1527837645)); + assertEquals(wxXmlMessage.getApprovalInfo().getApplyUserName(), "xiaoming"); + + assertNotNull(wxXmlMessage.getApprovalInfo().getApprovalNodes()); + assertNotNull(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0)); + assertEquals(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getNodeAttr(), Integer.valueOf(1)); + assertEquals(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getNodeType(), Integer.valueOf(1)); + + assertNotNull(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getItems()); + assertEquals(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getItems().get(0).getItemName(), "xiaohong"); + assertEquals(wxXmlMessage.getApprovalInfo().getApprovalNodes().get(0).getItems().get(0).getItemOpTime(), Long.valueOf(0)); + + assertNotNull(wxXmlMessage.getApprovalInfo().getNotifyNodes().get(0)); + assertEquals(wxXmlMessage.getApprovalInfo().getNotifyNodes().get(0).getItemImage(), "http://www.qq.com/xxx.png"); + assertEquals(wxXmlMessage.getApprovalInfo().getNotifyNodes().get(0).getItemUserId(), Integer.valueOf(3)); + } +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java similarity index 99% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java index 0a9f17a626..044e364b6d 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlMessageTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlMessageTest.java @@ -1,6 +1,7 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; import me.chanjar.weixin.common.api.WxConsts; +import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; import me.chanjar.weixin.cp.constant.WxCpConsts; import me.chanjar.weixin.cp.util.xml.XStreamTransformer; import org.testng.annotations.Test; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutImageMessageTest.java similarity index 89% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessageTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutImageMessageTest.java index 87c9454c91..0ecbec67dc 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutImageMessageTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutImageMessageTest.java @@ -1,5 +1,7 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutImageMessage; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutNewsMessageTest.java similarity index 95% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessageTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutNewsMessageTest.java index 128bc9a4c6..b0d3efabd4 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutNewsMessageTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutNewsMessageTest.java @@ -1,5 +1,7 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutNewsMessage; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutTextMessageTest.java similarity index 89% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessageTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutTextMessageTest.java index fd09ed6b92..68945f826a 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutTextMessageTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutTextMessageTest.java @@ -1,5 +1,7 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutTextMessage; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVideoMessageTest.java similarity index 91% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessageTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVideoMessageTest.java index c5551dec01..7077ceeede 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVideoMessageTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVideoMessageTest.java @@ -1,5 +1,7 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutVideoMessage; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessageTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVoiceMessageTest.java similarity index 89% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessageTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVoiceMessageTest.java index a3c9688c44..9c03486001 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/WxCpXmlOutVoiceMessageTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/message/WxCpXmlOutVoiceMessageTest.java @@ -1,5 +1,7 @@ -package me.chanjar.weixin.cp.bean; +package me.chanjar.weixin.cp.bean.message; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutVoiceMessage; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/calendar/WxCpOaCalendarTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/calendar/WxCpOaCalendarTest.java new file mode 100644 index 0000000000..761b0f8f9a --- /dev/null +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/bean/oa/calendar/WxCpOaCalendarTest.java @@ -0,0 +1,52 @@ +package me.chanjar.weixin.cp.bean.oa.calendar; + +import me.chanjar.weixin.common.util.json.GsonParser; +import org.testng.annotations.Test; + +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * 单元测试. + * + * @author Binary Wang + * @date 2020-09-20 + */ +public class WxCpOaCalendarTest { + + @Test + public void testToJson() { + String json = "{\n" + + " \"calendar\" : {\n" + + " \"organizer\" : \"userid1\",\n" + + " \"readonly\" : 1,\n" + + " \"set_as_default\" : 1,\n" + + " \"summary\" : \"test_summary\",\n" + + " \"color\" : \"#FF3030\",\n" + + " \"description\" : \"test_describe\",\n" + + " \"shares\" : [\n" + + " {\n" + + " \"userid\" : \"userid2\"\n" + + " },\n" + + " {\n" + + " \"userid\" : \"userid3\",\n" + + " \"readonly\" : 1\n" + + " }\n" + + " ]\n" + + " }\n" + + "}\n"; + + assertThat(WxCpOaCalendar.builder() + .organizer("userid1") + .readonly(1) + .setAsDefault(1) + .summary("test_summary") + .color("#FF3030") + .description("test_describe") + .shares(Arrays.asList(new WxCpOaCalendar.ShareInfo("userid2", null), + new WxCpOaCalendar.ShareInfo("userid3", 1))) + .build().toJson()) + .isEqualTo(GsonParser.parse(json).toString()); + } +} diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java index df656a68a8..52bc8e2ab7 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpDemoServer.java @@ -1,24 +1,19 @@ package me.chanjar.weixin.cp.demo; -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletHandler; -import org.eclipse.jetty.servlet.ServletHolder; - -import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.common.session.WxSessionManager; -import me.chanjar.weixin.cp.constant.WxCpConsts; import me.chanjar.weixin.cp.api.WxCpService; import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl; -import me.chanjar.weixin.cp.bean.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; -import me.chanjar.weixin.cp.bean.WxCpXmlOutTextMessage; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutTextMessage; import me.chanjar.weixin.cp.config.WxCpConfigStorage; +import me.chanjar.weixin.cp.constant.WxCpConsts; import me.chanjar.weixin.cp.message.WxCpMessageHandler; import me.chanjar.weixin.cp.message.WxCpMessageRouter; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletHandler; +import org.eclipse.jetty.servlet.ServletHolder; + +import java.io.IOException; +import java.io.InputStream; public class WxCpDemoServer { @@ -54,30 +49,20 @@ private static void initWeixin() throws IOException { wxCpService = new WxCpServiceImpl(); wxCpService.setWxCpConfigStorage(config); - WxCpMessageHandler handler = new WxCpMessageHandler() { - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, - Map context, WxCpService wxService, - WxSessionManager sessionManager) { - WxCpXmlOutTextMessage m = WxCpXmlOutMessage.TEXT().content("测试加密消息") - .fromUser(wxMessage.getToUserName()) - .toUser(wxMessage.getFromUserName()).build(); - return m; - } + WxCpMessageHandler handler = (wxMessage, context, wxService, sessionManager) -> { + WxCpXmlOutTextMessage m = WxCpXmlOutMessage.TEXT().content("测试加密消息") + .fromUser(wxMessage.getToUserName()) + .toUser(wxMessage.getFromUserName()).build(); + return m; }; - WxCpMessageHandler oauth2handler = new WxCpMessageHandler() { - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, - Map context, WxCpService wxService, - WxSessionManager sessionManager) { - String href = "测试oauth2"; - return WxCpXmlOutMessage.TEXT().content(href) - .fromUser(wxMessage.getToUserName()) - .toUser(wxMessage.getFromUserName()).build(); - } + WxCpMessageHandler oauth2handler = (wxMessage, context, wxService, sessionManager) -> { + String href = "测试oauth2"; + return WxCpXmlOutMessage.TEXT().content(href) + .fromUser(wxMessage.getToUserName()) + .toUser(wxMessage.getFromUserName()).build(); }; wxCpMessageRouter = new WxCpMessageRouter(wxCpService); @@ -93,12 +78,9 @@ public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, .end() .rule() .event(WxCpConsts.EventType.CHANGE_CONTACT) - .handler(new WxCpMessageHandler() { - @Override - public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map context, WxCpService wxCpService, WxSessionManager sessionManager) throws WxErrorException { - System.out.println("通讯录发生变更"); - return null; - } + .handler((wxMessage, context, wxCpService, sessionManager) -> { + System.out.println("通讯录发生变更"); + return null; }) .end(); diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpEndpointServlet.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpEndpointServlet.java index 291cef403d..a5e785ffdf 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpEndpointServlet.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/demo/WxCpEndpointServlet.java @@ -3,8 +3,8 @@ import me.chanjar.weixin.cp.config.WxCpConfigStorage; import me.chanjar.weixin.cp.message.WxCpMessageRouter; import me.chanjar.weixin.cp.api.WxCpService; -import me.chanjar.weixin.cp.bean.WxCpXmlMessage; -import me.chanjar.weixin.cp.bean.WxCpXmlOutMessage; +import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage; +import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage; import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; import org.apache.commons.lang3.StringUtils; @@ -61,7 +61,6 @@ protected void service(HttpServletRequest request, HttpServletResponse response) response.getWriter().write(outMessage.toEncryptedXml(this.wxCpConfigStorage)); } - return; } } diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java similarity index 91% rename from weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImplTest.java rename to weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java index 9f79735612..83ace79f3c 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/api/impl/BaseWxCpTpServiceImplTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java @@ -1,20 +1,20 @@ -package me.chanjar.weixin.cp.api.impl; +package me.chanjar.weixin.cp.tp.service.impl; import com.google.gson.JsonObject; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.cp.api.WxCpTpService; 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; import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl; +import me.chanjar.weixin.cp.tp.service.WxCpTpService; +import org.mockito.Mockito; +import org.testng.Assert; import org.testng.annotations.Test; import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_AUTH_INFO; import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.Tp.GET_PERMANENT_CODE; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; -import static org.testng.Assert.*; /** * 测试代码. @@ -23,7 +23,7 @@ * @date 2019-08-18 */ public class BaseWxCpTpServiceImplTest { - private WxCpTpService tpService = spy(new WxCpTpServiceImpl()); + private final WxCpTpService tpService = Mockito.spy(new WxCpTpServiceImpl()); @Test public void testCheckSignature() { @@ -123,7 +123,7 @@ public void testGetPermanentCode() throws WxErrorException { JsonObject jsonObject = new JsonObject(); String authCode = ""; jsonObject.addProperty("auth_code", authCode); - doReturn(returnJson).when(tpService).post(configStorage.getApiUrl(GET_PERMANENT_CODE), jsonObject.toString()); + Mockito.doReturn(returnJson).when(tpService).post(configStorage.getApiUrl(GET_PERMANENT_CODE), jsonObject.toString()); final WxCpTpCorp tpCorp = tpService.getPermanentCode(authCode); assertThat(tpCorp.getPermanentCode()).isEqualTo("xxxx"); @@ -134,7 +134,7 @@ public void testGetPermanentCode() throws WxErrorException { } @Test - public void testGetPermanentCodeInfo() throws WxErrorException{ + public void testGetPermanentCodeInfo() throws WxErrorException { String returnJson = "{\n" + " \"access_token\": \"u6SoEWyrEmworJ1uNzddbiXh42mCLNU_mdd6b01Afo2LKmyi-WdaaYqhEGFZjB1RGZ-rhjLcAJ86ger7b7Q0gowSw9iIDR8dm49aVH_MztzmQttP3XFG7np1Dxs_VQkVwhhRmfRpEonAmK1_JWIFqayJXXiPUS3LsFd3tWpE7rxmsRa7Ev2ml2htbRp_qGUjtFTErKoDsnNGSka6_RqFPA\", \n" + " \"expires_in\": 7200, \n" + @@ -187,15 +187,15 @@ public void testGetPermanentCodeInfo() throws WxErrorException{ JsonObject jsonObject = new JsonObject(); String authCode = ""; jsonObject.addProperty("auth_code", authCode); - doReturn(returnJson).when(tpService).post(configStorage.getApiUrl(GET_PERMANENT_CODE), jsonObject.toString()); + Mockito.doReturn(returnJson).when(tpService).post(configStorage.getApiUrl(GET_PERMANENT_CODE), jsonObject.toString()); final WxCpTpPermanentCodeInfo tpPermanentCodeInfo = tpService.getPermanentCodeInfo(authCode); assertThat(tpPermanentCodeInfo.getAuthInfo().getAgents().get(0).getAgentId()).isEqualTo(1000012); - assertNotNull(tpPermanentCodeInfo.getAuthInfo().getAgents().get(0).getSquareLogoUrl()); - assertNotNull(tpPermanentCodeInfo.getAuthCorpInfo().getCorpSquareLogoUrl()); + Assert.assertNotNull(tpPermanentCodeInfo.getAuthInfo().getAgents().get(0).getSquareLogoUrl()); + Assert.assertNotNull(tpPermanentCodeInfo.getAuthCorpInfo().getCorpSquareLogoUrl()); } @Test - public void testGetAuthInfo() throws WxErrorException{ + public void testGetAuthInfo() throws WxErrorException { String returnJson = "{\n" + " \"errcode\":0 ,\n" + " \"errmsg\":\"ok\" ,\n" + @@ -260,9 +260,9 @@ public void testGetAuthInfo() throws WxErrorException{ String permanentCode = "xxxxx"; jsonObject.addProperty("auth_corpid", authCorpId); jsonObject.addProperty("permanent_code", permanentCode); - doReturn(returnJson).when(tpService).post(configStorage.getApiUrl(GET_AUTH_INFO), jsonObject.toString()); - WxCpTpAuthInfo authInfo = tpService.getAuthInfo(authCorpId,permanentCode); - assertNotNull(authInfo.getAuthCorpInfo().getCorpId()); + Mockito.doReturn(returnJson).when(tpService).post(configStorage.getApiUrl(GET_AUTH_INFO), jsonObject.toString()); + WxCpTpAuthInfo authInfo = tpService.getAuthInfo(authCorpId, permanentCode); + Assert.assertNotNull(authInfo.getAuthCorpInfo().getCorpId()); } @Test diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java index d78175c1b8..a83e8837d9 100644 --- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java +++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/util/json/WxCpUserGsonAdapterTest.java @@ -91,7 +91,7 @@ public void testDeserialize() { assertThat(user).isNotNull(); assertThat(user.getOrders()).isNotEmpty(); - assertThat(user.getOrders().length).isEqualTo(2); + assertThat(user.getOrders()).hasSize(2); assertThat(user.getOrders()[0]).isEqualTo(1); assertThat(user.getOrders()[1]).isEqualTo(2); @@ -140,6 +140,12 @@ public void testDeserialize() { public void testSerialize() { WxCpUser user = new WxCpUser(); user.setOrders(new Integer[]{1, 2}); + user.addExtAttr(WxCpUser.Attr.builder() + .type(0) + .name("文本名称") + .textValue("文本") + .build()); + user.addExternalAttr(WxCpUser.ExternalAttribute.builder() .type(0) .name("文本名称") @@ -159,7 +165,9 @@ public void testSerialize() { .title("my miniprogram") .build()); - assertThat(user.toJson()).isEqualTo("{\"order\":[1,2],\"external_profile\":{\"external_attr\":" + + assertThat(user.toJson()).isEqualTo("{\"order\":[1,2]," + + "\"extattr\":{\"attrs\":[{\"type\":0,\"name\":\"文本名称\",\"text\":{\"value\":\"文本\"}}]}," + + "\"external_profile\":{\"external_attr\":" + "[{\"type\":0,\"name\":\"文本名称\",\"text\":{\"value\":\"文本\"}}," + "{\"type\":1,\"name\":\"网页名称\",\"web\":{\"url\":\"http://www.test.com\",\"title\":\"标题\"}}," + "{\"type\":2,\"name\":\"测试app\"," + diff --git a/weixin-java-cp/src/test/resources/moco/message.json b/weixin-java-cp/src/test/resources/moco/message.json new file mode 100644 index 0000000000..b6b333c114 --- /dev/null +++ b/weixin-java-cp/src/test/resources/moco/message.json @@ -0,0 +1,26 @@ +[ + { + "request": { + "uri": "/cgi-bin/gettoken" + }, + "response": { + "text": "{\"errcode\":0,\"errmsg\":\"ok\",\"access_token\":\"oG1MrhLSzGBl4YxM1W2EHJlL_5vAotNwQ6KBp98sP2fO8XGPPRUlWS9w98CKjxSgPx4YnTy0DU_DvmNXAwt3mSDJ1Uhg_WCFrxX8GWbbCRlzrj2csK-1Y3tzI6dBCMa2YmblBo2sX7qkkzc9pnjP38GzO7Yuo_Bbpyi4doilNWZme0z9ovwiBCkAtV7DXYuh14EsnNrODG454kstOxsqWA\",\"expires_in\":7200}" + } + }, + { + "request": { + "uri": "/cgi-bin/message/send" + }, + "response": { + "text": "{\"errcode\":0,\"errmsg\":\"ok\",\"invaliduser\":\"\"}" + } + }, + { + "request": { + "uri": "/cgi-bin/linkedcorp/message/send" + }, + "response": { + "text": "{\"errcode\":0,\"errmsg\":\"ok\",\"invaliduser\":\"\"}" + } + } +] diff --git a/weixin-java-cp/src/test/resources/testng.xml b/weixin-java-cp/src/test/resources/testng.xml index 563928bdf0..cfccea89b7 100644 --- a/weixin-java-cp/src/test/resources/testng.xml +++ b/weixin-java-cp/src/test/resources/testng.xml @@ -12,13 +12,13 @@ - - - - - - - + + + + + + + diff --git a/weixin-java-miniapp/pom.xml b/weixin-java-miniapp/pom.xml index 16d56b9307..02bbaf21a8 100644 --- a/weixin-java-miniapp/pom.xml +++ b/weixin-java-miniapp/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 3.9.0 + 4.0.0 weixin-java-miniapp @@ -126,7 +126,7 @@ 3.5.1 - cn.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor + com.github.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaLiveGoodsService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaLiveGoodsService.java index f52f9efde2..882627dd67 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaLiveGoodsService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaLiveGoodsService.java @@ -1,7 +1,7 @@ package cn.binarywang.wx.miniapp.api; -import cn.binarywang.wx.miniapp.bean.WxMaLiveInfo; -import cn.binarywang.wx.miniapp.bean.WxMaLiveResult; +import cn.binarywang.wx.miniapp.bean.live.WxMaLiveGoodInfo; +import cn.binarywang.wx.miniapp.bean.live.WxMaLiveResult; import me.chanjar.weixin.common.error.WxErrorException; import java.util.List; @@ -37,7 +37,7 @@ public interface WxMaLiveGoodsService { * @return 返回auditId、goodsId * @throws WxErrorException . */ - WxMaLiveResult addGoods(WxMaLiveInfo.Goods goods) throws WxErrorException; + WxMaLiveResult addGoods(WxMaLiveGoodInfo goods) throws WxErrorException; /** * 撤回审核 @@ -91,7 +91,7 @@ public interface WxMaLiveGoodsService { * @return 更新商品是否成功 * @throws WxErrorException . */ - boolean updateGoods(WxMaLiveInfo.Goods goods) throws WxErrorException; + boolean updateGoods(WxMaLiveGoodInfo goods) throws WxErrorException; /** * 获取商品状态 diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaLiveService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaLiveService.java index 185085478e..78cb4d497d 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaLiveService.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaLiveService.java @@ -1,7 +1,6 @@ package cn.binarywang.wx.miniapp.api; -import cn.binarywang.wx.miniapp.bean.WxMaLiveInfo; -import cn.binarywang.wx.miniapp.bean.WxMaLiveResult; +import cn.binarywang.wx.miniapp.bean.live.*; import me.chanjar.weixin.common.error.WxErrorException; import java.util.List; @@ -18,6 +17,14 @@ public interface WxMaLiveService { String GET_LIVE_INFO = "https://api.weixin.qq.com/wxa/business/getliveinfo"; String CREATE_ROOM = "https://api.weixin.qq.com/wxaapi/broadcast/room/create"; String ADD_GOODS = "https://api.weixin.qq.com/wxaapi/broadcast/room/addgoods"; + String DELETE_ROOM = "https://api.weixin.qq.com/wxaapi/broadcast/room/deleteroom"; + String EDIT_ROOM = "https://api.weixin.qq.com/wxaapi/broadcast/room/editroom"; + String GET_PUSH_URL = "https://api.weixin.qq.com/wxaapi/broadcast/room/getpushurl"; + String GET_SHARED_CODE = "https://api.weixin.qq.com/wxaapi/broadcast/room/getsharedcode"; + String ADD_ASSISTANT = "https://api.weixin.qq.com/wxaapi/broadcast/room/addassistant"; + String MODIFY_ASSISTANT = "https://api.weixin.qq.com/wxaapi/broadcast/room/modifyassistant"; + String REMOVE_ASSISTANT = "https://api.weixin.qq.com/wxaapi/broadcast/room/removeassistant"; + String GET_ASSISTANT_LIST = "https://api.weixin.qq.com/wxaapi/broadcast/room/getassistantlist"; /** * 创建直播间 @@ -31,8 +38,63 @@ public interface WxMaLiveService { * @return . * @throws WxErrorException . */ - Integer createRoom(WxMaLiveInfo.RoomInfo roomInfo) throws WxErrorException; + WxMaCreateRoomResult createRoom(WxMaLiveRoomInfo roomInfo) throws WxErrorException; + /** + * 删除直播间 + *
+   * 调用额度:10000次/一天
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/framework/liveplayer/studio-api.html#5
+   * http请求方式:POST https://api.weixin.qq.com/wxaapi/broadcast/room/deleteroom?access_token=ACCESS_TOKEN
+   * 
+ * + * @param roomId 直播间id + * @return . + * @throws WxErrorException . + */ + boolean deleteRoom(Integer roomId) throws WxErrorException; + + /** + * 编辑直播间 + *
+   * 调用此接口编辑直播间,调用额度:10000次/一天
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/framework/liveplayer/studio-api.html#6
+   * http请求方式:POST https://api.weixin.qq.com/wxaapi/broadcast/room/editroom?access_token=ACCESS_TOKEN
+   * 
+ * + * @param roomInfo 直播间信息 + * @return . + * @throws WxErrorException . + */ + boolean editRoom(WxMaLiveRoomInfo roomInfo) throws WxErrorException; + + /** + * 获取直播间推流地址 + *
+   * 调用额度:10000次/一天
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/framework/liveplayer/studio-api.html#7
+   * http请求方式:GET https://api.weixin.qq.com/wxaapi/broadcast/room/getpushurl?access_token=ACCESS_TOKEN
+   * 
+ * + * @param roomId 直播间id + * @return . + * @throws WxErrorException . + */ + String getPushUrl(Integer roomId) throws WxErrorException; + + /** + * 获取直播间分享二维码 + *
+   * 调用额度:10000次/一天
+   * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/framework/liveplayer/studio-api.html#8
+   * http请求方式:GET https://api.weixin.qq.com/wxaapi/broadcast/room/getsharedcode?access_token=ACCESS_TOKEN
+   * 
+ * + * @param roomId 直播间id + * @return . + * @throws WxErrorException . + */ + String getSharedCode(Integer roomId, String params) throws WxErrorException; /** * 获取直播房间列表.(分页) * @@ -91,4 +153,64 @@ public interface WxMaLiveService { * @throws WxErrorException . */ boolean addGoodsToRoom(Integer roomId, List goodsIds) throws WxErrorException; + /** + * 添加管理直播间小助手 + *

+ * 调用接口往指定直播间添加管理直播间小助手 + * 调用频率 + * 调用额度:10000次/一天 + *

+ * http请求方式:POST https://api.weixin.qq.com/wxaapi/broadcast/room/addassistant?access_token=ACCESS_TOKEN + *

+   * @param roomId 房间ID
+   * @param users 数组列表,可传入多个,"users": [{"username":"testwechat","nickname":"testnick"}]
+   * @return 添加管理直播间小助手是否成功
+   * @throws WxErrorException .
+   */
+  boolean addAssistant(Integer roomId, List users) throws WxErrorException;
+  /**
+   * 修改直播间小助手昵称
+   * 

+ * 调用接口修改直播间小助手昵称 + * 调用频率 + * 调用额度:10000次/一天 + *

+ * http请求方式:POST https://api.weixin.qq.com/wxaapi/broadcast/room/modifyassistant?access_token=ACCESS_TOKEN + *

+   * @param roomId 房间ID
+   * @param username 小助手微信号
+   * @param nickname 小助手直播间昵称
+   * @return 修改小助手昵称是否成功
+   * @throws WxErrorException .
+   */
+  boolean modifyAssistant(Integer roomId, String username,String nickname) throws WxErrorException;
+  /**
+   * 删除直播间小助手
+   * 

+ * 删除直播间小助手 + * 调用频率 + * 调用额度:10000次/一天 + *

+ * http请求方式:POST https://api.weixin.qq.com/wxaapi/broadcast/room/removeassistant?access_token=ACCESS_TOKEN + *

+   * @param roomId 房间ID
+   * @param username 小助手微信号
+   * @return 删除小助手昵称是否成功
+   * @throws WxErrorException .
+   */
+  boolean removeAssistant(Integer roomId, String username) throws WxErrorException;
+  /**
+   * 查询直播间小助手
+   * 

+ * 查询直播间小助手 + * 调用频率 + * 调用额度:10000次/一天 + *

+ * http请求方式:POST https://api.weixin.qq.com/wxaapi/broadcast/room/getassistantlist?access_token=ACCESS_TOKEN + *

+   * @param roomId 房间ID
+   * @return 小助手列表
+   * @throws WxErrorException .
+   */
+  List getAssistantList(Integer roomId) throws WxErrorException;
 }
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaQrcodeService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaQrcodeService.java
index f2c9e8f1dc..25e63e6269 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaQrcodeService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaQrcodeService.java
@@ -1,10 +1,10 @@
 package cn.binarywang.wx.miniapp.api;
 
-import java.io.File;
-
 import cn.binarywang.wx.miniapp.bean.WxMaCodeLineColor;
 import me.chanjar.weixin.common.error.WxErrorException;
 
+import java.io.File;
+
 /**
  * 
  * 二维码相关操作接口.
@@ -37,6 +37,23 @@ public interface WxMaQrcodeService {
    */
   byte[] createQrcodeBytes(String path, int width) throws WxErrorException;
 
+  /**
+   * 接口C: 获取小程序页面二维码.
+   * 
+   * 适用于需要的码数量较少的业务场景
+   * 通过该接口,仅能生成已发布的小程序的二维码。
+   * 可以在开发者工具预览时生成开发版的带参二维码。
+   * 带参二维码只有 100000 个,请谨慎调用。
+   * 
+ * + * @param path 不能为空,最大长度 128 字节 + * @param width 默认430 二维码的宽度 + * @param filePath 二维码生成的文件路径,例如: /var/temp + * @return 文件对象 + * @throws WxErrorException 异常 + */ + File createQrcode(String path, int width, String filePath) throws WxErrorException; + /** * 接口C: 获取小程序页面二维码. *
@@ -53,6 +70,22 @@ public interface WxMaQrcodeService {
    */
   File createQrcode(String path, int width) throws WxErrorException;
 
+  /**
+   * 接口C: 获取小程序页面二维码.
+   * 
+   * 适用于需要的码数量较少的业务场景
+   * 通过该接口,仅能生成已发布的小程序的二维码。
+   * 可以在开发者工具预览时生成开发版的带参二维码。
+   * 带参二维码只有 100000 个,请谨慎调用。
+   * 
+ * + * @param path 不能为空,最大长度 128 字节 + * @param filePath 二维码生成的文件路径,例如: /var/temp + * @return 文件对象 + * @throws WxErrorException 异常 + */ + File createQrcode(String path, String filePath) throws WxErrorException; + /** * 接口C: 获取小程序页面二维码. *
@@ -74,7 +107,7 @@ public interface WxMaQrcodeService {
    * @param path      不能为空,最大长度 128 字节
    * @param width     默认430 二维码的宽度
    * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调
-   * @param lineColor auth_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"}
+   * @param lineColor autoColor 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"}
    * @param isHyaline 是否需要透明底色, isHyaline 为true时,生成透明底色的小程序码
    * @return 文件内容字节数组
    * @throws WxErrorException 异常
@@ -82,13 +115,28 @@ public interface WxMaQrcodeService {
   byte[] createWxaCodeBytes(String path, int width, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline)
     throws WxErrorException;
 
+  /**
+   * 接口A: 获取小程序码.
+   *
+   * @param path      不能为空,最大长度 128 字节
+   * @param width     默认430 二维码的宽度
+   * @param filePath  二维码生成的文件路径,例如: /var/temp
+   * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调
+   * @param lineColor autoColor 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"}
+   * @param isHyaline 是否需要透明底色, isHyaline 为true时,生成透明底色的小程序码
+   * @return 文件对象
+   * @throws WxErrorException 异常
+   */
+  File createWxaCode(String path, int width, String filePath, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline)
+    throws WxErrorException;
+
   /**
    * 接口A: 获取小程序码.
    *
    * @param path      不能为空,最大长度 128 字节
    * @param width     默认430 二维码的宽度
    * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调
-   * @param lineColor auth_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"}
+   * @param lineColor autoColor 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"}
    * @param isHyaline 是否需要透明底色, isHyaline 为true时,生成透明底色的小程序码
    * @return 文件对象
    * @throws WxErrorException 异常
@@ -96,6 +144,17 @@ byte[] createWxaCodeBytes(String path, int width, boolean autoColor, WxMaCodeLin
   File createWxaCode(String path, int width, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline)
     throws WxErrorException;
 
+  /**
+   * 接口A: 获取小程序码.
+   *
+   * @param path     不能为空,最大长度 128 字节
+   * @param width    默认430 二维码的宽度
+   * @param filePath 二维码生成的文件路径,例如: /var/temp
+   * @return 文件对象
+   * @throws WxErrorException 异常
+   */
+  File createWxaCode(String path, int width, String filePath) throws WxErrorException;
+
   /**
    * 接口A: 获取小程序码.
    *
@@ -106,6 +165,16 @@ File createWxaCode(String path, int width, boolean autoColor, WxMaCodeLineColor
    */
   File createWxaCode(String path, int width) throws WxErrorException;
 
+  /**
+   * 接口A: 获取小程序码.
+   *
+   * @param path     不能为空,最大长度 128 字节
+   * @param filePath 二维码生成的文件路径,例如: /var/temp
+   * @return 文件对象
+   * @throws WxErrorException 异常
+   */
+  File createWxaCode(String path, String filePath) throws WxErrorException;
+
   /**
    * 接口A: 获取小程序码.
    *
@@ -129,7 +198,7 @@ File createWxaCode(String path, int width, boolean autoColor, WxMaCodeLineColor
    * @param page      必须是已经发布的小程序页面,例如 "pages/index/index" ,如果不填写这个字段,默认跳主页面
    * @param width     默认false 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调
    * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调
-   * @param lineColor auth_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"}
+   * @param lineColor autoColor 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"}
    * @param isHyaline 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码
    * @return 文件内容字节数组
    * @throws WxErrorException 异常
@@ -137,6 +206,29 @@ File createWxaCode(String path, int width, boolean autoColor, WxMaCodeLineColor
   byte[] createWxaCodeUnlimitBytes(String scene, String page, int width, boolean autoColor,
                                    WxMaCodeLineColor lineColor, boolean isHyaline) throws WxErrorException;
 
+  /**
+   * 接口B: 获取小程序码(永久有效、数量暂无限制).
+   * 
+   * 通过该接口生成的小程序码,永久有效,数量暂无限制。
+   * 用户扫描该码进入小程序后,将统一打开首页,开发者需在对应页面根据获取的码中 scene 字段的值,再做处理逻辑。
+   * 使用如下代码可以获取到二维码中的 scene 字段的值。
+   * 调试阶段可以使用开发工具的条件编译自定义参数 scene=xxxx 进行模拟,开发工具模拟时的 scene 的参数值需要进行 urlencode
+   * 
+ * + * @param scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~, + * 其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) + * @param page 必须是已经发布的小程序页面,例如 "pages/index/index" ,如果不填写这个字段,默认跳主页面 + * @param filePath 二维码生成的文件路径,例如: /var/temp + * @param width 默认false 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 + * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 + * @param lineColor autoColor 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"} + * @param isHyaline 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 + * @return 文件对象 + * @throws WxErrorException 异常 + */ + File createWxaCodeUnlimit(String scene, String page, String filePath, int width, boolean autoColor, + WxMaCodeLineColor lineColor, boolean isHyaline) throws WxErrorException; + /** * 接口B: 获取小程序码(永久有效、数量暂无限制). *
@@ -151,7 +243,7 @@ byte[] createWxaCodeUnlimitBytes(String scene, String page, int width, boolean a
    * @param page      必须是已经发布的小程序页面,例如 "pages/index/index" ,如果不填写这个字段,默认跳主页面
    * @param width     默认false 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调
    * @param autoColor 默认true 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调
-   * @param lineColor auth_color 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"}
+   * @param lineColor autoColor 为 false 时生效,使用 rgb 设置颜色 例如 {"r":"xxx","g":"xxx","b":"xxx"}
    * @param isHyaline 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码
    * @return 文件对象
    * @throws WxErrorException 异常
@@ -159,6 +251,24 @@ byte[] createWxaCodeUnlimitBytes(String scene, String page, int width, boolean a
   File createWxaCodeUnlimit(String scene, String page, int width, boolean autoColor,
                             WxMaCodeLineColor lineColor, boolean isHyaline) throws WxErrorException;
 
+  /**
+   * 接口B: 获取小程序码(永久有效、数量暂无限制).
+   * 
+   * 通过该接口生成的小程序码,永久有效,数量暂无限制。
+   * 用户扫描该码进入小程序后,将统一打开首页,开发者需在对应页面根据获取的码中 scene 字段的值,再做处理逻辑。
+   * 使用如下代码可以获取到二维码中的 scene 字段的值。
+   * 调试阶段可以使用开发工具的条件编译自定义参数 scene=xxxx 进行模拟,开发工具模拟时的 scene 的参数值需要进行 urlencode
+   * 
+ * + * @param scene 最大32个可见字符,只支持数字,大小写英文以及部分特殊字符:!#$&'()*+,/:;=?@-._~, + * 其它字符请自行编码为合法字符(因不支持%,中文无法使用 urlencode 处理,请使用其他编码方式) + * @param page 必须是已经发布的小程序页面,例如 "pages/index/index" ,如果不填写这个字段,默认跳主页面 + * @param filePath 二维码生成的文件路径,例如: /var/temp + * @return 文件对象 + * @throws WxErrorException 异常 + */ + File createWxaCodeUnlimit(String scene, String page, String filePath) throws WxErrorException; + /** * 接口B: 获取小程序码(永久有效、数量暂无限制). *
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
index 13862b12fd..e79e3cad36 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/WxMaService.java
@@ -2,8 +2,8 @@
 
 import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
-import me.chanjar.weixin.common.api.WxImgProcService;
-import me.chanjar.weixin.common.api.WxOcrService;
+import me.chanjar.weixin.common.service.WxImgProcService;
+import me.chanjar.weixin.common.service.WxOcrService;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.service.WxService;
 import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
index 7bd1eec748..bb189ce471 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/BaseWxMaServiceImpl.java
@@ -11,12 +11,14 @@
 import com.google.gson.JsonObject;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.common.api.WxImgProcService;
-import me.chanjar.weixin.common.api.WxOcrService;
+import me.chanjar.weixin.common.service.WxImgProcService;
+import me.chanjar.weixin.common.service.WxOcrService;
+import me.chanjar.weixin.common.bean.ToJson;
 import me.chanjar.weixin.common.bean.WxAccessToken;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.DataUtils;
 import me.chanjar.weixin.common.util.crypto.SHA1;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
@@ -155,7 +157,7 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
       String response = doGetAccessTokenRequest();
       return extractAccessToken(response);
     } catch (IOException | InterruptedException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     } finally {
       if (locked) {
         lock.unlock();
@@ -185,7 +187,15 @@ public String post(String url, String postData) throws WxErrorException {
   public String post(String url, Object obj) throws WxErrorException {
     return this.execute(SimplePostRequestExecutor.create(this), url, WxGsonBuilder.create().toJson(obj));
   }
+  @Override
+  public String post(String url, ToJson obj) throws WxErrorException {
+    return this.post(url, obj.toJson());
+  }
 
+  @Override
+  public String post(String url, JsonObject jsonObject) throws WxErrorException {
+    return this.post(url, jsonObject.toString());
+  }
   /**
    * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求
    */
@@ -222,7 +232,7 @@ public  T execute(RequestExecutor executor, String uri, E data) thro
     } while (retryTimes++ < this.maxRetryTimes);
 
     log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
-    throw new RuntimeException("微信服务端异常,超出重试次数");
+    throw new WxRuntimeException("微信服务端异常,超出重试次数");
   }
 
   private  T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException {
@@ -267,7 +277,7 @@ private  T executeInternal(RequestExecutor executor, String uri, E d
       return null;
     } catch (IOException e) {
       log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage());
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
   }
 
@@ -354,7 +364,7 @@ public WxMaService switchoverTo(String miniappId) {
       return this;
     }
 
-    throw new RuntimeException(String.format("无法找到对应【%s】的小程序配置信息,请核实!", miniappId));
+    throw new WxRuntimeException(String.format("无法找到对应【%s】的小程序配置信息,请核实!", miniappId));
   }
 
   @Override
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaAnalysisServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaAnalysisServiceImpl.java
index db69642714..b0f1606593 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaAnalysisServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaAnalysisServiceImpl.java
@@ -8,7 +8,7 @@
 import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitDistribution;
 import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitPage;
 import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitTrend;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.JsonObject;
 import com.google.gson.reflect.TypeToken;
 import lombok.AllArgsConstructor;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCloudServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCloudServiceImpl.java
index 300ded88fe..c4058e1523 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCloudServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCloudServiceImpl.java
@@ -5,7 +5,7 @@
 import cn.binarywang.wx.miniapp.bean.cloud.*;
 import cn.binarywang.wx.miniapp.constant.WxMaConstants;
 import cn.binarywang.wx.miniapp.util.JoinerUtils;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.gson.JsonArray;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java
index 2d965b4c45..4d73f6aa16 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaCodeServiceImpl.java
@@ -19,7 +19,7 @@
 import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest;
 import cn.binarywang.wx.miniapp.bean.code.WxMaCodeSubmitAuditRequest;
 import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.JsonObject;
 import com.google.gson.reflect.TypeToken;
 import me.chanjar.weixin.common.error.WxError;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImpl.java
index 21d0bfe0b0..ab2d23c9dc 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImpl.java
@@ -8,7 +8,7 @@
 import cn.binarywang.wx.miniapp.bean.express.WxMaExpressPrinter;
 import cn.binarywang.wx.miniapp.bean.express.request.*;
 import cn.binarywang.wx.miniapp.bean.express.result.WxMaExpressOrderInfoResult;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.AllArgsConstructor;
 import me.chanjar.weixin.common.error.WxErrorException;
 
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImgProcServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImgProcServiceImpl.java
index 0499b7c7e0..4bdb061675 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImgProcServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaImgProcServiceImpl.java
@@ -2,7 +2,7 @@
 
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import lombok.RequiredArgsConstructor;
-import me.chanjar.weixin.common.api.WxImgProcService;
+import me.chanjar.weixin.common.service.WxImgProcService;
 import me.chanjar.weixin.common.bean.imgproc.WxImgProcAiCropResult;
 import me.chanjar.weixin.common.bean.imgproc.WxImgProcQrCodeResult;
 import me.chanjar.weixin.common.bean.imgproc.WxImgProcSuperResolutionResult;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaJsapiServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaJsapiServiceImpl.java
index 9177910e36..43d3c22d1e 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaJsapiServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaJsapiServiceImpl.java
@@ -32,22 +32,25 @@ public String getCardApiTicket() throws WxErrorException {
 
   @Override
   public String getCardApiTicket(boolean forceRefresh) throws WxErrorException {
-    Lock lock = this.wxMaService.getWxMaConfig().getCardApiTicketLock();
-    lock.lock();
-    try {
-      if (forceRefresh) {
-        this.wxMaService.getWxMaConfig().expireCardApiTicket();
-      }
 
-      if (this.wxMaService.getWxMaConfig().isCardApiTicketExpired()) {
-        String responseContent = this.wxMaService.get(GET_JSAPI_TICKET_URL + "?type=wx_card", null);
-        JsonObject tmpJsonObject = GsonParser.parse(responseContent);
-        String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
-        int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
-        this.wxMaService.getWxMaConfig().updateCardApiTicket(jsapiTicket, expiresInSeconds);
+    if (forceRefresh) {
+      this.wxMaService.getWxMaConfig().expireCardApiTicket();
+    }
+
+    if (this.wxMaService.getWxMaConfig().isCardApiTicketExpired()) {
+      Lock lock = this.wxMaService.getWxMaConfig().getCardApiTicketLock();
+      lock.lock();
+      try {
+        if (this.wxMaService.getWxMaConfig().isCardApiTicketExpired()) {
+          String responseContent = this.wxMaService.get(GET_JSAPI_TICKET_URL + "?type=wx_card", null);
+          JsonObject tmpJsonObject = GsonParser.parse(responseContent);
+          String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
+          int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
+          this.wxMaService.getWxMaConfig().updateCardApiTicket(jsapiTicket, expiresInSeconds);
+        }
+      } finally {
+        lock.unlock();
       }
-    } finally {
-      lock.unlock();
     }
     return this.wxMaService.getWxMaConfig().getCardApiTicket();
   }
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImpl.java
index 3a36a3c75a..a3ff950c21 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImpl.java
@@ -2,16 +2,14 @@
 
 import cn.binarywang.wx.miniapp.api.WxMaLiveGoodsService;
 import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.bean.WxMaLiveInfo;
-import cn.binarywang.wx.miniapp.bean.WxMaLiveResult;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.bean.live.WxMaLiveGoodInfo;
+import cn.binarywang.wx.miniapp.bean.live.WxMaLiveResult;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableMap;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonObject;
 import lombok.AllArgsConstructor;
-import me.chanjar.weixin.common.enums.WxType;
-import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.json.GsonParser;
 
@@ -32,15 +30,9 @@ public class WxMaLiveGoodsServiceImpl implements WxMaLiveGoodsService {
   private final WxMaService wxMaService;
 
   @Override
-  public WxMaLiveResult addGoods(WxMaLiveInfo.Goods goods) throws WxErrorException {
-    Map map = new HashMap<>(2);
-    map.put("goodsInfo", goods);
-    String responseContent = this.wxMaService.post(ADD_GOODS, WxMaGsonBuilder.create().toJson(map));
-    JsonObject jsonObject = GsonParser.parse(responseContent);
-    if (jsonObject.get("errcode").getAsInt() != 0) {
-      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
-    }
-    return WxMaLiveResult.fromJson(jsonObject.toString());
+  public WxMaLiveResult addGoods(WxMaLiveGoodInfo goods) throws WxErrorException {
+    return WxMaLiveResult.fromJson(this.wxMaService.post(ADD_GOODS,
+      WxMaGsonBuilder.create().toJson(ImmutableMap.of("goodsInfo", goods))));
   }
 
   @Override
@@ -48,11 +40,7 @@ public boolean resetAudit(Integer auditId, Integer goodsId) throws WxErrorExcept
     Map map = new HashMap<>(4);
     map.put("auditId", auditId);
     map.put("goodsId", goodsId);
-    String responseContent = this.wxMaService.post(RESET_AUDIT_GOODS, WxMaGsonBuilder.create().toJson(map));
-    JsonObject jsonObject = GsonParser.parse(responseContent);
-    if (jsonObject.get("errcode").getAsInt() != 0) {
-      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
-    }
+    this.wxMaService.post(RESET_AUDIT_GOODS, WxMaGsonBuilder.create().toJson(map));
     return true;
   }
 
@@ -62,9 +50,6 @@ public String auditGoods(Integer goodsId) throws WxErrorException {
     map.put("goodsId", goodsId);
     String responseContent = this.wxMaService.post(AUDIT_GOODS, WxMaGsonBuilder.create().toJson(map));
     JsonObject jsonObject = GsonParser.parse(responseContent);
-    if (jsonObject.get("errcode").getAsInt() != 0) {
-      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
-    }
     return jsonObject.get("auditId").getAsString();
   }
 
@@ -72,23 +57,15 @@ public String auditGoods(Integer goodsId) throws WxErrorException {
   public boolean deleteGoods(Integer goodsId) throws WxErrorException {
     Map map = new HashMap<>(2);
     map.put("goodsId", goodsId);
-    String responseContent = this.wxMaService.post(DELETE_GOODS, WxMaGsonBuilder.create().toJson(map));
-    JsonObject jsonObject = GsonParser.parse(responseContent);
-    if (jsonObject.get("errcode").getAsInt() != 0) {
-      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
-    }
+    this.wxMaService.post(DELETE_GOODS, WxMaGsonBuilder.create().toJson(map));
     return true;
   }
 
   @Override
-  public boolean updateGoods(WxMaLiveInfo.Goods goods) throws WxErrorException {
+  public boolean updateGoods(WxMaLiveGoodInfo goods) throws WxErrorException {
     Map map = new HashMap<>(2);
     map.put("goodsInfo", goods);
-    String responseContent = this.wxMaService.post(UPDATE_GOODS, WxMaGsonBuilder.create().toJson(map));
-    JsonObject jsonObject = GsonParser.parse(responseContent);
-    if (jsonObject.get("errcode").getAsInt() != 0) {
-      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
-    }
+    this.wxMaService.post(UPDATE_GOODS, WxMaGsonBuilder.create().toJson(map));
     return true;
   }
 
@@ -97,11 +74,7 @@ public WxMaLiveResult getGoodsWareHouse(List goodsIds) throws WxErrorEx
     Map map = new HashMap<>(2);
     map.put("goods_ids", goodsIds);
     String responseContent = this.wxMaService.post(GET_GOODS_WARE_HOUSE, WxMaGsonBuilder.create().toJson(map));
-    JsonObject jsonObject = GsonParser.parse(responseContent);
-    if (jsonObject.get("errcode").getAsInt() != 0) {
-      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
-    }
-    return WxMaLiveResult.fromJson(jsonObject.toString());
+    return WxMaLiveResult.fromJson(responseContent);
   }
 
   @Override
@@ -109,9 +82,6 @@ public WxMaLiveResult getApprovedGoods(Integer offset, Integer limit, Integer st
     ImmutableMap params = ImmutableMap.of("status", status, "offset", offset, "limit", limit);
     String responseContent = wxMaService.get(GET_APPROVED_GOODS, Joiner.on("&").withKeyValueSeparator("=").join(params));
     JsonObject jsonObject = GsonParser.parse(responseContent);
-    if (jsonObject.get("errcode").getAsInt() != 0) {
-      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
-    }
     JsonArray goodsArr = jsonObject.getAsJsonArray("goods");
     if (goodsArr.size() > 0) {
       for (int i = 0; i < goodsArr.size(); i++) {
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveServiceImpl.java
index 3c5abc8781..1fcd23e51c 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveServiceImpl.java
@@ -2,9 +2,9 @@
 
 import cn.binarywang.wx.miniapp.api.WxMaLiveService;
 import cn.binarywang.wx.miniapp.api.WxMaService;
-import cn.binarywang.wx.miniapp.bean.WxMaLiveInfo;
-import cn.binarywang.wx.miniapp.bean.WxMaLiveResult;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.bean.live.*;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import com.google.common.base.Joiner;
 import com.google.gson.JsonObject;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -28,22 +28,68 @@
 @Slf4j
 @AllArgsConstructor
 public class WxMaLiveServiceImpl implements WxMaLiveService {
+  private static final String ERR_CODE = "errcode";
+  private static final String ROOM_ID = "roomId";
   private final WxMaService wxMaService;
 
   @Override
-  public Integer createRoom(WxMaLiveInfo.RoomInfo roomInfo) throws WxErrorException {
+  public WxMaCreateRoomResult createRoom(WxMaLiveRoomInfo roomInfo) throws WxErrorException {
     String responseContent = this.wxMaService.post(CREATE_ROOM, WxMaGsonBuilder.create().toJson(roomInfo));
     JsonObject jsonObject = GsonParser.parse(responseContent);
-    if (jsonObject.get("errcode").getAsInt() != 0) {
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
       throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
     }
-    return jsonObject.get("roomId").getAsInt();
+
+    return WxMaGsonBuilder.create().fromJson(responseContent, WxMaCreateRoomResult.class);
   }
 
   @Override
-  public WxMaLiveResult getLiveInfo(Integer start, Integer limit) throws WxErrorException {
-    JsonObject jsonObject = getLiveInfo(start, limit, null);
-    return WxMaLiveResult.fromJson(jsonObject.toString());
+  public boolean deleteRoom(Integer roomId) throws WxErrorException {
+    Map map = new HashMap<>(2);
+    map.put("id", roomId);
+    String responseContent = this.wxMaService.post(DELETE_ROOM, WxMaGsonBuilder.create().toJson(map));
+    JsonObject jsonObject = GsonParser.parse(responseContent);
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+    }
+    return true;
+  }
+
+  @Override
+  public boolean editRoom(WxMaLiveRoomInfo roomInfo) throws WxErrorException {
+    String responseContent = this.wxMaService.post(EDIT_ROOM, WxMaGsonBuilder.create().toJson(roomInfo));
+    JsonObject jsonObject = GsonParser.parse(responseContent);
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+    }
+    return true;
+  }
+
+  @Override
+  public String getPushUrl(Integer roomId) throws WxErrorException {
+    Map map = new HashMap<>(2);
+    map.put(ROOM_ID, roomId);
+    String responseContent = this.wxMaService.get(GET_PUSH_URL, Joiner.on("&").withKeyValueSeparator("=").join(map));
+    JsonObject jsonObject = GsonParser.parse(responseContent);
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+    }
+    return jsonObject.get("pushAddr").getAsString();
+  }
+
+  @Override
+  public String getSharedCode(Integer roomId, String params) throws WxErrorException {
+    Map map = new HashMap<>(2);
+    map.put(ROOM_ID, roomId);
+    if (null != params) {
+      map.put("params", params);
+    }
+    String responseContent = this.wxMaService.get(GET_SHARED_CODE, Joiner.on("&").withKeyValueSeparator("=").join(map));
+    JsonObject jsonObject = GsonParser.parse(responseContent);
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+    }
+    return jsonObject.get("cdnUrl").getAsString();
   }
 
   @Override
@@ -74,6 +120,13 @@ public List getLiveInfos() throws WxErrorException {
     return results;
   }
 
+  @Override
+  public WxMaLiveResult getLiveInfo(Integer start, Integer limit) throws WxErrorException {
+    JsonObject jsonObject = getLiveInfo(start, limit, null);
+    return WxMaLiveResult.fromJson(jsonObject.toString());
+  }
+
+
   @Override
   public WxMaLiveResult getLiveReplay(String action, Integer roomId, Integer start, Integer limit) throws WxErrorException {
     Map map = new HashMap<>(4);
@@ -83,6 +136,20 @@ public WxMaLiveResult getLiveReplay(String action, Integer roomId, Integer start
     return WxMaLiveResult.fromJson(jsonObject.toString());
   }
 
+  private JsonObject getLiveInfo(Integer start, Integer limit, Map map) throws WxErrorException {
+    if (map == null) {
+      map = new HashMap<>(2);
+    }
+    map.put("start", start);
+    map.put("limit", limit);
+    String responseContent = wxMaService.post(GET_LIVE_INFO, WxMaGsonBuilder.create().toJson(map));
+    JsonObject jsonObject = GsonParser.parse(responseContent);
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+    }
+    return jsonObject;
+  }
+
   @Override
   public WxMaLiveResult getLiveReplay(Integer roomId, Integer start, Integer limit) throws WxErrorException {
     return getLiveReplay("get_replay", roomId, start, limit);
@@ -91,27 +158,66 @@ public WxMaLiveResult getLiveReplay(Integer roomId, Integer start, Integer limit
   @Override
   public boolean addGoodsToRoom(Integer roomId, List goodsIds) throws WxErrorException {
     Map map = new HashMap<>(2);
-    map.put("roomId", roomId);
+    map.put(ROOM_ID, roomId);
     map.put("ids", goodsIds);
     String responseContent = this.wxMaService.post(ADD_GOODS, WxMaGsonBuilder.create().toJson(map));
     JsonObject jsonObject = GsonParser.parse(responseContent);
-    if (jsonObject.get("errcode").getAsInt() != 0) {
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
       throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
     }
     return true;
   }
 
-  private JsonObject getLiveInfo(Integer start, Integer limit, Map map) throws WxErrorException {
-    if (map == null) {
-      map = new HashMap(2);
+  @Override
+  public boolean addAssistant(Integer roomId, List users) throws WxErrorException {
+    Map map = new HashMap<>(2);
+    map.put(ROOM_ID, roomId);
+    map.put("users", users);
+    String responseContent = this.wxMaService.post(ADD_ASSISTANT, WxMaGsonBuilder.create().toJson(map));
+    JsonObject jsonObject = GsonParser.parse(responseContent);
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
     }
-    map.put("start", start);
-    map.put("limit", limit);
-    String responseContent = wxMaService.post(GET_LIVE_INFO, WxMaGsonBuilder.create().toJson(map));
+    return true;
+  }
+
+  @Override
+  public boolean modifyAssistant(Integer roomId, String username, String nickname) throws WxErrorException {
+    Map map = new HashMap<>(2);
+    map.put(ROOM_ID, roomId);
+    map.put("username", username);
+    map.put("nickname", nickname);
+    String responseContent = this.wxMaService.post(MODIFY_ASSISTANT, WxMaGsonBuilder.create().toJson(map));
     JsonObject jsonObject = GsonParser.parse(responseContent);
-    if (jsonObject.get("errcode").getAsInt() != 0) {
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
       throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
     }
-    return jsonObject;
+    return true;
+  }
+
+  @Override
+  public boolean removeAssistant(Integer roomId, String username) throws WxErrorException {
+    Map map = new HashMap<>(2);
+    map.put(ROOM_ID, roomId);
+    map.put("username", username);
+    String responseContent = this.wxMaService.post(REMOVE_ASSISTANT, WxMaGsonBuilder.create().toJson(map));
+    JsonObject jsonObject = GsonParser.parse(responseContent);
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+    }
+    return true;
+  }
+
+  @Override
+  public List getAssistantList(Integer roomId) throws WxErrorException {
+    Map map = new HashMap<>(2);
+    map.put(ROOM_ID, roomId);
+    String responseContent = this.wxMaService.post(GET_ASSISTANT_LIST, WxMaGsonBuilder.create().toJson(map));
+    JsonObject jsonObject = GsonParser.parse(responseContent);
+    if (jsonObject.get(ERR_CODE).getAsInt() != 0) {
+      throw new WxErrorException(WxError.fromJson(responseContent, WxType.MiniApp));
+    }
+    return WxMaAssistantResult.fromJson(responseContent).getList();
   }
+
 }
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java
index f647a80fee..776a17a251 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImpl.java
@@ -4,7 +4,7 @@
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.bean.*;
 import cn.binarywang.wx.miniapp.constant.WxMaConstants;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.JsonObject;
 import lombok.AllArgsConstructor;
 import me.chanjar.weixin.common.enums.WxType;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOcrServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOcrServiceImpl.java
index 8106dcd6c7..3e7eb8c38a 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOcrServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaOcrServiceImpl.java
@@ -2,7 +2,7 @@
 
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import lombok.RequiredArgsConstructor;
-import me.chanjar.weixin.common.api.WxOcrService;
+import me.chanjar.weixin.common.service.WxOcrService;
 import me.chanjar.weixin.common.bean.ocr.*;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.requestexecuter.ocr.OcrDiscernRequestExecutor;
@@ -43,7 +43,7 @@ public WxOcrIdCardResult idCard(String imgUrl) throws WxErrorException {
       // ignore cannot happen
     }
 
-    final String result = this.mainService.post(String.format(IDCARD, imgUrl), null);
+    final String result = this.mainService.post(String.format(IDCARD, imgUrl), (String) null);
     return WxOcrIdCardResult.fromJson(result);
   }
 
@@ -62,7 +62,7 @@ public WxOcrBankCardResult bankCard(String imgUrl) throws WxErrorException {
       // ignore cannot happen
     }
 
-    final String result = this.mainService.post(String.format(BANK_CARD, imgUrl), null);
+    final String result = this.mainService.post(String.format(BANK_CARD, imgUrl), (String) null);
     return WxOcrBankCardResult.fromJson(result);
   }
 
@@ -81,7 +81,7 @@ public WxOcrDrivingResult driving(String imgUrl) throws WxErrorException {
       // ignore cannot happen
     }
 
-    final String result = this.mainService.post(String.format(DRIVING, imgUrl), null);
+    final String result = this.mainService.post(String.format(DRIVING, imgUrl), (String) null);
     return WxOcrDrivingResult.fromJson(result);
   }
 
@@ -100,7 +100,7 @@ public WxOcrDrivingLicenseResult drivingLicense(String imgUrl) throws WxErrorExc
       // ignore cannot happen
     }
 
-    final String result = this.mainService.post(String.format(DRIVING_LICENSE, imgUrl), null);
+    final String result = this.mainService.post(String.format(DRIVING_LICENSE, imgUrl), (String) null);
     return WxOcrDrivingLicenseResult.fromJson(result);
   }
 
@@ -119,7 +119,7 @@ public WxOcrBizLicenseResult bizLicense(String imgUrl) throws WxErrorException {
       // ignore cannot happen
     }
 
-    final String result = this.mainService.post(String.format(BIZ_LICENSE, imgUrl), null);
+    final String result = this.mainService.post(String.format(BIZ_LICENSE, imgUrl), (String) null);
     return WxOcrBizLicenseResult.fromJson(result);
   }
 
@@ -138,7 +138,7 @@ public WxOcrCommResult comm(String imgUrl) throws WxErrorException {
       // ignore cannot happen
     }
 
-    final String result = this.mainService.post(String.format(COMM, imgUrl), null);
+    final String result = this.mainService.post(String.format(COMM, imgUrl), (String) null);
     return WxOcrCommResult.fromJson(result);
   }
 
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaPluginServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaPluginServiceImpl.java
index 134ed66d51..643b3e0592 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaPluginServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaPluginServiceImpl.java
@@ -3,7 +3,7 @@
 import cn.binarywang.wx.miniapp.api.WxMaPluginService;
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.bean.WxMaPluginListResult;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.common.collect.ImmutableMap;
 import lombok.AllArgsConstructor;
 import me.chanjar.weixin.common.error.WxErrorException;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaQrcodeServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaQrcodeServiceImpl.java
index 905aff4a2a..524be9fe92 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaQrcodeServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaQrcodeServiceImpl.java
@@ -6,8 +6,9 @@
 import cn.binarywang.wx.miniapp.bean.WxMaQrcode;
 import cn.binarywang.wx.miniapp.bean.WxaCode;
 import cn.binarywang.wx.miniapp.bean.WxaCodeUnlimit;
-import cn.binarywang.wx.miniapp.util.QrcodeBytesRequestExecutor;
-import cn.binarywang.wx.miniapp.util.QrcodeRequestExecutor;
+import cn.binarywang.wx.miniapp.executor.QrcodeBytesRequestExecutor;
+import cn.binarywang.wx.miniapp.executor.QrcodeFileRequestExecutor;
+import cn.binarywang.wx.miniapp.executor.QrcodeRequestExecutor;
 import lombok.AllArgsConstructor;
 import me.chanjar.weixin.common.error.WxErrorException;
 
@@ -107,4 +108,51 @@ public File createWxaCodeUnlimit(String scene, String page) throws WxErrorExcept
     return this.createWxaCodeUnlimit(scene, page, 430, true, null, false);
   }
 
+  @Override
+  public File createQrcode(String path, int width, String filePath) throws WxErrorException {
+    final QrcodeFileRequestExecutor executor = new QrcodeFileRequestExecutor(this.wxMaService.getRequestHttp(), filePath);
+    return this.wxMaService.execute(executor, CREATE_QRCODE_URL, new WxMaQrcode(path, width));
+  }
+
+  @Override
+  public File createQrcode(String path, String filePath) throws WxErrorException {
+    return createQrcode(path, 430, filePath);
+  }
+
+  @Override
+  public File createWxaCode(String path, int width, String filePath, boolean autoColor, WxMaCodeLineColor lineColor, boolean isHyaline)
+    throws WxErrorException {
+    final QrcodeFileRequestExecutor executor = new QrcodeFileRequestExecutor(this.wxMaService.getRequestHttp(), filePath);
+    return this.wxMaService.execute(executor, GET_WXACODE_URL, WxaCode.builder()
+      .path(path)
+      .width(width)
+      .autoColor(autoColor)
+      .lineColor(lineColor)
+      .isHyaline(isHyaline)
+      .build());
+  }
+
+  @Override
+  public File createWxaCode(String path, int width, String filePath) throws WxErrorException {
+    return this.createWxaCode(path, width, filePath, true, null, false);
+  }
+
+  @Override
+  public File createWxaCode(String path, String filePath) throws WxErrorException {
+    return this.createWxaCode(path, 430, filePath);
+  }
+
+  @Override
+  public File createWxaCodeUnlimit(String scene, String page, String filePath, int width, boolean autoColor,
+                                   WxMaCodeLineColor lineColor, boolean isHyaline) throws WxErrorException {
+    return this.wxMaService.execute(new QrcodeFileRequestExecutor(this.wxMaService.getRequestHttp(), filePath),
+      GET_WXACODE_UNLIMIT_URL,
+      this.buildWxaCodeUnlimit(scene, page, width, autoColor, lineColor, isHyaline));
+  }
+
+  @Override
+  public File createWxaCodeUnlimit(String scene, String page, String filePath) throws WxErrorException {
+    return this.createWxaCodeUnlimit(scene, page, filePath, 430, true, null, false);
+  }
+
 }
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImpl.java
index c69a58d1b1..3980f145fc 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSettingServiceImpl.java
@@ -3,7 +3,7 @@
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.api.WxMaSettingService;
 import cn.binarywang.wx.miniapp.bean.WxMaDomainAction;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.AllArgsConstructor;
 import me.chanjar.weixin.common.error.WxErrorException;
 
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java
index 8682612a9b..faa88a6387 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/api/impl/WxMaSubscribeServiceImpl.java
@@ -3,7 +3,7 @@
 import cn.binarywang.wx.miniapp.api.WxMaService;
 import cn.binarywang.wx.miniapp.api.WxMaSubscribeService;
 import cn.binarywang.wx.miniapp.bean.template.WxMaPubTemplateTitleListResult;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableMap;
 import com.google.gson.reflect.TypeToken;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/AbstractWxMaQrcodeWrapper.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/AbstractWxMaQrcodeWrapper.java
index cb444c94c1..c7fbe10666 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/AbstractWxMaQrcodeWrapper.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/AbstractWxMaQrcodeWrapper.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 
 /**
  * 微信二维码(小程序码)包装器.
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaAuditMediaUploadResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaAuditMediaUploadResult.java
new file mode 100644
index 0000000000..6468662528
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaAuditMediaUploadResult.java
@@ -0,0 +1,33 @@
+package cn.binarywang.wx.miniapp.bean;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 小程序 提审素材上传接口
+ *
+ * @author yangyh22
+ * @since 2020/11/14
+ */
+@Data
+public class WxMaAuditMediaUploadResult implements Serializable {
+  private static final long serialVersionUID = 1L;
+
+  private String type;
+
+  @SerializedName("mediaid")
+  private String mediaId;
+
+  public static WxMaAuditMediaUploadResult fromJson(String json) {
+    return WxGsonBuilder.create().fromJson(json, WxMaAuditMediaUploadResult.class);
+  }
+
+  @Override
+  public String toString() {
+    return WxGsonBuilder.create().toJson(this);
+  }
+
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaDomainAction.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaDomainAction.java
index 19a6a1cde9..b41782597f 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaDomainAction.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaDomainAction.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaJscode2SessionResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaJscode2SessionResult.java
index 85b4767702..af113e4ec5 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaJscode2SessionResult.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaJscode2SessionResult.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java
index 73ca435d62..5d16b60b75 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaKefuMessage.java
@@ -4,7 +4,7 @@
 import cn.binarywang.wx.miniapp.builder.LinkMessageBuilder;
 import cn.binarywang.wx.miniapp.builder.MaPageMessageBuilder;
 import cn.binarywang.wx.miniapp.builder.TextMessageBuilder;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaLiveInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaLiveInfo.java
deleted file mode 100644
index 8a98b4a218..0000000000
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaLiveInfo.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package cn.binarywang.wx.miniapp.bean;
-
-import lombok.Data;
-
-import java.io.Serializable;
-import java.util.List;
-
-/**
- * 直播接口入参
- *
- * @author yjwang
- * @date 2020/4/5
- */
-@Data
-public class WxMaLiveInfo implements Serializable {
-  private static final long serialVersionUID = 7285263767524755887L;
-
-  /**
-   * 直播列表
-   */
-  @Data
-  public static class RoomInfo implements Serializable {
-    private static final long serialVersionUID = 7745775280267417154L;
-    private String name;
-    private Integer roomid;
-    private String coverImg;
-    private String shareImg;
-    private Integer liveStatus;
-    private Long startTime;
-    private Long endTime;
-    private String anchorName;
-    private String anchorWechat;
-    private String anchorImg;
-    private Integer type;
-    private Integer screenType;
-    private Integer closeLike;
-    private Integer closeGoods;
-    private Integer closeComment;
-    private List goods;
-  }
-
-  /**
-   * 商品列表
-   */
-  @Data
-  public static class Goods implements Serializable {
-    private static final long serialVersionUID = 5769245932149287574L;
-    private Integer goodsId;
-    private String coverImgUrl;
-    private String url;
-    private Integer priceType;
-    private String price;
-    private String price2;
-    private String name;
-    /**
-     * 1, 2:表示是为api添加商品,否则是在MP添加商品
-     */
-    private String thirdPartyTag;
-  }
-}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMediaAsyncCheckResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMediaAsyncCheckResult.java
index e7fda61a02..f4428b959b 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMediaAsyncCheckResult.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMediaAsyncCheckResult.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
index 4e06e42394..57d6a5b9be 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaMessage.java
@@ -2,12 +2,13 @@
 
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
 import cn.binarywang.wx.miniapp.util.crypt.WxMaCryptUtils;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import cn.binarywang.wx.miniapp.util.xml.XStreamTransformer;
 import com.google.gson.annotations.SerializedName;
 import com.thoughtworks.xstream.annotations.XStreamAlias;
 import com.thoughtworks.xstream.annotations.XStreamConverter;
 import lombok.Data;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
 import org.apache.commons.io.IOUtils;
 
@@ -174,7 +175,7 @@ public static WxMaMessage fromEncryptedXml(InputStream is, WxMaConfig wxMaConfig
       return fromEncryptedXml(IOUtils.toString(is, StandardCharsets.UTF_8), wxMaConfig,
         timestamp, nonce, msgSignature);
     } catch (IOException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
   }
 
@@ -188,7 +189,7 @@ public static WxMaMessage fromEncryptedJson(String encryptedJson, WxMaConfig con
       String plainText = new WxMaCryptUtils(config).decrypt(encryptedMessage.getEncrypt());
       return fromJson(plainText);
     } catch (Exception e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
   }
 
@@ -196,7 +197,7 @@ public static WxMaMessage fromEncryptedJson(InputStream inputStream, WxMaConfig
     try {
       return fromEncryptedJson(IOUtils.toString(inputStream, StandardCharsets.UTF_8), config);
     } catch (IOException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
   }
 
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaPhoneNumberInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaPhoneNumberInfo.java
index 149ecbebe8..da481f0983 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaPhoneNumberInfo.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaPhoneNumberInfo.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.Data;
 
 import java.io.Serializable;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaQrcode.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaQrcode.java
index 5c17cd1e58..cb505a1654 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaQrcode.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaQrcode.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
 
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaRunStepInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaRunStepInfo.java
index 5e6ff641c4..fe9e74b3fc 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaRunStepInfo.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaRunStepInfo.java
@@ -3,7 +3,7 @@
 import java.io.Serializable;
 import java.util.List;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.JsonObject;
 import com.google.gson.reflect.TypeToken;
 import lombok.Data;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaShareInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaShareInfo.java
index 91aff519c0..e8c7f1a9ae 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaShareInfo.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaShareInfo.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.Data;
 
 import java.io.Serializable;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeMessage.java
index 791687b6c0..edf4dc3c3a 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeMessage.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaSubscribeMessage.java
@@ -1,7 +1,7 @@
 package cn.binarywang.wx.miniapp.bean;
 
 import cn.binarywang.wx.miniapp.constant.WxMaConstants;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.*;
 
 import java.io.Serializable;
@@ -65,12 +65,12 @@ public class WxMaSubscribeMessage implements Serializable {
   /**
    * 跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
    */
-  private String miniprogramState = WxMaConstants.MiniprogramState.FORMAL;
+  private String miniprogramState = WxMaConstants.MiniProgramState.FORMAL;
 
   /**
    * 进入小程序查看的语言类型,支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN
    */
-  private String lang = WxMaConstants.MiniprogramLang.ZH_CN;
+  private String lang = WxMaConstants.MiniProgramLang.ZH_CN;
 
   public WxMaSubscribeMessage addData(Data datum) {
     if (this.data == null) {
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateData.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateData.java
index 040edda4d0..9ead69646d 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateData.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaTemplateData.java
@@ -3,6 +3,8 @@
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import java.io.Serializable;
+
 /**
  * 
  * 参考文档 https://developers.weixin.qq.com/miniprogram/dev/api-backend/templateMessage.send.html
@@ -13,7 +15,9 @@
  */
 @Data
 @NoArgsConstructor
-public class WxMaTemplateData {
+public class WxMaTemplateData implements Serializable {
+  private static final long serialVersionUID = 855214313056578490L;
+
   private String name;
   private String value;
   private String color;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUniformMessage.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUniformMessage.java
index 7515bdbe25..6df12b1b86 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUniformMessage.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUniformMessage.java
@@ -4,7 +4,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.adaptor.WxMaUniformMessageGsonAdapter;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -97,6 +98,10 @@ public static class MiniProgram implements Serializable {
     private static final long serialVersionUID = -7945254706501974849L;
 
     private String appid;
+    /**
+     *  注意,此属性不是最终的json字符串,可结合以下两个属性一起使用,确定最终json字符串是什么
+     *  转换的代码逻辑,请阅读 {@link WxMaUniformMessageGsonAdapter}
+     */
     private String pagePath;
 
     /**
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUserInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUserInfo.java
index 368fa772cc..86b14f7555 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUserInfo.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaUserInfo.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.Data;
 
 import java.io.Serializable;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxaCode.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxaCode.java
index 2d94f05cea..2361355adf 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxaCode.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxaCode.java
@@ -2,7 +2,7 @@
 
 import java.io.Serializable;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxaCodeUnlimit.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxaCodeUnlimit.java
index 05bf134c6b..ab0dad4e2b 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxaCodeUnlimit.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxaCodeUnlimit.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaRetainInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaRetainInfo.java
index 7021a180e9..8006cca01d 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaRetainInfo.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaRetainInfo.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.analysis;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaUserPortrait.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaUserPortrait.java
index 5e1164909e..0dcf30ee38 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaUserPortrait.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaUserPortrait.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.analysis;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.Data;
 
 import java.io.Serializable;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitDistribution.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitDistribution.java
index 1655eec286..84a8ac1220 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitDistribution.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/analysis/WxMaVisitDistribution.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.analysis;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java
index 3faa18660b..d8733756c1 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeAuditStatus.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.code;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequest.java
index f59fb1f039..788f166413 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequest.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeCommitRequest.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.code;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequest.java
index b65c4df588..ff245c8e6a 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequest.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeSubmitAuditRequest.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.code;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistribution.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistribution.java
index dd0a03a918..9a57933d75 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistribution.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/code/WxMaCodeVersionDistribution.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.code;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressAccount.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressAccount.java
index 950bda3066..d783c29acf 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressAccount.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressAccount.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.express;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.JsonObject;
 import com.google.gson.annotations.SerializedName;
 import com.google.gson.reflect.TypeToken;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressDelivery.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressDelivery.java
index dbdb02c113..7a5465ee38 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressDelivery.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressDelivery.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.express;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.JsonObject;
 import com.google.gson.annotations.SerializedName;
 import com.google.gson.reflect.TypeToken;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressPath.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressPath.java
index bbd3feacdb..28c0bcdfc2 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressPath.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressPath.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.express;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Data;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressPrinter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressPrinter.java
index b41d33305c..2c1e98602a 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressPrinter.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/WxMaExpressPrinter.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.express;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Data;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressAddOrderRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressAddOrderRequest.java
index e11f5beb6e..01056753f7 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressAddOrderRequest.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressAddOrderRequest.java
@@ -2,7 +2,7 @@
 
 
 import cn.binarywang.wx.miniapp.bean.express.*;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressBindAccountRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressBindAccountRequest.java
index be0ef991c1..aee69f9743 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressBindAccountRequest.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressBindAccountRequest.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.express.request;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressGetOrderRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressGetOrderRequest.java
index 6fe03ddae9..419be9e600 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressGetOrderRequest.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressGetOrderRequest.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.express.request;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressPrinterUpdateRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressPrinterUpdateRequest.java
index da47f77042..e3aea495fa 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressPrinterUpdateRequest.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressPrinterUpdateRequest.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.express.request;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressTestUpdateOrderRequest.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressTestUpdateOrderRequest.java
index c0a8561243..3377a7e77d 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressTestUpdateOrderRequest.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/request/WxMaExpressTestUpdateOrderRequest.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.express.request;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/result/WxMaExpressOrderInfoResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/result/WxMaExpressOrderInfoResult.java
index fb47057d87..9502eee826 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/result/WxMaExpressOrderInfoResult.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/express/result/WxMaExpressOrderInfoResult.java
@@ -1,6 +1,6 @@
 package cn.binarywang.wx.miniapp.bean.express.result;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.JsonObject;
 import com.google.gson.annotations.SerializedName;
 import com.google.gson.reflect.TypeToken;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaAssistantResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaAssistantResult.java
new file mode 100644
index 0000000000..b508b2e09d
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaAssistantResult.java
@@ -0,0 +1,49 @@
+package cn.binarywang.wx.miniapp.bean.live;
+
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 直播间小助手用户信息
+ */
+@Data
+public class WxMaAssistantResult implements Serializable {
+  private static final long serialVersionUID = 5829108618580715870L;
+
+  private Integer count;
+  private Integer maxCount;
+  private Integer errcode;
+
+  private List list;
+
+  public static WxMaAssistantResult fromJson(String json) {
+    return WxMaGsonBuilder.create().fromJson(json, WxMaAssistantResult.class);
+  }
+  @Data
+  public static class Assistant implements Serializable {
+    private static final long serialVersionUID = 6362128855371134033L;
+    /**
+     * 修改时间
+     */
+    private Long timestamp;
+    /**
+     * 头像
+     **/
+    private String headimg;
+    /**
+     * 用户昵称
+     **/
+    private String nickname;
+    /**
+     * 微信号
+     **/
+    private String alias;
+    /**
+     * openid
+     **/
+    private String openid;
+  }
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaCreateRoomResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaCreateRoomResult.java
new file mode 100644
index 0000000000..56b4eb7251
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaCreateRoomResult.java
@@ -0,0 +1,30 @@
+package cn.binarywang.wx.miniapp.bean.live;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 创建直播间接口返回.
+ *
+ * @author Binary Wang
+ * @date 2020-11-29
+ */
+@Data
+public class WxMaCreateRoomResult implements Serializable {
+  private static final long serialVersionUID = -335928442728127170L;
+
+  /**
+   * "小程序直播" 小程序码
+   * 当主播微信号没有在 “小程序直播“ 小程序实名认证 返回该字段
+   */
+  @SerializedName("qrcode_url")
+  private String qrcodeUrl;
+
+  /**
+   * 房间ID
+   */
+  @SerializedName("roomId")
+  private Integer roomId;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveAssistantInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveAssistantInfo.java
new file mode 100644
index 0000000000..bfd727ca82
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveAssistantInfo.java
@@ -0,0 +1,38 @@
+package cn.binarywang.wx.miniapp.bean.live;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 直播间小助手用户信息
+ */
+@Data
+public class WxMaLiveAssistantInfo implements Serializable {
+  private static final long serialVersionUID = -5603581848069320808L;
+  /**
+   * 修改时间
+   */
+  private Long timestamp;
+  /**
+   * 头像
+   **/
+  private String headimg;
+  /**
+   * 用户微信号
+   **/
+  private String username;
+  /**
+   * 用户昵称
+   **/
+  private String nickname;
+  /**
+   * 微信号
+   **/
+  private String alias;
+  /**
+   * openid
+   **/
+  private String openid;
+
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveGoodInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveGoodInfo.java
new file mode 100644
index 0000000000..6566491244
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveGoodInfo.java
@@ -0,0 +1,24 @@
+package cn.binarywang.wx.miniapp.bean.live;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 直播商品信息
+ */
+@Data
+public class WxMaLiveGoodInfo implements Serializable {
+  private static final long serialVersionUID = 5769245932149287574L;
+  private Integer goodsId;
+  private String coverImgUrl;
+  private String url;
+  private Integer priceType;
+  private String price;
+  private String price2;
+  private String name;
+  /**
+   * 1, 2:表示是为api添加商品,否则是在MP添加商品
+   */
+  private String thirdPartyTag;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaLiveResult.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveResult.java
similarity index 95%
rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaLiveResult.java
rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveResult.java
index 2040b4a525..9c8fc4016c 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/WxMaLiveResult.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveResult.java
@@ -1,6 +1,6 @@
-package cn.binarywang.wx.miniapp.bean;
+package cn.binarywang.wx.miniapp.bean.live;
 
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 
@@ -18,8 +18,6 @@
 @Data
 public class WxMaLiveResult implements Serializable {
   private static final long serialVersionUID = 1L;
-  private Integer errcode;
-  private String errmsg;
   private Integer total;
   private Integer auditId;
   private Integer goodsId;
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveRoomInfo.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveRoomInfo.java
new file mode 100644
index 0000000000..ca387946eb
--- /dev/null
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/bean/live/WxMaLiveRoomInfo.java
@@ -0,0 +1,94 @@
+package cn.binarywang.wx.miniapp.bean.live;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 直播间信息
+ */
+@Data
+public class WxMaLiveRoomInfo implements Serializable {
+  private static final long serialVersionUID = 7745775280267417154L;
+
+  /**
+   * 直播间ID
+   */
+  private Integer id;
+  /**
+   * 直播间名字,最短3个汉字,最长17个汉字,1个汉字相当于2个字符
+   **/
+  private String name;
+  /**
+   * 背景图,填入mediaID(mediaID获取后,三天内有效);图片mediaID的获取,请参考以下文档: https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html;直播间背景图,图片规则:建议像素1080*1920,大小不超过2M
+   **/
+  private String coverImg;
+  /**
+   * 直播计划开始时间(开播时间需要在当前时间的10分钟后 并且 开始时间不能在 6 个月后)
+   **/
+  private Long startTime;
+  /**
+   * 直播计划结束时间(开播时间和结束时间间隔不得短于30分钟,不得超过24小时)
+   **/
+  private Long endTime;
+  /**
+   * 主播昵称,最短2个汉字,最长15个汉字,1个汉字相当于2个字符
+   **/
+  private String anchorName;
+  /**
+   * 主播微信号,如果未实名认证,需要先前往“小程序直播”小程序进行实名验证, 小程序二维码链接:https://res.wx.qq.com/op_res/BbVNeczA1XudfjVqCVoKgfuWe7e3aUhokktRVOqf_F0IqS6kYR--atCpVNUUC3zr
+   **/
+  private String anchorWechat;
+  /**
+   * 主播副号微信号,如果未实名认证,需要先前往“小程序直播”小程序进行实名验证, 小程序二维码链接:https://res.wx.qq.com/op_res/BbVNeczA1XudfjVqCVoKgfuWe7e3aUhokktRVOqf_F0IqS6kYR--atCpVNUUC3zr
+   **/
+  private String subAnchorWechat;
+  /**
+   * 创建者微信号,不传入则此直播间所有成员可见。传入则此房间仅创建者、管理员、超管、直播间主播可见
+   **/
+  private String createrWechat;
+  /**
+   * 分享图,填入mediaID(mediaID获取后,三天内有效);图片mediaID的获取,请参考以下文档: https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html;直播间分享图,图片规则:建议像素800*640,大小不超过1M;
+   **/
+  private String shareImg;
+  /**
+   * 购物直播频道封面图,填入mediaID(mediaID获取后,三天内有效);图片mediaID的获取,请参考以下文档: https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html; 购物直播频道封面图,图片规则:建议像素800*800,大小不超过100KB;
+   **/
+  private String feedsImg;
+  /**
+   * 是否开启官方收录 【1: 开启,0:关闭】,默认开启收录
+   **/
+  private Integer isFeedsPublic;
+  /**
+   * 直播间类型 【1: 推流,0:手机直播】
+   **/
+  private Integer type;
+  /**
+   * 横屏、竖屏 【1:横屏,0:竖屏】(横屏:视频宽高比为16:9、4:3、1.85:1 ;竖屏:视频宽高比为9:16、2:3)
+   **/
+  private Integer screenType;
+  /**
+   * 是否关闭点赞 【0:开启,1:关闭】(若关闭,直播开始后不允许开启)
+   **/
+  private Integer closeLike;
+  /**
+   * 是否关闭货架 【0:开启,1:关闭】(若关闭,直播开始后不允许开启)
+   **/
+  private Integer closeGoods;
+  /**
+   * 是否关闭评论 【0:开启,1:关闭】(若关闭,直播开始后不允许开启)
+   **/
+  private Integer closeComment;
+  /**
+   * 是否关闭回放 【0:开启,1:关闭】默认关闭回放
+   **/
+  private Integer closeReplay;
+  /**
+   * 是否关闭分享 【0:开启,1:关闭】默认开启分享(直播开始后不允许修改)
+   **/
+  private Integer closeShare;
+  /**
+   * closeKf	Number	否	是否关闭客服 【0:开启,1:关闭】 默认关闭客服
+   **/
+  private Integer closeKf;
+}
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/AbstractWxMaRedisConfig.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/AbstractWxMaRedisConfig.java
index ac75b3697d..9b94a04bbb 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/AbstractWxMaRedisConfig.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/AbstractWxMaRedisConfig.java
@@ -1,6 +1,7 @@
 package cn.binarywang.wx.miniapp.config.impl;
 
 import com.github.jedis.lock.JedisLock;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import redis.clients.jedis.Jedis;
 
 import java.io.File;
@@ -232,10 +233,10 @@ private DistributedLock(String key) {
     public void lock() {
       try (Jedis jedis = getConfiguredJedis()) {
         if (!lock.acquire(jedis)) {
-          throw new RuntimeException("acquire timeouted");
+          throw new WxRuntimeException("acquire timeouted");
         }
       } catch (InterruptedException e) {
-        throw new RuntimeException("lock failed", e);
+        throw new WxRuntimeException("lock failed", e);
       }
     }
 
@@ -243,7 +244,7 @@ public void lock() {
     public void lockInterruptibly() throws InterruptedException {
       try (Jedis jedis = getConfiguredJedis()) {
         if (!lock.acquire(jedis)) {
-          throw new RuntimeException("acquire timeouted");
+          throw new WxRuntimeException("acquire timeouted");
         }
       }
     }
@@ -253,7 +254,7 @@ public boolean tryLock() {
       try (Jedis jedis = getConfiguredJedis()) {
         return lock.acquire(jedis);
       } catch (InterruptedException e) {
-        throw new RuntimeException("lock failed", e);
+        throw new WxRuntimeException("lock failed", e);
       }
     }
 
@@ -273,7 +274,7 @@ public void unlock() {
 
     @Override
     public Condition newCondition() {
-      throw new RuntimeException("unsupported method");
+      throw new WxRuntimeException("unsupported method");
     }
 
   }
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java
index 94e09bc5ca..73104bd367 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaDefaultConfigImpl.java
@@ -1,7 +1,7 @@
 package cn.binarywang.wx.miniapp.config.impl;
 
 import cn.binarywang.wx.miniapp.config.WxMaConfig;
-import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder;
+import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder;
 import me.chanjar.weixin.common.bean.WxAccessToken;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaRedisConfigImpl.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaRedisConfigImpl.java
index b2ef782d42..ca0e4fd253 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaRedisConfigImpl.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/config/impl/WxMaRedisConfigImpl.java
@@ -1,5 +1,6 @@
 package cn.binarywang.wx.miniapp.config.impl;
 
+import org.apache.commons.lang3.builder.ToStringBuilder;
 import redis.clients.jedis.Jedis;
 import redis.clients.jedis.JedisPool;
 
@@ -80,4 +81,9 @@ public void expireAccessToken() {
       jedis.expire(this.accessTokenKey, 0);
     }
   }
+
+  @Override
+  public String toString() {
+    return ToStringBuilder.reflectionToString(this);
+  }
 }
diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaConstants.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaConstants.java
index 2ca92d084c..8ac322aa5f 100644
--- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaConstants.java
+++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/constant/WxMaConstants.java
@@ -7,7 +7,10 @@
  *
  * @author Binary Wang
  */
-public class WxMaConstants {
+public abstract class WxMaConstants {
+  private WxMaConstants() {
+  }
+
   /**
    * 微信接口返回的参数errcode.
    */
@@ -16,7 +19,7 @@ public class WxMaConstants {
   /**
    * 素材类型.
    */
-  public static class MediaType {
+  public abstract static class MediaType {
     /**
      * 图片.
      */
@@ -26,7 +29,7 @@ public static class MediaType {
   /**
    * 消息格式.
    */
-  public static class MsgDataFormat {
+  public abstract static class MsgDataFormat {
     public static final String XML = "XML";
     public static final String JSON = "JSON";
   }
@@ -72,7 +75,7 @@ public static final class SecCheckMediaType {
   /**
    * 快递账号绑定类型
    */
-  public static final class BindAccountType{
+  public static final class BindAccountType {
 
     /**
      * 绑定
@@ -88,7 +91,7 @@ public static final class BindAccountType{
   /**
    * 快递下单订单来源
    */
-  public static final class OrderAddSource{
+  public static final class OrderAddSource {
 
     /**
      * 小程序
@@ -104,7 +107,11 @@ public static final class OrderAddSource{
   /**
    * 快递下单保价
    */
-  public static final class OrderAddInsured{
+  public static final class OrderAddInsured {
+    private OrderAddInsured() {
+
+    }
+
     /**
      * 不保价
      */
@@ -121,13 +128,15 @@ public static final class OrderAddInsured{
     public static final int DEFAULT_INSURED_VALUE = 0;
   }
 
-
   /**
    * 小程序订阅消息跳转小程序类型
-   *
+   * 

* developer为开发版;trial为体验版;formal为正式版;默认为正式版 */ - public static final class MiniprogramState{ + public static final class MiniProgramState { + private MiniProgramState() { + } + /** * 开发版 */ @@ -149,7 +158,10 @@ public static final class MiniprogramState{ * 进入小程序查看的语言类型 * 支持zh_CN(简体中文)、en_US(英文)、zh_HK(繁体中文)、zh_TW(繁体中文),默认为zh_CN */ - public static final class MiniprogramLang{ + public static final class MiniProgramLang { + private MiniProgramLang() { + } + /** * 简体中文 */ diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheAuditMediaUploadRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheAuditMediaUploadRequestExecutor.java new file mode 100644 index 0000000000..782dc46f29 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/ApacheAuditMediaUploadRequestExecutor.java @@ -0,0 +1,58 @@ +package cn.binarywang.wx.miniapp.executor; + +import java.io.File; +import java.io.IOException; + +import me.chanjar.weixin.common.enums.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.apache.Utf8ResponseHandler; +import cn.binarywang.wx.miniapp.bean.WxMaAuditMediaUploadResult; +import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; + +/** + * @author yangyh22 + * @since 2020/11/14 + */ +public class ApacheAuditMediaUploadRequestExecutor extends AuditMediaUploadRequestExecutor { + + public ApacheAuditMediaUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMaAuditMediaUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + RequestConfig config = RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build(); + httpPost.setConfig(config); + } + if (file != null) { + HttpEntity entity = MultipartEntityBuilder + .create() + .addBinaryBody("media", file) + .setMode(HttpMultipartMode.RFC6532) + .build(); + httpPost.setEntity(entity); + } + try (CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost)) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMaAuditMediaUploadResult.fromJson(responseContent); + } finally { + httpPost.releaseConnection(); + } + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/AuditMediaUploadRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/AuditMediaUploadRequestExecutor.java new file mode 100644 index 0000000000..6aad5cfdc3 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/AuditMediaUploadRequestExecutor.java @@ -0,0 +1,47 @@ +package cn.binarywang.wx.miniapp.executor; + +import java.io.File; +import java.io.IOException; + +import me.chanjar.weixin.common.util.http.RequestExecutor; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.ResponseHandler; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import cn.binarywang.wx.miniapp.bean.WxMaAuditMediaUploadResult; + +/** + * 小程序 提审素材上传接口 + * 上传媒体文件请求执行器. + * 请求的参数是File, 返回的结果是String + * + * @author yangyh22 + * @since 2020/11/14 + */ +public abstract class AuditMediaUploadRequestExecutor implements RequestExecutor { + + protected RequestHttp requestHttp; + + public AuditMediaUploadRequestExecutor(RequestHttp requestHttp) { + this.requestHttp = requestHttp; + } + + @Override + public void execute(String uri, File data, ResponseHandler handler, WxType wxType) throws WxErrorException, IOException { + handler.handle(this.execute(uri, data, wxType)); + } + + public static RequestExecutor create(RequestHttp requestHttp) { + switch (requestHttp.getRequestType()) { + case APACHE_HTTP: + return new ApacheAuditMediaUploadRequestExecutor(requestHttp); + case JODD_HTTP: + return new JoddHttpAuditMediaUploadRequestExecutor(requestHttp); + case OK_HTTP: + return new OkHttpAuditMediaUploadRequestExecutor(requestHttp); + default: + return null; + } + } + +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpAuditMediaUploadRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpAuditMediaUploadRequestExecutor.java new file mode 100644 index 0000000000..cce7990983 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/JoddHttpAuditMediaUploadRequestExecutor.java @@ -0,0 +1,45 @@ +package cn.binarywang.wx.miniapp.executor; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import jodd.http.HttpConnectionProvider; +import jodd.http.HttpRequest; +import jodd.http.HttpResponse; +import jodd.http.ProxyInfo; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import cn.binarywang.wx.miniapp.bean.WxMaAuditMediaUploadResult; + +/** + * @author yangyh22 + * @since 2020/11/14 + */ +public class JoddHttpAuditMediaUploadRequestExecutor extends AuditMediaUploadRequestExecutor { + + public JoddHttpAuditMediaUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMaAuditMediaUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + HttpRequest request = HttpRequest.post(uri); + if (requestHttp.getRequestHttpProxy() != null) { + requestHttp.getRequestHttpClient().useProxy(requestHttp.getRequestHttpProxy()); + } + request.withConnectionProvider(requestHttp.getRequestHttpClient()); + request.form("media", file); + HttpResponse response = request.send(); + response.charset(StandardCharsets.UTF_8.name()); + + String responseContent = response.bodyText(); + WxError error = WxError.fromJson(responseContent, wxType); + if (error.getErrorCode() != 0) { + throw new WxErrorException(error); + } + return WxMaAuditMediaUploadResult.fromJson(responseContent); + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpAuditMediaUploadRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpAuditMediaUploadRequestExecutor.java new file mode 100644 index 0000000000..808f16d838 --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/OkHttpAuditMediaUploadRequestExecutor.java @@ -0,0 +1,49 @@ +package cn.binarywang.wx.miniapp.executor; + +import java.io.File; +import java.io.IOException; + +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.http.RequestHttp; +import cn.binarywang.wx.miniapp.bean.WxMaAuditMediaUploadResult; +import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +/** + * @author yangyh22 + * @since 2020/11/14 + */ +public class OkHttpAuditMediaUploadRequestExecutor extends AuditMediaUploadRequestExecutor { + + public OkHttpAuditMediaUploadRequestExecutor(RequestHttp requestHttp) { + super(requestHttp); + } + + @Override + public WxMaAuditMediaUploadResult execute(String uri, File file, WxType wxType) throws WxErrorException, IOException { + + RequestBody body = new MultipartBody.Builder() + .setType(MediaType.parse("multipart/form-data")) + .addFormDataPart("media", + file.getName(), + RequestBody.create(MediaType.parse("application/octet-stream"), file)) + .build(); + 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 WxMaAuditMediaUploadResult.fromJson(responseContent); + } + +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/QrcodeBytesRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeBytesRequestExecutor.java similarity index 98% rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/QrcodeBytesRequestExecutor.java rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeBytesRequestExecutor.java index bd473fb21c..ab2d262f2c 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/QrcodeBytesRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeBytesRequestExecutor.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.miniapp.util; +package cn.binarywang.wx.miniapp.executor; import cn.binarywang.wx.miniapp.bean.AbstractWxMaQrcodeWrapper; import me.chanjar.weixin.common.enums.WxType; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeFileRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeFileRequestExecutor.java new file mode 100644 index 0000000000..6580678efb --- /dev/null +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeFileRequestExecutor.java @@ -0,0 +1,78 @@ +package cn.binarywang.wx.miniapp.executor; + +import cn.binarywang.wx.miniapp.bean.AbstractWxMaQrcodeWrapper; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxError; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.util.fs.FileUtils; +import me.chanjar.weixin.common.util.http.RequestHttp; +import me.chanjar.weixin.common.util.http.apache.InputStreamResponseHandler; +import me.chanjar.weixin.common.util.http.apache.Utf8ResponseHandler; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Paths; +import java.util.UUID; + +/** + * @author gentryhuang + */ +public class QrcodeFileRequestExecutor extends QrcodeRequestExecutor { + /** + * 二维码生成的文件路径,例如: /var/temp + */ + private final String filePath; + + public QrcodeFileRequestExecutor(RequestHttp requestHttp, String filePath) { + super(requestHttp); + this.filePath = filePath; + } + + /** + * 执行http请求. + * + * @param uri uri + * @param qrcodeWrapper 数据 + * @param wxType 微信模块类型 + * @return 响应结果 + * @throws WxErrorException 自定义异常 + * @throws IOException io异常 + */ + @Override + public File execute(String uri, AbstractWxMaQrcodeWrapper qrcodeWrapper, WxType wxType) throws WxErrorException, IOException { + HttpPost httpPost = new HttpPost(uri); + if (requestHttp.getRequestHttpProxy() != null) { + httpPost.setConfig( + RequestConfig.custom().setProxy(requestHttp.getRequestHttpProxy()).build() + ); + } + + httpPost.setEntity(new StringEntity(qrcodeWrapper.toJson(), ContentType.APPLICATION_JSON)); + + try (final CloseableHttpResponse response = requestHttp.getRequestHttpClient().execute(httpPost); + final InputStream inputStream = InputStreamResponseHandler.INSTANCE.handleResponse(response)) { + Header[] contentTypeHeader = response.getHeaders("Content-Type"); + if (contentTypeHeader != null && contentTypeHeader.length > 0 + && ContentType.APPLICATION_JSON.getMimeType() + .equals(ContentType.parse(contentTypeHeader[0].getValue()).getMimeType())) { + String responseContent = Utf8ResponseHandler.INSTANCE.handleResponse(response); + throw new WxErrorException(WxError.fromJson(responseContent, wxType)); + } + if (StringUtils.isBlank(filePath)) { + return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg"); + } + + return FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), "jpg", Paths.get(filePath).toFile()); + } finally { + httpPost.releaseConnection(); + } + } +} diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/QrcodeRequestExecutor.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeRequestExecutor.java similarity index 98% rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/QrcodeRequestExecutor.java rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeRequestExecutor.java index d3b764ff1a..83e710dc10 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/QrcodeRequestExecutor.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/executor/QrcodeRequestExecutor.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.miniapp.util; +package cn.binarywang.wx.miniapp.executor; import cn.binarywang.wx.miniapp.bean.AbstractWxMaQrcodeWrapper; import me.chanjar.weixin.common.enums.WxType; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/WxMaGsonBuilder.java similarity index 94% rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/WxMaGsonBuilder.java index 21b582d5bd..e6f6842fa2 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaGsonBuilder.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/WxMaGsonBuilder.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.miniapp.util.json; +package cn.binarywang.wx.miniapp.json; import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; @@ -7,6 +7,7 @@ import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitDistribution; import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest; import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution; +import cn.binarywang.wx.miniapp.json.adaptor.*; import com.google.gson.Gson; import com.google.gson.GsonBuilder; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeCommitRequestGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaCodeCommitRequestGsonAdapter.java old mode 100755 new mode 100644 similarity index 90% rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeCommitRequestGsonAdapter.java rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaCodeCommitRequestGsonAdapter.java index 410f86ca1f..accf80fc93 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeCommitRequestGsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaCodeCommitRequestGsonAdapter.java @@ -1,6 +1,7 @@ -package cn.binarywang.wx.miniapp.util.json; +package cn.binarywang.wx.miniapp.json.adaptor; import cn.binarywang.wx.miniapp.bean.code.WxMaCodeCommitRequest; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonSerializationContext; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeVersionDistributionGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaCodeVersionDistributionGsonAdapter.java similarity index 97% rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeVersionDistributionGsonAdapter.java rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaCodeVersionDistributionGsonAdapter.java index 027ca6a959..018be6b046 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaCodeVersionDistributionGsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaCodeVersionDistributionGsonAdapter.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.miniapp.util.json; +package cn.binarywang.wx.miniapp.json.adaptor; import cn.binarywang.wx.miniapp.bean.code.WxMaCodeVersionDistribution; import com.google.gson.JsonArray; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaRetainInfoGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaRetainInfoGsonAdapter.java similarity index 97% rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaRetainInfoGsonAdapter.java rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaRetainInfoGsonAdapter.java index a51972b4bd..2e71f9eb4e 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaRetainInfoGsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaRetainInfoGsonAdapter.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.miniapp.util.json; +package cn.binarywang.wx.miniapp.json.adaptor; import cn.binarywang.wx.miniapp.bean.analysis.WxMaRetainInfo; import com.google.gson.JsonArray; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaSubscribeMessageGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaSubscribeMessageGsonAdapter.java similarity index 96% rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaSubscribeMessageGsonAdapter.java rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaSubscribeMessageGsonAdapter.java index 89f8bad8d3..ac877f8b21 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaSubscribeMessageGsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaSubscribeMessageGsonAdapter.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.miniapp.util.json; +package cn.binarywang.wx.miniapp.json.adaptor; import cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage; import com.google.gson.JsonElement; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaUniformMessageGsonAdapter.java similarity index 98% rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapter.java rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaUniformMessageGsonAdapter.java index 75ccd68aaa..3f81914d0c 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaUniformMessageGsonAdapter.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.miniapp.util.json; +package cn.binarywang.wx.miniapp.json.adaptor; import java.lang.reflect.Type; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUserPortraitGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaUserPortraitGsonAdapter.java similarity index 98% rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUserPortraitGsonAdapter.java rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaUserPortraitGsonAdapter.java index b8a7c448ff..c99fd67ba3 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaUserPortraitGsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaUserPortraitGsonAdapter.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.miniapp.util.json; +package cn.binarywang.wx.miniapp.json.adaptor; import cn.binarywang.wx.miniapp.bean.analysis.WxMaUserPortrait; import com.google.gson.JsonArray; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaVisitDistributionGsonAdapter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaVisitDistributionGsonAdapter.java similarity index 97% rename from weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaVisitDistributionGsonAdapter.java rename to weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaVisitDistributionGsonAdapter.java index 0fc79d44bd..74ae61821b 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/json/WxMaVisitDistributionGsonAdapter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/json/adaptor/WxMaVisitDistributionGsonAdapter.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.miniapp.util.json; +package cn.binarywang.wx.miniapp.json.adaptor; import cn.binarywang.wx.miniapp.bean.analysis.WxMaVisitDistribution; import com.google.gson.JsonArray; diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java index e932da641d..031c688c52 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouter.java @@ -45,7 +45,7 @@ public WxMaMessageRouter(WxMaService wxMaService) { this.wxMaService = wxMaService; ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("WxMaMessageRouter-pool-%d").build(); this.executorService = new ThreadPoolExecutor(DEFAULT_THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_SIZE, - 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), namedThreadFactory); + 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), namedThreadFactory); this.sessionManager = new StandardSessionManager(); this.exceptionHandler = new LogExceptionHandler(); this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); @@ -88,11 +88,8 @@ private WxMaXmlOutMessage route(final WxMaMessage wxMessage, final Map { + rule.service(wxMessage, context, WxMaMessageRouter.this.wxMaService, WxMaMessageRouter.this.sessionManager, WxMaMessageRouter.this.exceptionHandler); }) ); } else { @@ -104,18 +101,15 @@ public void run() { } if (futures.size() > 0) { - this.executorService.submit(new Runnable() { - @Override - public void run() { - for (Future future : futures) { - try { - future.get(); - WxMaMessageRouter.this.log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUser()); - // 异步操作结束,session访问结束 - sessionEndAccess(wxMessage); - } catch (InterruptedException | ExecutionException e) { - WxMaMessageRouter.this.log.error("Error happened when wait task finish", e); - } + this.executorService.submit(() -> { + for (Future future : futures) { + try { + future.get(); + WxMaMessageRouter.this.log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUser()); + // 异步操作结束,session访问结束 + sessionEndAccess(wxMessage); + } catch (InterruptedException | ExecutionException e) { + WxMaMessageRouter.this.log.error("Error happened when wait task finish", e); } } }); @@ -124,7 +118,7 @@ public void run() { } public WxMaXmlOutMessage route(final WxMaMessage wxMessage) { - return this.route(wxMessage, new HashMap(2)); + return this.route(wxMessage, new HashMap<>(2)); } private boolean isMsgDuplicated(WxMaMessage wxMessage) { diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java index 41f3e99574..99181e0434 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/message/WxMaMessageRouterRule.java @@ -6,10 +6,7 @@ import me.chanjar.weixin.common.error.WxErrorException; import me.chanjar.weixin.common.session.WxSessionManager; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Pattern; /** @@ -135,9 +132,7 @@ public WxMaMessageRouterRule interceptor(WxMaMessageInterceptor interceptor) { public WxMaMessageRouterRule interceptor(WxMaMessageInterceptor interceptor, WxMaMessageInterceptor... otherInterceptors) { this.interceptors.add(interceptor); if (otherInterceptors != null && otherInterceptors.length > 0) { - for (WxMaMessageInterceptor i : otherInterceptors) { - this.interceptors.add(i); - } + Collections.addAll(this.interceptors, otherInterceptors); } return this; } @@ -155,9 +150,7 @@ public WxMaMessageRouterRule handler(WxMaMessageHandler handler) { public WxMaMessageRouterRule handler(WxMaMessageHandler handler, WxMaMessageHandler... otherHandlers) { this.handlers.add(handler); if (otherHandlers != null && otherHandlers.length > 0) { - for (WxMaMessageHandler i : otherHandlers) { - this.handlers.add(i); - } + Collections.addAll(this.handlers, otherHandlers); } return this; } diff --git a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java index 6913541de7..f1a05d001a 100644 --- a/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java +++ b/weixin-java-miniapp/src/main/java/cn/binarywang/wx/miniapp/util/crypt/WxMaCryptUtils.java @@ -12,6 +12,7 @@ import com.google.common.base.CharMatcher; import com.google.common.io.BaseEncoding; +import me.chanjar.weixin.common.error.WxRuntimeException; import org.apache.commons.codec.binary.Base64; import org.bouncycastle.jce.provider.BouncyCastleProvider; @@ -47,7 +48,7 @@ public static String decrypt(String sessionKey, String encryptedData, String ivS return new String(PKCS7Encoder.decode(cipher.doFinal(Base64.decodeBase64(encryptedData))), UTF_8); } catch (Exception e) { - throw new RuntimeException("AES解密失败!", e); + throw new WxRuntimeException("AES解密失败!", e); } } @@ -78,7 +79,7 @@ public static String decryptAnotherWay(String sessionKey, String encryptedData, cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(Base64.decodeBase64(ivStr.getBytes(UTF_8)))); return new String(cipher.doFinal(Base64.decodeBase64(encryptedData.getBytes(UTF_8))), UTF_8); } catch (Exception e) { - throw new RuntimeException("AES解密失败!", e); + throw new WxRuntimeException("AES解密失败!", e); } } diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImplTest.java index 6991ad9c25..bf6e23797e 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImplTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaExpressServiceImplTest.java @@ -7,7 +7,7 @@ import cn.binarywang.wx.miniapp.bean.express.result.WxMaExpressOrderInfoResult; import cn.binarywang.wx.miniapp.constant.WxMaConstants; import cn.binarywang.wx.miniapp.test.ApiTestModule; -import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; import com.google.inject.Inject; import me.chanjar.weixin.common.error.WxErrorException; import org.apache.commons.lang3.StringUtils; diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImplTest.java index 769d82919e..af068777ee 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImplTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveGoodsServiceImplTest.java @@ -1,8 +1,8 @@ package cn.binarywang.wx.miniapp.api.impl; import cn.binarywang.wx.miniapp.api.WxMaService; -import cn.binarywang.wx.miniapp.bean.WxMaLiveInfo; -import cn.binarywang.wx.miniapp.bean.WxMaLiveResult; +import cn.binarywang.wx.miniapp.bean.live.WxMaLiveGoodInfo; +import cn.binarywang.wx.miniapp.bean.live.WxMaLiveResult; import cn.binarywang.wx.miniapp.test.ApiTestModule; import com.google.inject.Inject; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; @@ -31,7 +31,7 @@ public void addGoods() throws Exception { //上传临时素材 WxMediaUploadResult mediaUpload = this.wxService.getMediaService().uploadMedia("image", new File("E:\\1.png")); - WxMaLiveInfo.Goods goods = new WxMaLiveInfo.Goods(); + WxMaLiveGoodInfo goods = new WxMaLiveGoodInfo(); goods.setCoverImgUrl(mediaUpload.getMediaId()); goods.setName("宫廷奢华真丝四件套"); goods.setPrice("1599"); @@ -64,7 +64,7 @@ public void deleteGoods() throws Exception { @Test public void updateGoods() throws Exception { - WxMaLiveInfo.Goods goods = new WxMaLiveInfo.Goods(); + WxMaLiveGoodInfo goods = new WxMaLiveGoodInfo(); goods.setGoodsId(8); goods.setName("宫廷奢华真丝四件套"); goods.setCoverImgUrl("http://mmbiz.qpic.cn/mmbiz_png/omYktZNGamuUQE0WPVfqdnLV61JDhluXOac7PiaoZeticFpcR7wvicC0aXUC2VXkl7r1gN0QSKosv2satn6oCFeiaQ/0"); diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveServiceImplTest.java index e92913366a..b9a5b94121 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveServiceImplTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaLiveServiceImplTest.java @@ -1,8 +1,9 @@ package cn.binarywang.wx.miniapp.api.impl; import cn.binarywang.wx.miniapp.api.WxMaService; -import cn.binarywang.wx.miniapp.bean.WxMaLiveInfo; -import cn.binarywang.wx.miniapp.bean.WxMaLiveResult; +import cn.binarywang.wx.miniapp.bean.live.WxMaCreateRoomResult; +import cn.binarywang.wx.miniapp.bean.live.WxMaLiveResult; +import cn.binarywang.wx.miniapp.bean.live.WxMaLiveRoomInfo; import cn.binarywang.wx.miniapp.test.ApiTestModule; import com.google.inject.Inject; import me.chanjar.weixin.common.bean.result.WxMediaUploadResult; @@ -14,6 +15,7 @@ import java.util.Calendar; import java.util.List; +import static org.assertj.core.api.Assertions.assertThat; import static org.testng.Assert.assertNotNull; /** @@ -33,24 +35,68 @@ public void createRoom() throws Exception { //上传临时素材 WxMediaUploadResult mediaUpload = this.wxService.getMediaService().uploadMedia("image", new File("E:\\1.png")); - WxMaLiveInfo.RoomInfo roomInfo = new WxMaLiveInfo.RoomInfo(); + WxMaLiveRoomInfo roomInfo = new WxMaLiveRoomInfo(); roomInfo.setName("订阅通知直播间"); roomInfo.setCoverImg(mediaUpload.getMediaId()); Calendar c = Calendar.getInstance(); - c.set(2020, Calendar.SEPTEMBER, 10, 8, 0); + c.set(2020, Calendar.DECEMBER, 10, 8, 0); roomInfo.setStartTime(c.getTimeInMillis() / 1000); - c.set(2020, Calendar.SEPTEMBER, 10, 12, 0); + c.set(2020, Calendar.DECEMBER, 10, 12, 0); roomInfo.setEndTime(c.getTimeInMillis() / 1000); roomInfo.setAnchorName("鹏军_专业小程序开发"); roomInfo.setAnchorWechat("pengjun939961241"); + roomInfo.setCreaterWechat("pengjun939961241"); roomInfo.setShareImg(mediaUpload.getMediaId()); roomInfo.setType(1); roomInfo.setScreenType(1); roomInfo.setCloseLike(0); roomInfo.setCloseGoods(0); roomInfo.setCloseComment(0); - Integer roomId = this.wxService.getLiveService().createRoom(roomInfo); - System.out.println(roomId); + WxMaCreateRoomResult result = this.wxService.getLiveService().createRoom(roomInfo); + assertNotNull(result); + assertThat(result.getRoomId()).isNotNull(); + } + + @Test + public void deletRoom() throws Exception { + this.wxService.getLiveService().deleteRoom(29); + } + + @Test + public void editRoom() throws Exception { + //上传临时素材 +// WxMediaUploadResult mediaUpload = this.wxService.getMediaService().uploadMedia("image", new File("E:\\1.png")); + + WxMaLiveRoomInfo roomInfo = new WxMaLiveRoomInfo(); + roomInfo.setId(39); + roomInfo.setName("修改订阅通知直播间"); + roomInfo.setCoverImg("http://mmbiz.qpic.cn/mmbiz_png/omYktZNGamuBLBYlP2FjpIL2AHoiayH8HXeZRibtXDMesHn5aevEaM4etUVwfnX1HHqrXBDY3KPgT8MIlqbtqX8Q/0"); + Calendar c = Calendar.getInstance(); + c.set(2021, Calendar.SEPTEMBER, 10, 8, 0); + roomInfo.setStartTime(c.getTimeInMillis() / 1000); + c.set(2021, Calendar.SEPTEMBER, 10, 12, 0); + roomInfo.setEndTime(c.getTimeInMillis() / 1000); + roomInfo.setAnchorName("鹏军_专业小程序开发"); + roomInfo.setAnchorWechat("pengjun939961241"); + roomInfo.setShareImg("http://mmbiz.qpic.cn/mmbiz_png/omYktZNGamuBLBYlP2FjpIL2AHoiayH8HXeZRibtXDMesHn5aevEaM4etUVwfnX1HHqrXBDY3KPgT8MIlqbtqX8Q/0"); + roomInfo.setType(1); + roomInfo.setScreenType(1); + roomInfo.setCloseLike(0); + roomInfo.setCloseGoods(0); + roomInfo.setCloseComment(0); + boolean editRoom = this.wxService.getLiveService().editRoom(roomInfo); + System.out.println(editRoom); + } + @Test + public void getPushUrl() throws Exception { + String result = this.wxService.getLiveService().getPushUrl(39); + System.out.println(result); + } + + @Test + public void getSharedCode() throws Exception { + String result = this.wxService.getLiveService().getSharedCode(39, null); + System.out.println(result); } @Test diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImplTest.java index cf127970fd..1946a1fcfd 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImplTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaMsgServiceImplTest.java @@ -12,9 +12,6 @@ import com.google.inject.Inject; import me.chanjar.weixin.common.error.WxErrorException; -import java.text.SimpleDateFormat; -import java.util.Date; - import static org.assertj.core.api.Assertions.assertThat; /** @@ -47,8 +44,8 @@ public void testSendSubscribeMsg() throws WxErrorException { WxMaSubscribeMessage message = new WxMaSubscribeMessage(); message.setTemplateId(config.getTemplateId()); message.setToUser(config.getOpenid()); - message.setLang(WxMaConstants.MiniprogramLang.ZH_CN); - message.setMiniprogramState(WxMaConstants.MiniprogramState.FORMAL); + message.setLang(WxMaConstants.MiniProgramLang.ZH_CN); + message.setMiniprogramState(WxMaConstants.MiniProgramState.FORMAL); message.addData(new WxMaSubscribeMessage.Data("thing1", "苹果到货啦")); message.addData(new WxMaSubscribeMessage.Data("amount3", "¥5")); message.addData(new WxMaSubscribeMessage.Data("thing5", "记得领取哦")); diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaQrcodeServiceImplTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaQrcodeServiceImplTest.java index e73fec4270..e6c5969441 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaQrcodeServiceImplTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/api/impl/WxMaQrcodeServiceImplTest.java @@ -55,4 +55,22 @@ public void testCreateWxaCodeUnlimitBytes() throws WxErrorException { final byte[] wxCode = this.wxService.getQrcodeService().createWxaCodeUnlimitBytes("111", null, 122, true, null, false); assertThat(wxCode).isNotNull(); } + + @Test + public void testCreateQrcodeByFile() throws WxErrorException { + final File qrCode = this.wxService.getQrcodeService().createQrcode("111", "/opt/logs"); + assertThat(qrCode).isNotNull(); + } + + @Test + public void testCreateWxaCodeByFile() throws WxErrorException { + final File wxCode = this.wxService.getQrcodeService().createWxaCode("111", "/opt/logs"); + assertThat(wxCode).isNotNull(); + } + + @Test + public void testCreateQrcodeUnlimitByFile() throws WxErrorException { + final File wxCode = this.wxService.getQrcodeService().createWxaCodeUnlimit("111",null,"/opt/logs"); + assertThat(wxCode).isNotNull(); + } } diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapterTest.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/json/WxMaUniformMessageGsonAdapterTest.java similarity index 98% rename from weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapterTest.java rename to weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/json/WxMaUniformMessageGsonAdapterTest.java index 1ae60070cd..7b19136aff 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/util/json/WxMaUniformMessageGsonAdapterTest.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/json/WxMaUniformMessageGsonAdapterTest.java @@ -1,4 +1,4 @@ -package cn.binarywang.wx.miniapp.util.json; +package cn.binarywang.wx.miniapp.json; import cn.binarywang.wx.miniapp.bean.WxMaTemplateData; import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; diff --git a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/test/ApiTestModule.java b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/test/ApiTestModule.java index 267eb70ca3..a9f5def789 100644 --- a/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/test/ApiTestModule.java +++ b/weixin-java-miniapp/src/test/java/cn/binarywang/wx/miniapp/test/ApiTestModule.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.util.concurrent.locks.ReentrantLock; +import me.chanjar.weixin.common.error.WxRuntimeException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,7 +24,7 @@ public class ApiTestModule implements Module { public void configure(Binder binder) { try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(TEST_CONFIG_XML)) { if (inputStream == null) { - throw new RuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成"); + throw new WxRuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成"); } TestConfig config = TestConfig.fromXml(inputStream); config.setAccessTokenLock(new ReentrantLock()); diff --git a/weixin-java-mp/pom.xml b/weixin-java-mp/pom.xml index a89664d469..5e5815661e 100644 --- a/weixin-java-mp/pom.xml +++ b/weixin-java-mp/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 3.9.0 + 4.0.0 weixin-java-mp @@ -115,7 +115,7 @@ 3.5.1 - cn.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor + com.github.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java index f0bd0b8f04..a9eac896d7 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpCardService.java @@ -16,16 +16,16 @@ public interface WxMpCardService { /** * 得到WxMpService. * - * @return WxMpService + * @return WxMpService wx mp service */ WxMpService getWxMpService(); /** * 获得卡券api_ticket,不强制刷新卡券api_ticket. * - * @return 卡券api_ticket + * @return 卡券api_ticket card api ticket * @throws WxErrorException 异常 - * @see #getCardApiTicket(boolean) + * @see #getCardApiTicket(boolean) #getCardApiTicket(boolean) */ String getCardApiTicket() throws WxErrorException; @@ -38,7 +38,7 @@ public interface WxMpCardService { *

* * @param forceRefresh 强制刷新 - * @return 卡券api_ticket + * @return 卡券api_ticket card api ticket * @throws WxErrorException 异常 */ String getCardApiTicket(boolean forceRefresh) throws WxErrorException; @@ -52,9 +52,8 @@ public interface WxMpCardService { * .9F.E6.88.90.E7.AE.97.E6.B3.95 *
* - * @param optionalSignParam 参与签名的参数数组。可以为下列字段:app_id, card_id, card_type, code, openid, location_id - *
注意:当做wx.chooseCard调用时,必须传入app_id参与签名,否则会造成签名失败导致拉取卡券列表为空 - * @return 卡券Api签名对象 + * @param optionalSignParam 参与签名的参数数组。可以为下列字段:app_id, card_id, card_type, code, openid, location_id
注意:当做wx.chooseCard调用时,必须传入app_id参与签名,否则会造成签名失败导致拉取卡券列表为空 + * @return 卡券Api签名对象 wx card api signature * @throws WxErrorException 异常 */ WxCardApiSignature createCardApiSignature(String... optionalSignParam) throws WxErrorException; @@ -63,7 +62,7 @@ public interface WxMpCardService { * 卡券Code解码. * * @param encryptCode 加密Code,通过JSSDK的chooseCard接口获得 - * @return 解密后的Code + * @return 解密后的Code string * @throws WxErrorException 异常 */ String decryptCardCode(String encryptCode) throws WxErrorException; @@ -75,7 +74,7 @@ public interface WxMpCardService { * @param cardId 卡券ID代表一类卡券 * @param code 单张卡券的唯一标准 * @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同 - * @return WxMpCardResult对象 + * @return WxMpCardResult对象 wx mp card result * @throws WxErrorException 异常 */ WxMpCardResult queryCardCode(String cardId, String code, boolean checkConsume) throws WxErrorException; @@ -84,7 +83,7 @@ public interface WxMpCardService { * 卡券Code核销。核销失败会抛出异常 * * @param code 单张卡券的唯一标准 - * @return 调用返回的JSON字符串。可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。 + * @return 调用返回的JSON字符串 。可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。 * @throws WxErrorException 异常 */ String consumeCardCode(String code) throws WxErrorException; @@ -94,7 +93,7 @@ public interface WxMpCardService { * * @param code 单张卡券的唯一标准 * @param cardId 当自定义Code卡券时需要传入card_id - * @return 调用返回的JSON字符串。可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。 + * @return 调用返回的JSON字符串 。可用 com.google.gson.JsonParser#parse 等方法直接取JSON串中的errcode等信息。 * @throws WxErrorException 异常 */ String consumeCardCode(String code, String cardId) throws WxErrorException; @@ -117,9 +116,7 @@ public interface WxMpCardService { * 详见 https://mp.weixin.qq.com/wiki/14/8dd77aeaee85f922db5f8aa6386d385e.html#.E6.9F.A5.E7.9C.8B.E5.8D.A1.E5.88.B8.E8.AF.A6.E6.83.85 * * @param cardId 卡券的ID - * @return 返回的卡券详情JSON字符串 - *
[注] 由于返回的JSON格式过于复杂,难以定义其对应格式的Bean并且难以维护,因此只返回String格式的JSON串。 - *
可由 com.google.gson.JsonParser#parse 等方法直接取JSON串中的某个字段。 + * @return 返回的卡券详情JSON字符串
[注] 由于返回的JSON格式过于复杂,难以定义其对应格式的Bean并且难以维护,因此只返回String格式的JSON串。
可由 com.google.gson.JsonParser#parse 等方法直接取JSON串中的某个字段。 * @throws WxErrorException 异常 */ String getCardDetail(String cardId) throws WxErrorException; @@ -128,7 +125,7 @@ public interface WxMpCardService { * 添加测试白名单. * * @param openid 用户的openid - * @return string + * @return string string * @throws WxErrorException 异常 */ String addTestWhiteList(String openid) throws WxErrorException; @@ -137,7 +134,7 @@ public interface WxMpCardService { * 创建卡券. * * @param cardCreateMessage 请求 - * @return result + * @return result wx mp card create result * @throws WxErrorException 异常 */ WxMpCardCreateResult createCard(WxMpCardCreateRequest cardCreateMessage) throws WxErrorException; @@ -147,7 +144,7 @@ public interface WxMpCardService { * * @param cardId 卡券编号 * @param outerStr 二维码标识 - * @return WxMpCardQrcodeCreateResult + * @return WxMpCardQrcodeCreateResult wx mp card qrcode create result * @throws WxErrorException 异常 */ WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr) throws WxErrorException; @@ -158,7 +155,7 @@ public interface WxMpCardService { * @param cardId 卡券编号 * @param outerStr 二维码标识 * @param expiresIn 指定二维码的有效时间,范围是60 ~ 1800秒。不填默认为365天有效 - * @return WxMpCardQrcodeCreateResult + * @return WxMpCardQrcodeCreateResult wx mp card qrcode create result * @throws WxErrorException 异常 */ WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int expiresIn) throws WxErrorException; @@ -169,10 +166,10 @@ public interface WxMpCardService { * @param cardId 卡券编号 * @param outerStr 用户首次领卡时,会通过 领取事件推送 给商户; 对于会员卡的二维码,用户每次扫码打开会员卡后点击任何url,会将该值拼入url中,方便开发者定位扫码来源 * @param expiresIn 指定二维码的有效时间,范围是60 ~ 1800秒。不填默认为365天有效 - * @param isUniqueCode 指定下发二维码,生成的二维码随机分配一个code,领取后不可再次扫描。填写true或false。默认false,注意填写该字段时,卡券须通过审核且库存不为0。 - * @param code 卡券Code码,use_custom_code字段为true的卡券必须填写,非自定义code和导入code模式的卡券不必填写。 * @param openid 指定领取者的openid,只有该用户能领取。bind_openid字段为true的卡券必须填写,非指定openid不必填写。 - * @return WxMpCardQrcodeCreateResult + * @param code 卡券Code码,use_custom_code字段为true的卡券必须填写,非自定义code和导入code模式的卡券不必填写。 + * @param isUniqueCode 指定下发二维码,生成的二维码随机分配一个code,领取后不可再次扫描。填写true或false。默认false,注意填写该字段时,卡券须通过审核且库存不为0。 + * @return WxMpCardQrcodeCreateResult wx mp card qrcode create result * @throws WxErrorException 异常 */ WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int expiresIn, String openid, @@ -182,7 +179,7 @@ WxMpCardQrcodeCreateResult createQrcodeCard(String cardId, String outerStr, int * 创建卡券货架. * * @param createRequest 货架创建参数 - * @return WxMpCardLandingPageCreateResult + * @return WxMpCardLandingPageCreateResult wx mp card landing page create result * @throws WxErrorException 异常 */ WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateRequest createRequest) @@ -195,7 +192,7 @@ WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateReque * @param cardId 卡券编号 * @param code 用户会员卡号 * @param reason 设置为失效的原因 - * @return result + * @return result string * @throws WxErrorException 异常 */ String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException; @@ -204,17 +201,18 @@ WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateReque * 删除卡券接口. * * @param cardId 卡券id - * @return 删除结果 + * @return 删除结果 wx mp card delete result * @throws WxErrorException 异常 */ WxMpCardDeleteResult deleteCard(String cardId) throws WxErrorException; - /** * 导入自定义code(仅对自定义code商户) * * @param cardId 卡券id * @param codeList 需导入微信卡券后台的自定义code,上限为100个。 + * @return the wx mp card code deposit result + * @throws WxErrorException the wx error exception */ WxMpCardCodeDepositResult cardCodeDeposit(String cardId, List codeList) throws WxErrorException; @@ -222,15 +220,18 @@ WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateReque * 查询导入code数目接口 * * @param cardId 卡券id + * @return the wx mp card code deposit count result + * @throws WxErrorException the wx error exception */ WxMpCardCodeDepositCountResult cardCodeDepositCount(String cardId) throws WxErrorException; - /** * 核查code接口 * * @param cardId 卡券id * @param codeList 已经微信卡券后台的自定义code,上限为100个 + * @return the wx mp card code checkcode result + * @throws WxErrorException the wx error exception */ WxMpCardCodeCheckcodeResult cardCodeCheckcode(String cardId, List codeList) throws WxErrorException; @@ -238,6 +239,8 @@ WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateReque * 图文消息群发卡券获取内嵌html * * @param cardId 卡券id + * @return the wx mp card mpnews gethtml result + * @throws WxErrorException the wx error exception */ WxMpCardMpnewsGethtmlResult cardMpnewsGethtml(String cardId) throws WxErrorException; @@ -248,6 +251,7 @@ WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateReque * * @param cardId 卡券ID * @param changeValue 库存变更值,负值为减少库存 + * @throws WxErrorException the wx error exception */ void cardModifyStock(String cardId, Integer changeValue) throws WxErrorException; @@ -259,6 +263,7 @@ WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateReque * @param cardId 卡券ID * @param oldCode 需变更的Code码 * @param newCode 变更后的有效Code码 + * @throws WxErrorException the wx error exception */ void cardCodeUpdate(String cardId, String oldCode, String newCode) throws WxErrorException; @@ -268,6 +273,7 @@ WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateReque * * @param cardId 卡券ID * @param isOpen 是否开启买单功能,填true/false + * @throws WxErrorException the wx error exception */ void cardPaycellSet(String cardId, Boolean isOpen) throws WxErrorException; @@ -279,6 +285,7 @@ WxMpCardLandingPageCreateResult createLandingPage(WxMpCardLandingPageCreateReque * @param isOpen 是否开启自助核销功能 * @param needVerifyCod 用户核销时是否需要输入验证码, 填true/false, 默认为false * @param needRemarkAmount 用户核销时是否需要备注核销金额, 填true/false, 默认为false + * @throws WxErrorException the wx error exception */ void cardSelfConsumeCellSet(String cardId, Boolean isOpen, Boolean needVerifyCod, Boolean needRemarkAmount) throws WxErrorException; @@ -289,8 +296,8 @@ void cardSelfConsumeCellSet(String cardId, Boolean isOpen, * * @param openId 需要查询的用户openid * @param cardId 卡券ID。不填写时默认查询当前appid下的卡券 - * @return - * @throws WxErrorException + * @return user card list + * @throws WxErrorException the wx error exception */ WxUserCardListResult getUserCardList(String openId, String cardId) throws WxErrorException; diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpGuideService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpGuideService.java new file mode 100644 index 0000000000..e1427dbb67 --- /dev/null +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpGuideService.java @@ -0,0 +1,96 @@ +package me.chanjar.weixin.mp.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.bean.guide.WxMpGuideInfo; +import me.chanjar.weixin.mp.bean.guide.WxMpGuideList; + +/** + * 微信导购助手(现在叫对话能力)接口. + * + * @author Binary Wang + * @date 2020 -10-06 + */ +public interface WxMpGuideService { + /** + * 为服务号添加顾问 + *
+   * 请求地址: POST https://api.weixin.qq.com/cgi-bin/guide/addguideacct?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Shopping_Guide/guide-account/shopping-guide.addGuideAcct.html
+   * 
+ * + * @param account 顾问微信号(guide_account和guide_openid二选一,若同时请求,默认为guide_account) + * @param openid 顾问openid或者unionid(guide_account和guide_openid二选一) + * @param headImgUrl 顾问头像,头像url只能用《上传图文消息内的图片获取URL》 me.chanjar.weixin.mp.api.impl.WxMpMaterialServiceImpl#mediaImgUpload(java.io.File) + * @param nickName 顾问昵称 + * @throws WxErrorException . + */ + void addGuide(String account, String openid, String headImgUrl, String nickName) throws WxErrorException; + + /** + * 为服务号添加顾问 + *
+   * 请求地址: POST https://api.weixin.qq.com/cgi-bin/guide/addguideacct?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Shopping_Guide/guide-account/shopping-guide.addGuideAcct.html
+   * 
+ * + * @param guideInfo 顾问信息 + * @throws WxErrorException . + */ + void addGuide(WxMpGuideInfo guideInfo) throws WxErrorException; + + /** + * 修改顾问的昵称或头像 + *
+   * 请求地址: POST https://api.weixin.qq.com/cgi-bin/guide/updateguideacct?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Shopping_Guide/guide-account/shopping-guide.updateGuideAcct.html
+   * 
+ * + * @param guideInfo 顾问信息 + * @throws WxErrorException . + */ + void updateGuide(WxMpGuideInfo guideInfo) throws WxErrorException; + + /** + * 获取顾问信息 + * + *
+   * 请求地址:  POST https://api.weixin.qq.com/cgi-bin/guide/getguideacct?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Shopping_Guide/guide-account/shopping-guide.getGuideAcct.html
+   * 
+ * + * @param account 顾问微信号(guide_account和guide_openid二选一,若同时请求,默认为guide_account) + * @param openid 顾问openid或者unionid(guide_account和guide_openid二选一) + * @return 顾问信息 + * @throws WxErrorException . + */ + WxMpGuideInfo getGuide(String account, String openid) throws WxErrorException; + + /** + * 删除顾问 + * + *
+   * 请求地址:  POST https://api.weixin.qq.com/cgi-bin/guide/delguideacct?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Shopping_Guide/guide-account/shopping-guide.delGuideAcct.html
+   * 
+ * + * @param account 顾问微信号(guide_account和guide_openid二选一,若同时请求,默认为guide_account) + * @param openid 顾问openid或者unionid(guide_account和guide_openid二选一) + * @throws WxErrorException . + */ + void delGuide(String account, String openid) throws WxErrorException; + + /** + * 获取服务号顾问列表 + * + *
+   * 请求地址: POST https://api.weixin.qq.com/cgi-bin/guide/getguideacctlist?access_token=ACCESS_TOKEN
+   * 文档地址:https://developers.weixin.qq.com/doc/offiaccount/Shopping_Guide/guide-account/shopping-guide.getGuideAcctList.html
+   * 
+ * + * @param page 分页页数,从0开始 + * @param num 每页数量 + * @return 顾问信息列表 + * @throws WxErrorException . + */ + WxMpGuideList listGuide(int page, int num) throws WxErrorException; +} diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java index 98ef7716f9..263305c0d0 100644 --- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java +++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouter.java @@ -1,18 +1,8 @@ package me.chanjar.weixin.mp.api; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import me.chanjar.weixin.common.api.WxErrorExceptionHandler; import me.chanjar.weixin.common.api.WxMessageDuplicateChecker; import me.chanjar.weixin.common.api.WxMessageInMemoryDuplicateChecker; @@ -23,6 +13,15 @@ import me.chanjar.weixin.common.util.LogExceptionHandler; import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.*; /** *
@@ -52,9 +51,10 @@
  *
  * @author Daniel Qian
  */
+@Slf4j
+@AllArgsConstructor
 public class WxMpMessageRouter {
   private static final int DEFAULT_THREAD_POOL_SIZE = 100;
-  protected final Logger log = LoggerFactory.getLogger(WxMpMessageRouter.class);
   private final List rules = new ArrayList<>();
 
   private final WxMpService wxMpService;
@@ -69,7 +69,9 @@ public class WxMpMessageRouter {
 
   public WxMpMessageRouter(WxMpService wxMpService) {
     this.wxMpService = wxMpService;
-    this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE);
+    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("WxMpMessageRouter-pool-%d").build();
+    this.executorService = new ThreadPoolExecutor(DEFAULT_THREAD_POOL_SIZE, DEFAULT_THREAD_POOL_SIZE,
+      0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), namedThreadFactory);
     this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker();
     this.sessionManager = new StandardSessionManager();
     this.exceptionHandler = new LogExceptionHandler();
@@ -183,7 +185,7 @@ public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage, final Map {
+            rule.service(wxMessage, context, mpService, WxMpMessageRouter.this.sessionManager, WxMpMessageRouter.this.exceptionHandler);
           })
         );
       } else {
         res = rule.service(wxMessage, context, mpService, this.sessionManager, this.exceptionHandler);
         // 在同步操作结束,session访问结束
-        this.log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUser());
+        log.debug("End session access: async=false, sessionId={}", wxMessage.getFromUser());
         sessionEndAccess(wxMessage);
       }
     }
 
-    if (futures.size() > 0) {
-      this.executorService.submit(new Runnable() {
-        @Override
-        public void run() {
-          for (Future future : futures) {
-            try {
-              future.get();
-              WxMpMessageRouter.this.log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUser());
-              // 异步操作结束,session访问结束
-              sessionEndAccess(wxMessage);
-            } catch (InterruptedException e) {
-              WxMpMessageRouter.this.log.error("Error happened when wait task finish", e);
-              Thread.currentThread().interrupt();
-            } catch (ExecutionException e) {
-              WxMpMessageRouter.this.log.error("Error happened when wait task finish", e);
-            }
-          }
-        }
-      });
+    if (futures.isEmpty()) {
+      return res;
     }
+
+    this.executorService.submit(() -> {
+      for (Future future : futures) {
+        try {
+          future.get();
+          log.debug("End session access: async=true, sessionId={}", wxMessage.getFromUser());
+          // 异步操作结束,session访问结束
+          sessionEndAccess(wxMessage);
+        } catch (InterruptedException e) {
+          log.error("Error happened when wait task finish", e);
+          Thread.currentThread().interrupt();
+        } catch (ExecutionException e) {
+          log.error("Error happened when wait task finish", e);
+        }
+      }
+    });
     return res;
   }
 
   public WxMpXmlOutMessage route(final WxMpXmlMessage wxMessage) {
-    return this.route(wxMessage, new HashMap(2));
+    return this.route(wxMessage, new HashMap<>(2));
   }
 
   public WxMpXmlOutMessage route(String appid, final WxMpXmlMessage wxMessage) {
-    return this.route(appid, wxMessage, new HashMap(2));
+    return this.route(appid, wxMessage, new HashMap<>(2));
   }
 
   private boolean isMsgDuplicated(WxMpXmlMessage wxMessage) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouterRule.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouterRule.java
index ad11e81b41..a742c196c9 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouterRule.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpMessageRouterRule.java
@@ -7,10 +7,7 @@
 import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
 import org.apache.commons.lang3.StringUtils;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.regex.Pattern;
 
 public class WxMpMessageRouterRule {
@@ -130,9 +127,7 @@ public WxMpMessageRouterRule interceptor(WxMpMessageInterceptor interceptor) {
   public WxMpMessageRouterRule interceptor(WxMpMessageInterceptor interceptor, WxMpMessageInterceptor... otherInterceptors) {
     this.interceptors.add(interceptor);
     if (otherInterceptors != null && otherInterceptors.length > 0) {
-      for (WxMpMessageInterceptor i : otherInterceptors) {
-        this.interceptors.add(i);
-      }
+      Collections.addAll(this.interceptors, otherInterceptors);
     }
     return this;
   }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
index aa7a872f39..fe8d2abf38 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/WxMpService.java
@@ -1,20 +1,20 @@
 package me.chanjar.weixin.mp.api;
 
-import me.chanjar.weixin.common.api.WxImgProcService;
-import me.chanjar.weixin.common.api.WxOcrService;
+import com.google.gson.JsonObject;
+import me.chanjar.weixin.common.service.WxImgProcService;
+import me.chanjar.weixin.common.service.WxOcrService;
 import me.chanjar.weixin.common.bean.WxJsapiSignature;
 import me.chanjar.weixin.common.bean.WxNetCheckResult;
 import me.chanjar.weixin.common.enums.TicketType;
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.service.WxOAuth2Service;
 import me.chanjar.weixin.common.service.WxService;
 import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.RequestHttp;
 import me.chanjar.weixin.mp.bean.WxMpSemanticQuery;
 import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo;
-import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
 import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult;
-import me.chanjar.weixin.mp.bean.result.WxMpUser;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 import me.chanjar.weixin.mp.enums.WxMpApiUrl;
 
@@ -35,16 +35,16 @@ public interface WxMpService extends WxService {
    * @param timestamp 时间戳
    * @param nonce     随机串
    * @param signature 签名
-   * @return 是否验证通过
+   * @return 是否验证通过 boolean
    */
   boolean checkSignature(String timestamp, String nonce, String signature);
 
   /**
    * 获取access_token, 不强制刷新access_token.
    *
-   * @return token
+   * @return token access token
    * @throws WxErrorException .
-   * @see #getAccessToken(boolean)
+   * @see #getAccessToken(boolean) #getAccessToken(boolean)
    */
   String getAccessToken() throws WxErrorException;
 
@@ -61,7 +61,7 @@ public interface WxMpService extends WxService {
    * 
* * @param forceRefresh 是否强制刷新 - * @return token + * @return token access token * @throws WxErrorException . */ String getAccessToken(boolean forceRefresh) throws WxErrorException; @@ -70,9 +70,9 @@ public interface WxMpService extends WxService { * 获得ticket,不强制刷新ticket. * * @param type ticket 类型 - * @return ticket + * @return ticket ticket * @throws WxErrorException . - * @see #getTicket(TicketType, boolean) + * @see #getTicket(TicketType, boolean) #getTicket(TicketType, boolean) */ String getTicket(TicketType type) throws WxErrorException; @@ -84,7 +84,7 @@ public interface WxMpService extends WxService { * * @param type ticket类型 * @param forceRefresh 强制刷新 - * @return ticket + * @return ticket ticket * @throws WxErrorException . */ String getTicket(TicketType type, boolean forceRefresh) throws WxErrorException; @@ -94,7 +94,7 @@ public interface WxMpService extends WxService { * * @return jsapi ticket * @throws WxErrorException . - * @see #getJsapiTicket(boolean) + * @see #getJsapiTicket(boolean) #getJsapiTicket(boolean) */ String getJsapiTicket() throws WxErrorException; @@ -120,7 +120,7 @@ public interface WxMpService extends WxService { *
* * @param url 地址 - * @return 生成的签名对象 + * @return 生成的签名对象 wx jsapi signature * @throws WxErrorException . */ WxJsapiSignature createJsapiSignature(String url) throws WxErrorException; @@ -132,7 +132,7 @@ public interface WxMpService extends WxService { *
* * @param longUrl 长url - * @return 生成的短地址 + * @return 生成的短地址 string * @throws WxErrorException . */ String shortUrl(String longUrl) throws WxErrorException; @@ -144,7 +144,7 @@ public interface WxMpService extends WxService { *
* * @param semanticQuery 查询条件 - * @return 查询结果 + * @return 查询结果 wx mp semantic query result * @throws WxErrorException . */ WxMpSemanticQueryResult semanticQuery(WxMpSemanticQuery semanticQuery) throws WxErrorException; @@ -169,7 +169,7 @@ public interface WxMpService extends WxService { * http://mp.weixin.qq.com/wiki/0/2ad4b6bfd29f30f71d39616c2a0fcedc.html *
* - * @return 微信服务器ip地址数组 + * @return 微信服务器ip地址数组 string [ ] * @throws WxErrorException . */ String[] getCallbackIP() throws WxErrorException; @@ -183,7 +183,7 @@ public interface WxMpService extends WxService { * * @param action 执行的检测动作 * @param operator 指定平台从某个运营商进行检测 - * @return 检测结果 + * @return 检测结果 wx net check result * @throws WxErrorException . */ WxNetCheckResult netCheck(String action, String operator) throws WxErrorException; @@ -204,7 +204,7 @@ public interface WxMpService extends WxService { * https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info?access_token=ACCESS_TOKEN *
* - * @return 公众号的自动回复规则 + * @return 公众号的自动回复规则 current auto reply info * @throws WxErrorException . */ WxMpCurrentAutoReplyInfo getCurrentAutoReplyInfo() throws WxErrorException; @@ -234,7 +234,7 @@ public interface WxMpService extends WxService { * @param executor 执行器 * @param url 接口地址 * @param data 参数数据 - * @return 结果 + * @return 结果 t * @throws WxErrorException 异常 */ T execute(RequestExecutor executor, String url, E data) throws WxErrorException; @@ -244,7 +244,7 @@ public interface WxMpService extends WxService { * * @param url 请求接口地址 * @param queryParam 参数 - * @return 接口响应字符串 + * @return 接口响应字符串 string * @throws WxErrorException 异常 */ String get(WxMpApiUrl url, String queryParam) throws WxErrorException; @@ -254,11 +254,21 @@ public interface WxMpService extends WxService { * * @param url 请求接口地址 * @param postData 请求参数json值 - * @return 接口响应字符串 + * @return 接口响应字符串 string * @throws WxErrorException 异常 */ String post(WxMpApiUrl url, String postData) throws WxErrorException; + /** + * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的POST请求. + * + * @param url 请求接口地址 + * @param jsonObject 请求参数json对象 + * @return 接口响应字符串 string + * @throws WxErrorException 异常 + */ + String post(WxMpApiUrl url, JsonObject jsonObject) throws WxErrorException; + /** *
    * Service没有实现某个API的时候,可以用这个,
@@ -271,7 +281,7 @@ public interface WxMpService extends WxService {
    * @param executor 执行器
    * @param url      接口地址
    * @param data     参数数据
-   * @return 结果
+   * @return 结果 t
    * @throws WxErrorException 异常
    */
    T execute(RequestExecutor executor, WxMpApiUrl url, E data) throws WxErrorException;
@@ -296,7 +306,7 @@ public interface WxMpService extends WxService {
   /**
    * 获取WxMpConfigStorage 对象.
    *
-   * @return WxMpConfigStorage
+   * @return WxMpConfigStorage wx mp config storage
    */
   WxMpConfigStorage getWxMpConfigStorage();
 
@@ -342,7 +352,7 @@ public interface WxMpService extends WxService {
    * 进行相应的公众号切换.
    *
    * @param mpId 公众号标识
-   * @return 切换是否成功
+   * @return 切换是否成功 boolean
    */
   boolean switchover(String mpId);
 
@@ -350,119 +360,119 @@ public interface WxMpService extends WxService {
    * 进行相应的公众号切换.
    *
    * @param mpId 公众号标识
-   * @return 切换成功,则返回当前对象,方便链式调用,否则抛出异常
+   * @return 切换成功 ,则返回当前对象,方便链式调用,否则抛出异常
    */
   WxMpService switchoverTo(String mpId);
 
   /**
    * 返回客服接口方法实现类,以方便调用其各个接口.
    *
-   * @return WxMpKefuService
+   * @return WxMpKefuService kefu service
    */
   WxMpKefuService getKefuService();
 
   /**
    * 返回素材相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpMaterialService
+   * @return WxMpMaterialService material service
    */
   WxMpMaterialService getMaterialService();
 
   /**
    * 返回菜单相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpMenuService
+   * @return WxMpMenuService menu service
    */
   WxMpMenuService getMenuService();
 
   /**
    * 返回用户相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpUserService
+   * @return WxMpUserService user service
    */
   WxMpUserService getUserService();
 
   /**
    * 返回用户标签相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpUserTagService
+   * @return WxMpUserTagService user tag service
    */
   WxMpUserTagService getUserTagService();
 
   /**
    * 返回二维码相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpQrcodeService
+   * @return WxMpQrcodeService qrcode service
    */
   WxMpQrcodeService getQrcodeService();
 
   /**
    * 返回卡券相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpCardService
+   * @return WxMpCardService card service
    */
   WxMpCardService getCardService();
 
   /**
    * 返回数据分析统计相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpDataCubeService
+   * @return WxMpDataCubeService data cube service
    */
   WxMpDataCubeService getDataCubeService();
 
   /**
    * 返回用户黑名单管理相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpUserBlacklistService
+   * @return WxMpUserBlacklistService black list service
    */
   WxMpUserBlacklistService getBlackListService();
 
   /**
    * 返回门店管理相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpStoreService
+   * @return WxMpStoreService store service
    */
   WxMpStoreService getStoreService();
 
   /**
    * 返回模板消息相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpTemplateMsgService
+   * @return WxMpTemplateMsgService template msg service
    */
   WxMpTemplateMsgService getTemplateMsgService();
 
   /**
    * 返回一次性订阅消息相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpSubscribeMsgService
+   * @return WxMpSubscribeMsgService subscribe msg service
    */
   WxMpSubscribeMsgService getSubscribeMsgService();
 
   /**
    * 返回硬件平台相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpDeviceService
+   * @return WxMpDeviceService device service
    */
   WxMpDeviceService getDeviceService();
 
   /**
    * 返回摇一摇周边相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpShakeService
+   * @return WxMpShakeService shake service
    */
   WxMpShakeService getShakeService();
 
   /**
    * 返回会员卡相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpMemberCardService
+   * @return WxMpMemberCardService member card service
    */
   WxMpMemberCardService getMemberCardService();
 
   /**
    * 返回营销相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpMarketingService
+   * @return WxMpMarketingService marketing service
    */
   WxMpMarketingService getMarketingService();
 
@@ -474,42 +484,42 @@ public interface WxMpService extends WxService {
   /**
    * 获取RequestHttp对象.
    *
-   * @return RequestHttp对象
+   * @return RequestHttp对象 request http
    */
   RequestHttp getRequestHttp();
 
   /**
    * 返回群发消息相关接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpMassMessageService
+   * @return WxMpMassMessageService mass message service
    */
   WxMpMassMessageService getMassMessageService();
 
   /**
    * 返回AI开放接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpAiOpenService
+   * @return WxMpAiOpenService ai open service
    */
   WxMpAiOpenService getAiOpenService();
 
   /**
    * 返回WIFI接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpWifiService
+   * @return WxMpWifiService wifi service
    */
   WxMpWifiService getWifiService();
 
   /**
    * 返回WIFI接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpWifiService
+   * @return WxMpWifiService ocr service
    */
   WxOcrService getOcrService();
 
   /**
    * 返回图像处理接口的实现类对象,以方便调用其各个接口.
    *
-   * @return WxImgProcService
+   * @return WxImgProcService img proc service
    */
   WxImgProcService getImgProcService();
 
@@ -649,7 +659,7 @@ public interface WxMpService extends WxService {
   /**
    * 返回评论数据管理接口方法的实现类对象,以方便调用其各个接口.
    *
-   * @return WxMpWifiService
+   * @return WxMpWifiService comment service
    */
   WxMpCommentService getCommentService();
 
@@ -673,4 +683,18 @@ public interface WxMpService extends WxService {
    * @param oAuth2Service the o auth 2 service
    */
   void setOAuth2Service(WxOAuth2Service oAuth2Service);
+
+  /**
+   * Gets guide service.
+   *
+   * @return the guide service
+   */
+  WxMpGuideService getGuideService();
+
+  /**
+   * Sets guide service.
+   *
+   * @param guideService the guide service
+   */
+  void setGuideService(WxMpGuideService guideService);
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
index f836cebaa6..0047535517 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImpl.java
@@ -8,8 +8,9 @@
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.common.api.WxImgProcService;
-import me.chanjar.weixin.common.api.WxOcrService;
+import me.chanjar.weixin.common.service.WxImgProcService;
+import me.chanjar.weixin.common.service.WxOcrService;
+import me.chanjar.weixin.common.bean.ToJson;
 import me.chanjar.weixin.common.bean.WxAccessToken;
 import me.chanjar.weixin.common.bean.WxJsapiSignature;
 import me.chanjar.weixin.common.bean.WxNetCheckResult;
@@ -17,6 +18,8 @@
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
+import me.chanjar.weixin.common.service.WxOAuth2Service;
 import me.chanjar.weixin.common.session.StandardSessionManager;
 import me.chanjar.weixin.common.session.WxSessionManager;
 import me.chanjar.weixin.common.util.DataUtils;
@@ -28,9 +31,7 @@
 import me.chanjar.weixin.mp.api.*;
 import me.chanjar.weixin.mp.bean.WxMpSemanticQuery;
 import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo;
-import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
 import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult;
-import me.chanjar.weixin.mp.bean.result.WxMpUser;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 import me.chanjar.weixin.mp.enums.WxMpApiUrl;
 import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder;
@@ -122,7 +123,11 @@ public abstract class BaseWxMpServiceImpl implements WxMpService, RequestH
 
   @Getter
   @Setter
-  private WxOAuth2Service oAuth2Service = new WxOAuth2ServiceImpl(this);
+  private WxMpGuideService guideService = new WxMpGuideServiceImpl(this);
+
+  @Getter
+  @Setter
+  private WxOAuth2Service oAuth2Service = new WxMpOAuth2ServiceImpl(this);
 
   private Map configStorageMap;
 
@@ -147,23 +152,26 @@ public String getTicket(TicketType type) throws WxErrorException {
 
   @Override
   public String getTicket(TicketType type, boolean forceRefresh) throws WxErrorException {
-    Lock lock = this.getWxMpConfigStorage().getTicketLock(type);
-    lock.lock();
-    try {
-      if (forceRefresh) {
-        this.getWxMpConfigStorage().expireTicket(type);
-      }
 
-      if (this.getWxMpConfigStorage().isTicketExpired(type)) {
-        String responseContent = execute(SimpleGetRequestExecutor.create(this),
-          GET_TICKET_URL.getUrl(this.getWxMpConfigStorage()) + type.getCode(), null);
-        JsonObject tmpJsonObject = GsonParser.parse(responseContent);
-        String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
-        int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
-        this.getWxMpConfigStorage().updateTicket(type, jsapiTicket, expiresInSeconds);
+    if (forceRefresh) {
+      this.getWxMpConfigStorage().expireTicket(type);
+    }
+
+    if (this.getWxMpConfigStorage().isTicketExpired(type)) {
+      Lock lock = this.getWxMpConfigStorage().getTicketLock(type);
+      lock.lock();
+      try {
+        if (this.getWxMpConfigStorage().isTicketExpired(type)) {
+          String responseContent = execute(SimpleGetRequestExecutor.create(this),
+            GET_TICKET_URL.getUrl(this.getWxMpConfigStorage()) + type.getCode(), null);
+          JsonObject tmpJsonObject = GsonParser.parse(responseContent);
+          String jsapiTicket = tmpJsonObject.get("ticket").getAsString();
+          int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
+          this.getWxMpConfigStorage().updateTicket(type, jsapiTicket, expiresInSeconds);
+        }
+      } finally {
+        lock.unlock();
       }
-    } finally {
-      lock.unlock();
     }
 
     return this.getWxMpConfigStorage().getTicket(type);
@@ -203,9 +211,8 @@ public String getAccessToken() throws WxErrorException {
   @Override
   public String shortUrl(String longUrl) throws WxErrorException {
     if (longUrl.contains("&access_token=")) {
-      throw new WxErrorException(WxError.builder().errorCode(-1)
-        .errorMsg("要转换的网址中存在非法字符{&access_token=},会导致微信接口报错,属于微信bug,请调整地址,否则不建议使用此方法!")
-        .build());
+      throw new WxErrorException("要转换的网址中存在非法字符{&access_token=}," +
+        "会导致微信接口报错,属于微信bug,请调整地址,否则不建议使用此方法!");
     }
 
     JsonObject o = new JsonObject();
@@ -280,6 +287,21 @@ public String post(WxMpApiUrl url, String postData) throws WxErrorException {
     return this.post(url.getUrl(this.getWxMpConfigStorage()), postData);
   }
 
+  @Override
+  public String post(WxMpApiUrl url, JsonObject jsonObject) throws WxErrorException {
+    return this.post(url.getUrl(this.getWxMpConfigStorage()), jsonObject.toString());
+  }
+
+  @Override
+  public String post(String url, ToJson obj) throws WxErrorException {
+    return this.post(url, obj.toJson());
+  }
+
+  @Override
+  public String post(String url, JsonObject jsonObject) throws WxErrorException {
+    return this.post(url, jsonObject.toString());
+  }
+
   @Override
   public String post(String url, Object obj) throws WxErrorException {
     return this.execute(SimplePostRequestExecutor.create(this), url, WxGsonBuilder.create().toJson(obj));
@@ -303,7 +325,7 @@ public  T execute(RequestExecutor executor, String uri, E data) thro
         if (retryTimes + 1 > this.maxRetryTimes) {
           log.warn("重试达到最大次数【{}】", maxRetryTimes);
           //最后一次重试失败后,直接抛出异常,不再等待
-          throw new RuntimeException("微信服务端异常,超出重试次数");
+          throw new WxRuntimeException("微信服务端异常,超出重试次数");
         }
 
         WxError error = e.getError();
@@ -314,7 +336,7 @@ public  T execute(RequestExecutor executor, String uri, E data) thro
             log.warn("微信系统繁忙,{} ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
             Thread.sleep(sleepMillis);
           } catch (InterruptedException e1) {
-            throw new RuntimeException(e1);
+            throw new WxRuntimeException(e1);
           }
         } else {
           throw e;
@@ -323,7 +345,7 @@ public  T execute(RequestExecutor executor, String uri, E data) thro
     } while (retryTimes++ < this.maxRetryTimes);
 
     log.warn("重试达到最大次数【{}】", this.maxRetryTimes);
-    throw new RuntimeException("微信服务端异常,超出重试次数");
+    throw new WxRuntimeException("微信服务端异常,超出重试次数");
   }
 
   protected  T executeInternal(RequestExecutor executor, String uri, E data) throws WxErrorException {
@@ -368,7 +390,7 @@ protected  T executeInternal(RequestExecutor executor, String uri, E
       return null;
     } catch (IOException e) {
       log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uriWithAccessToken, dataForLog, e.getMessage());
-      throw new WxErrorException(WxError.builder().errorMsg(e.getMessage()).build(), e);
+      throw new WxErrorException(e);
     }
   }
 
@@ -448,7 +470,7 @@ public WxMpService switchoverTo(String mpId) {
       return this;
     }
 
-    throw new RuntimeException(String.format("无法找到对应【%s】的公众号配置信息,请核实!", mpId));
+    throw new WxRuntimeException(String.format("无法找到对应【%s】的公众号配置信息,请核实!", mpId));
   }
 
   @Override
@@ -477,4 +499,13 @@ public RequestHttp getRequestHttp() {
     return this;
   }
 
+  @Override
+  public WxMpGuideService getGuideService() {
+    return this.guideService;
+  }
+
+  @Override
+  public void setGuideService(WxMpGuideService guideService) {
+    this.guideService = guideService;
+  }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
index 36f49acd35..cbfd5d8d07 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImpl.java
@@ -48,24 +48,26 @@ public String getCardApiTicket() throws WxErrorException {
   @Override
   public String getCardApiTicket(boolean forceRefresh) throws WxErrorException {
     final TicketType type = TicketType.WX_CARD;
-    Lock lock = getWxMpService().getWxMpConfigStorage().getTicketLock(type);
-    lock.lock();
-    try {
 
-      if (forceRefresh) {
-        this.getWxMpService().getWxMpConfigStorage().expireTicket(type);
-      }
+    if (forceRefresh) {
+      this.getWxMpService().getWxMpConfigStorage().expireTicket(type);
+    }
 
-      if (this.getWxMpService().getWxMpConfigStorage().isTicketExpired(type)) {
-        String responseContent = this.wxMpService.execute(SimpleGetRequestExecutor
-          .create(this.getWxMpService().getRequestHttp()), WxMpApiUrl.Card.CARD_GET_TICKET, null);
-        JsonObject tmpJsonObject = GsonParser.parse(responseContent);
-        String cardApiTicket = tmpJsonObject.get("ticket").getAsString();
-        int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
-        this.getWxMpService().getWxMpConfigStorage().updateTicket(type, cardApiTicket, expiresInSeconds);
+    if (this.getWxMpService().getWxMpConfigStorage().isTicketExpired(type)) {
+      Lock lock = getWxMpService().getWxMpConfigStorage().getTicketLock(type);
+      lock.lock();
+      try {
+        if (this.getWxMpService().getWxMpConfigStorage().isTicketExpired(type)) {
+          String responseContent = this.wxMpService.execute(SimpleGetRequestExecutor
+            .create(this.getWxMpService().getRequestHttp()), WxMpApiUrl.Card.CARD_GET_TICKET, null);
+          JsonObject tmpJsonObject = GsonParser.parse(responseContent);
+          String cardApiTicket = tmpJsonObject.get("ticket").getAsString();
+          int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt();
+          this.getWxMpService().getWxMpConfigStorage().updateTicket(type, cardApiTicket, expiresInSeconds);
+        }
+      } finally {
+        lock.unlock();
       }
-    } finally {
-      lock.unlock();
     }
     return this.getWxMpService().getWxMpConfigStorage().getTicket(type);
   }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideServiceImpl.java
new file mode 100644
index 0000000000..51513fbfe7
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpGuideServiceImpl.java
@@ -0,0 +1,66 @@
+package me.chanjar.weixin.mp.api.impl;
+
+import lombok.AllArgsConstructor;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.util.json.GsonHelper;
+import me.chanjar.weixin.mp.api.WxMpGuideService;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.bean.guide.WxMpGuideInfo;
+import me.chanjar.weixin.mp.bean.guide.WxMpGuideList;
+import me.chanjar.weixin.mp.enums.WxMpApiUrl;
+
+/**
+ * .
+ *
+ * @author Binary Wang
+ * @date 2020-10-06
+ */
+@AllArgsConstructor
+public class WxMpGuideServiceImpl implements WxMpGuideService {
+  private static final String ACCOUNT = "guide_account";
+  private static final String OPENID = "guide_openid";
+  private final WxMpService mpService;
+
+  @Override
+  public void addGuide(String account, String openid, String headImgUrl, String nickName) throws WxErrorException {
+    this.mpService.post(WxMpApiUrl.Guide.ADD_GUIDE, GsonHelper.buildJsonObject(ACCOUNT, account,
+      "guide_headimgurl", headImgUrl, "guide_nickname", nickName, OPENID, openid));
+  }
+
+  @Override
+  public void addGuide(WxMpGuideInfo guideInfo) throws WxErrorException {
+    this.mpService.post(WxMpApiUrl.Guide.ADD_GUIDE,
+      GsonHelper.buildJsonObject(ACCOUNT, guideInfo.getAccount(),
+        "guide_headimgurl", guideInfo.getHeadImgUrl(),
+        "guide_nickname", guideInfo.getNickName(),
+        OPENID, guideInfo.getOpenid()));
+  }
+
+  @Override
+  public void updateGuide(WxMpGuideInfo guideInfo) throws WxErrorException {
+    this.mpService.post(WxMpApiUrl.Guide.UPDATE_GUIDE,
+      GsonHelper.buildJsonObject(ACCOUNT, guideInfo.getAccount(),
+        "guide_headimgurl", guideInfo.getHeadImgUrl(),
+        "guide_nickname", guideInfo.getNickName(),
+        OPENID, guideInfo.getOpenid()));
+
+  }
+
+  @Override
+  public WxMpGuideInfo getGuide(String account, String openid) throws WxErrorException {
+    return WxMpGuideInfo.fromJson(this.mpService.post(WxMpApiUrl.Guide.GET_GUIDE,
+      GsonHelper.buildJsonObject(ACCOUNT, account, OPENID, openid)));
+  }
+
+  @Override
+  public void delGuide(String account, String openid) throws WxErrorException {
+    this.mpService.post(WxMpApiUrl.Guide.DEL_GUIDE,
+      GsonHelper.buildJsonObject(ACCOUNT, account, OPENID, openid));
+  }
+
+  @Override
+  public WxMpGuideList listGuide(int page, int num) throws WxErrorException {
+    return WxMpGuideList.fromJson(this.mpService.post(WxMpApiUrl.Guide.LIST_GUIDE,
+      GsonHelper.buildJsonObject("page", page, "num", num)));
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImpl.java
index 24c699657d..ea1785f233 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImpl.java
@@ -2,7 +2,7 @@
 
 import lombok.RequiredArgsConstructor;
 import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.common.api.WxImgProcService;
+import me.chanjar.weixin.common.service.WxImgProcService;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.common.bean.imgproc.WxImgProcAiCropResult;
 import me.chanjar.weixin.common.bean.imgproc.WxImgProcQrCodeResult;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java
index a131e3a9f3..1ed957aae5 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpKefuServiceImpl.java
@@ -115,11 +115,11 @@ public WxMpKfSessionWaitCaseList kfSessionGetWaitCase() throws WxErrorException
   @Override
   public WxMpKfMsgList kfMsgList(Date startTime, Date endTime, Long msgId, Integer number) throws WxErrorException {
     if (number > 10000) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("非法参数请求,每次最多查询10000条记录!").build());
+      throw new WxErrorException("非法参数请求,每次最多查询10000条记录!");
     }
 
     if (startTime.after(endTime)) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("起始时间不能晚于结束时间!").build());
+      throw new WxErrorException("起始时间不能晚于结束时间!");
     }
 
     JsonObject param = new JsonObject();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxOAuth2ServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOAuth2ServiceImpl.java
similarity index 62%
rename from weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxOAuth2ServiceImpl.java
rename to weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOAuth2ServiceImpl.java
index 3c6287b7dd..f77da7c855 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxOAuth2ServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOAuth2ServiceImpl.java
@@ -1,23 +1,23 @@
 package me.chanjar.weixin.mp.api.impl;
 
-import lombok.AllArgsConstructor;
 import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
+import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor;
 import me.chanjar.weixin.common.util.http.URIUtil;
 import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.mp.api.WxOAuth2Service;
-import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
-import me.chanjar.weixin.mp.bean.result.WxMpUser;
+import me.chanjar.weixin.common.service.WxOAuth2Service;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 import org.apache.commons.lang3.StringUtils;
 
 import java.io.IOException;
 
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Other.*;
+import static me.chanjar.weixin.mp.enums.WxMpApiUrl.OAuth2.*;
 
 /**
  * oauth2 相关接口实现类.
@@ -26,34 +26,37 @@
  * @date 2020-08-08
  */
 @RequiredArgsConstructor
-public class WxOAuth2ServiceImpl implements WxOAuth2Service {
+public class WxMpOAuth2ServiceImpl implements WxOAuth2Service {
   private final WxMpService wxMpService;
 
   @Override
-  public String buildAuthorizationUrl(String redirectURI, String scope, String state) {
+  public String buildAuthorizationUrl(String redirectUri, String scope, String state) {
     return String.format(CONNECT_OAUTH2_AUTHORIZE_URL.getUrl(getMpConfigStorage()),
-      getMpConfigStorage().getAppId(), URIUtil.encodeURIComponent(redirectURI), scope, StringUtils.trimToEmpty(state));
+      getMpConfigStorage().getAppId(), URIUtil.encodeURIComponent(redirectUri), scope, StringUtils.trimToEmpty(state));
   }
 
-  private WxMpOAuth2AccessToken getOAuth2AccessToken(String url) throws WxErrorException {
+  private WxOAuth2AccessToken getOAuth2AccessToken(String url) throws WxErrorException {
     try {
       RequestExecutor executor = SimpleGetRequestExecutor.create(this.wxMpService.getRequestHttp());
       String responseText = executor.execute(url, null, WxType.MP);
-      return WxMpOAuth2AccessToken.fromJson(responseText);
+      return WxOAuth2AccessToken.fromJson(responseText);
     } catch (IOException e) {
       throw new WxErrorException(WxError.builder().errorCode(99999).errorMsg(e.getMessage()).build(), e);
     }
   }
 
   @Override
-  public WxMpOAuth2AccessToken getAccessToken(String code) throws WxErrorException {
-    String url = String.format(OAUTH2_ACCESS_TOKEN_URL.getUrl(getMpConfigStorage()), getMpConfigStorage().getAppId(),
-      getMpConfigStorage().getSecret(), code);
-    return this.getOAuth2AccessToken(url);
+  public WxOAuth2AccessToken getAccessToken(String code) throws WxErrorException {
+    return this.getAccessToken(getMpConfigStorage().getAppId(), getMpConfigStorage().getSecret(), code);
+  }
+
+  @Override
+  public WxOAuth2AccessToken getAccessToken(String appId, String appSecret, String code) throws WxErrorException {
+    return this.getOAuth2AccessToken(String.format(OAUTH2_ACCESS_TOKEN_URL.getUrl(getMpConfigStorage()), appId, appSecret, code));
   }
 
   @Override
-  public WxMpOAuth2AccessToken refreshAccessToken(String refreshToken) throws WxErrorException {
+  public WxOAuth2AccessToken refreshAccessToken(String refreshToken) throws WxErrorException {
     String url = String.format(OAUTH2_REFRESH_TOKEN_URL.getUrl(getMpConfigStorage()), getMpConfigStorage().getAppId(), refreshToken);
     return this.getOAuth2AccessToken(url);
   }
@@ -63,7 +66,7 @@ protected WxMpConfigStorage getMpConfigStorage() {
   }
 
   @Override
-  public WxMpUser getUserInfo(WxMpOAuth2AccessToken token, String lang) throws WxErrorException {
+  public WxOAuth2UserInfo getUserInfo(WxOAuth2AccessToken token, String lang) throws WxErrorException {
     if (lang == null) {
       lang = "zh_CN";
     }
@@ -73,20 +76,20 @@ public WxMpUser getUserInfo(WxMpOAuth2AccessToken token, String lang) throws WxE
     try {
       RequestExecutor executor = SimpleGetRequestExecutor.create(this.wxMpService.getRequestHttp());
       String responseText = executor.execute(url, null, WxType.MP);
-      return WxMpUser.fromJson(responseText);
+      return WxOAuth2UserInfo.fromJson(responseText);
     } catch (IOException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
   }
 
   @Override
-  public boolean validateAccessToken(WxMpOAuth2AccessToken token) {
+  public boolean validateAccessToken(WxOAuth2AccessToken token) {
     String url = String.format(OAUTH2_VALIDATE_TOKEN_URL.getUrl(getMpConfigStorage()), token.getAccessToken(), token.getOpenId());
 
     try {
       SimpleGetRequestExecutor.create(this.wxMpService.getRequestHttp()).execute(url, null, WxType.MP);
     } catch (IOException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     } catch (WxErrorException e) {
       return false;
     }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImpl.java
index f6748b5641..7f6a2e3cff 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpOcrServiceImpl.java
@@ -1,34 +1,18 @@
 package me.chanjar.weixin.mp.api.impl;
 
 import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.common.service.WxOcrService;
+import me.chanjar.weixin.common.bean.ocr.*;
 import me.chanjar.weixin.common.error.WxErrorException;
-import me.chanjar.weixin.common.api.WxOcrService;
-import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.common.bean.ocr.WxOcrBankCardResult;
-import me.chanjar.weixin.common.bean.ocr.WxOcrBizLicenseResult;
-import me.chanjar.weixin.common.bean.ocr.WxOcrCommResult;
-import me.chanjar.weixin.common.bean.ocr.WxOcrDrivingLicenseResult;
-import me.chanjar.weixin.common.bean.ocr.WxOcrDrivingResult;
-import me.chanjar.weixin.common.bean.ocr.WxOcrIdCardResult;
 import me.chanjar.weixin.common.requestexecuter.ocr.OcrDiscernRequestExecutor;
+import me.chanjar.weixin.mp.api.WxMpService;
 
 import java.io.File;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.BANK_CARD;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.BIZ_LICENSE;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.COMM;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.DRIVING;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.DRIVING_LICENSE;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.FILEIDCARD;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.FILE_BANK_CARD;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.FILE_BIZ_LICENSE;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.FILE_COMM;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.FILE_DRIVING;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.FILE_DRIVING_LICENSE;
-import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.IDCARD;
+import static me.chanjar.weixin.mp.enums.WxMpApiUrl.Ocr.*;
 
 /**
  * ocr 接口实现.
@@ -49,7 +33,7 @@ public WxOcrIdCardResult idCard(String imgUrl) throws WxErrorException {
     }
 
     final String result = this.mainService.post(String.format(IDCARD.getUrl(this.mainService.getWxMpConfigStorage()),
-      imgUrl), null);
+      imgUrl), (String) null);
     return WxOcrIdCardResult.fromJson(result);
   }
 
@@ -69,7 +53,7 @@ public WxOcrBankCardResult bankCard(String imgUrl) throws WxErrorException {
     }
 
     final String result = this.mainService.post(String.format(BANK_CARD.getUrl(this.mainService.getWxMpConfigStorage()),
-      imgUrl), null);
+      imgUrl), (String) null);
     return WxOcrBankCardResult.fromJson(result);
   }
 
@@ -89,7 +73,7 @@ public WxOcrDrivingResult driving(String imgUrl) throws WxErrorException {
     }
 
     final String result = this.mainService.post(String.format(DRIVING.getUrl(this.mainService.getWxMpConfigStorage()),
-      imgUrl), null);
+      imgUrl), (String) null);
     return WxOcrDrivingResult.fromJson(result);
   }
 
@@ -109,7 +93,7 @@ public WxOcrDrivingLicenseResult drivingLicense(String imgUrl) throws WxErrorExc
     }
 
     final String result = this.mainService.post(String.format(DRIVING_LICENSE.getUrl(this.mainService.getWxMpConfigStorage()),
-      imgUrl), null);
+      imgUrl), (String) null);
     return WxOcrDrivingLicenseResult.fromJson(result);
   }
 
@@ -129,7 +113,7 @@ public WxOcrBizLicenseResult bizLicense(String imgUrl) throws WxErrorException {
     }
 
     final String result = this.mainService.post(String.format(BIZ_LICENSE.getUrl(this.mainService.getWxMpConfigStorage()),
-      imgUrl), null);
+      imgUrl), (String) null);
     return WxOcrBizLicenseResult.fromJson(result);
   }
 
@@ -149,7 +133,7 @@ public WxOcrCommResult comm(String imgUrl) throws WxErrorException {
     }
 
     final String result = this.mainService.post(String.format(COMM.getUrl(this.mainService.getWxMpConfigStorage()),
-      imgUrl), null);
+      imgUrl), (String) null);
     return WxOcrCommResult.fromJson(result);
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpQrcodeServiceImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpQrcodeServiceImpl.java
index a654afb769..7bad648cb5 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpQrcodeServiceImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpQrcodeServiceImpl.java
@@ -29,7 +29,7 @@ public class WxMpQrcodeServiceImpl implements WxMpQrcodeService {
   @Override
   public WxMpQrCodeTicket qrCodeCreateTmpTicket(int sceneId, Integer expireSeconds) throws WxErrorException {
     if (sceneId == 0) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("临时二维码场景值不能为0!").build());
+      throw new WxErrorException("临时二维码场景值不能为0!");
     }
 
     return this.createQrCode("QR_SCENE", null, sceneId, expireSeconds);
@@ -38,7 +38,7 @@ public WxMpQrCodeTicket qrCodeCreateTmpTicket(int sceneId, Integer expireSeconds
   @Override
   public WxMpQrCodeTicket qrCodeCreateTmpTicket(String sceneStr, Integer expireSeconds) throws WxErrorException {
     if (StringUtils.isBlank(sceneStr)) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("临时二维码场景值不能为空!").build());
+      throw new WxErrorException("临时二维码场景值不能为空!");
     }
 
     return this.createQrCode("QR_STR_SCENE", sceneStr, null, expireSeconds);
@@ -48,8 +48,7 @@ private WxMpQrCodeTicket createQrCode(String actionName, String sceneStr, Intege
     throws WxErrorException {
     //expireSeconds 该二维码有效时间,以秒为单位。 最大不超过2592000(即30天),此字段如果不填,则默认有效期为30秒。
     if (expireSeconds != null && expireSeconds > 2592000) {
-      throw new WxErrorException(WxError.builder().errorCode(-1)
-        .errorMsg("临时二维码有效时间最大不能超过2592000(即30天)!").build());
+      throw new WxErrorException("临时二维码有效时间最大不能超过2592000(即30天)!");
     }
 
     if (expireSeconds == null) {
@@ -84,9 +83,7 @@ private WxMpQrCodeTicket getQrCodeTicket(String actionName, String sceneStr, Int
   @Override
   public WxMpQrCodeTicket qrCodeCreateLastTicket(int sceneId) throws WxErrorException {
     if (sceneId < 1 || sceneId > 100000) {
-      throw new WxErrorException(WxError.builder().errorCode(-1)
-        .errorMsg("永久二维码的场景值目前只支持1--100000!")
-        .build());
+      throw new WxErrorException("永久二维码的场景值目前只支持1--100000!");
     }
 
     return this.getQrCodeTicket("QR_LIMIT_SCENE", null, sceneId, null);
@@ -113,7 +110,7 @@ public String qrCodePictureUrl(String ticket, boolean needShortUrl) throws WxErr
 
       return resultUrl;
     } catch (UnsupportedEncodingException e) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg(e.getMessage()).build());
+      throw new WxErrorException(e.getMessage());
     }
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java
index 6e7ee376c7..8b5e029104 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceHttpClientImpl.java
@@ -1,6 +1,7 @@
 package me.chanjar.weixin.mp.api.impl;
 
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.http.HttpType;
 import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
 import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder;
@@ -92,10 +93,10 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
           httpGet.releaseConnection();
         }
       } catch (IOException e) {
-        throw new RuntimeException(e);
+        throw new WxRuntimeException(e);
       }
     } catch (InterruptedException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     } finally {
       if (locked) {
         lock.unlock();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java
index 56b8eb12eb..eb75f1ff62 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceJoddHttpImpl.java
@@ -5,6 +5,7 @@
 import jodd.http.ProxyInfo;
 import jodd.http.net.SocketHttpConnectionProvider;
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.http.HttpType;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 
@@ -77,7 +78,7 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
 
       return this.extractAccessToken(request.send().bodyText());
     } catch (InterruptedException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     } finally {
       if (locked) {
         lock.unlock();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java
index 6d6708349f..3639d1bc06 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/api/impl/WxMpServiceOkHttpImpl.java
@@ -1,6 +1,7 @@
 package me.chanjar.weixin.mp.api.impl;
 
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.http.HttpType;
 import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
@@ -59,9 +60,9 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException {
       Response response = getRequestHttpClient().newCall(request).execute();
       return this.extractAccessToken(Objects.requireNonNull(response.body()).string());
     } catch (IOException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     } catch (InterruptedException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     } finally {
       if (locked) {
         lock.unlock();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/BaseInfo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/BaseInfo.java
index 8d73460d00..94f12a4afb 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/BaseInfo.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/BaseInfo.java
@@ -14,6 +14,7 @@
  */
 @Data
 public class BaseInfo implements Serializable {
+  private static final long serialVersionUID = 4753535126193166020L;
 
   /**
    * 卡券的商户logo,建议像素为300*300.
@@ -173,6 +174,12 @@ public class BaseInfo implements Serializable {
   @SerializedName("get_limit")
   private Integer getLimit = 1;
 
+  /**
+   * 每人可核销的数量限制,不填写默认为50.
+   */
+  @SerializedName("use_limit")
+  private Integer useLimit = 50;
+
   /**
    * 卡券领取页面是否可分享,默认为true.
    */
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/BaseInfoUpdate.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/BaseInfoUpdate.java
index b0ec28082d..ab995fa5ad 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/BaseInfoUpdate.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/BaseInfoUpdate.java
@@ -14,6 +14,7 @@
  */
 @Data
 public class BaseInfoUpdate implements Serializable {
+  private static final long serialVersionUID = -7810188893073599733L;
 
   /**
    * 需要审核:卡券名,字数上限为9个汉字 (建议涵盖卡券属性、服务及金额).
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/CardUpdateResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/CardUpdateResult.java
index 42df19ff0f..e5d04358d4 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/CardUpdateResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/CardUpdateResult.java
@@ -3,16 +3,21 @@
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
 
+import java.io.Serializable;
+
 /**
  * @author yqx
  * @date 2018/11/07
  */
 @Data
-public class CardUpdateResult {
+public class CardUpdateResult implements Serializable {
+  private static final long serialVersionUID = 6049989267790615497L;
 
-  private int errcode;
+  @SerializedName("errcode")
+  private int errCode;
 
-  private String errmsg;
+  @SerializedName("errmsg")
+  private String errMsg;
 
   /**
    * 此次更新是否需要提审,true为需要,false为不需要。
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeCheckcodeResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeCheckcodeResult.java
index 84768c0916..4b86bbbdd5 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeCheckcodeResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeCheckcodeResult.java
@@ -2,7 +2,6 @@
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
-import me.chanjar.weixin.mp.bean.result.WxMpResult;
 import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 
 import java.io.Serializable;
@@ -10,8 +9,7 @@
 
 
 @Data
-public class WxMpCardCodeCheckcodeResult extends WxMpResult implements Serializable {
-
+public class WxMpCardCodeCheckcodeResult implements Serializable {
   private static final long serialVersionUID = -5128692403997016750L;
 
   /**
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeDepositCountResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeDepositCountResult.java
index a7a114bf58..00631ec74d 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeDepositCountResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeDepositCountResult.java
@@ -2,15 +2,16 @@
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
-import me.chanjar.weixin.mp.bean.result.WxMpResult;
 import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 
 import java.io.Serializable;
 
 
+/**
+ * @author S 
+ */
 @Data
-public class WxMpCardCodeDepositCountResult extends WxMpResult implements Serializable {
-
+public class WxMpCardCodeDepositCountResult implements Serializable {
   private static final long serialVersionUID = -6707587956061215868L;
 
   /**
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeDepositResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeDepositResult.java
index aeb1246b8e..794c6cd881 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeDepositResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardCodeDepositResult.java
@@ -2,34 +2,36 @@
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
-import me.chanjar.weixin.mp.bean.result.WxMpResult;
 import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 
 import java.io.Serializable;
+import java.util.List;
 
 
+/**
+ * @author S 
+ */
 @Data
-public class WxMpCardCodeDepositResult extends WxMpResult implements Serializable {
-
+public class WxMpCardCodeDepositResult  implements Serializable {
   private static final long serialVersionUID = 2955588617765355420L;
 
   /**
-   * 成功个数
+   * 成功的code
    */
   @SerializedName("succ_code")
-  private Integer succCode;
+  private List successCodes;
 
   /**
-   * 重复导入的code会自动被过滤
+   * 重复导入的code
    */
   @SerializedName("duplicate_code")
-  private Integer duplicateCode;
+  private List duplicateCodes;
 
   /**
-   * 失败个数
+   * 失败的code
    */
   @SerializedName("fail_code")
-  private Integer failCode;
+  private List failCodes;
 
 
   public static WxMpCardCodeDepositResult fromJson(String json) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java
index 13db310d5b..6d7dde1ad6 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxMpCardMpnewsGethtmlResult.java
@@ -2,15 +2,16 @@
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
-import me.chanjar.weixin.mp.bean.result.WxMpResult;
 import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 
 import java.io.Serializable;
 
 
+/**
+ * @author S 
+ */
 @Data
-public class WxMpCardMpnewsGethtmlResult extends WxMpResult implements Serializable {
-
+public class WxMpCardMpnewsGethtmlResult implements Serializable {
   private static final long serialVersionUID = 6435268886823478711L;
 
   /**
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxUserCardListResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxUserCardListResult.java
index 9133a32f17..e38c11564e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxUserCardListResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/card/WxUserCardListResult.java
@@ -2,18 +2,20 @@
 
 import com.google.gson.annotations.SerializedName;
 import lombok.Data;
-import me.chanjar.weixin.mp.bean.result.WxMpResult;
 import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder;
 
+import java.io.Serializable;
 import java.util.List;
 
 /**
  * 用户已领卡券返回
+ *
  * @author yang229
  * @date 2019/12/22
  */
 @Data
-public class WxUserCardListResult extends WxMpResult implements java.io.Serializable {
+public class WxUserCardListResult implements Serializable {
+  private static final long serialVersionUID = 4348804828075982412L;
 
   /**
    * 卡券列表
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/guide/WxMpGuideInfo.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/guide/WxMpGuideInfo.java
new file mode 100644
index 0000000000..b20b743ab4
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/guide/WxMpGuideInfo.java
@@ -0,0 +1,66 @@
+package me.chanjar.weixin.mp.bean.guide;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+import me.chanjar.weixin.common.bean.ToJson;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+
+/**
+ * 对话能力-顾问信息.
+ *
+ * @author Binary Wang
+ * @date 2020-10-06
+ */
+@Data
+@Builder
+@Accessors(chain = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class WxMpGuideInfo implements ToJson, Serializable {
+  private static final long serialVersionUID = -8159470115679031290L;
+
+  /**
+   * 顾问的微信帐号
+   */
+  @SerializedName("guide_account")
+  private String account;
+
+  /**
+   * 顾问的openid或者unionid
+   */
+  @SerializedName("guide_openid")
+  private String openid;
+
+  /**
+   * 顾问昵称
+   */
+  @SerializedName("guide_nickname")
+  private String nickName;
+
+  /**
+   * 顾问头像
+   */
+  @SerializedName("guide_headimgurl")
+  private String headImgUrl;
+
+  /**
+   * 顾问状态(1:确认中;2已确认;3已拒绝;4已过期)
+   */
+  @SerializedName("status")
+  private Integer status;
+
+  @Override
+  public String toJson() {
+    return WxGsonBuilder.create().toJson(this);
+  }
+
+  public static WxMpGuideInfo fromJson(String json) {
+    return WxGsonBuilder.create().fromJson(json, WxMpGuideInfo.class);
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/guide/WxMpGuideList.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/guide/WxMpGuideList.java
new file mode 100644
index 0000000000..e550c34608
--- /dev/null
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/guide/WxMpGuideList.java
@@ -0,0 +1,34 @@
+package me.chanjar.weixin.mp.bean.guide;
+
+import com.google.gson.annotations.SerializedName;
+import lombok.Data;
+import me.chanjar.weixin.common.util.json.WxGsonBuilder;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 顾问列表.
+ *
+ * @author Binary Wang
+ * @date 2020-10-07
+ */
+@Data
+public class WxMpGuideList implements Serializable {
+  private static final long serialVersionUID = 144044550239346216L;
+
+  /**
+   * 顾问总数量
+   */
+  @SerializedName("total_num")
+  private Integer totalNum;
+
+  /**
+   * 顾问列表
+   */
+  private List list;
+
+  public static WxMpGuideList fromJson(String json) {
+    return WxGsonBuilder.create().fromJson(json, WxMpGuideList.class);
+  }
+}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/material/WxMpMaterialFileBatchGetResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/material/WxMpMaterialFileBatchGetResult.java
index ebad06bfec..f1e46ee9b5 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/material/WxMpMaterialFileBatchGetResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/material/WxMpMaterialFileBatchGetResult.java
@@ -24,7 +24,9 @@ public String toString() {
   }
 
   @Data
-  public static class WxMaterialFileBatchGetNewsItem {
+  public static class WxMaterialFileBatchGetNewsItem implements Serializable {
+    private static final long serialVersionUID = -8300080343204117459L;
+
     private String mediaId;
     private Date updateTime;
     private String name;
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java
index f6b54a20b2..43d6a47bde 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/message/WxMpXmlMessage.java
@@ -5,6 +5,7 @@
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.XmlUtils;
 import me.chanjar.weixin.common.util.xml.XStreamCDataConverter;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
@@ -568,7 +569,7 @@ public class WxMpXmlMessage implements Serializable {
    * 审核成功时的时间(整形),时间戳
    */
   @XStreamAlias("SuccTime")
-  private Long succTime;
+  private Long successTime;
 
   /**
    * 审核失败的原因
@@ -716,7 +717,7 @@ public static WxMpXmlMessage fromEncryptedXml(InputStream is, WxMpConfigStorage
     try {
       return fromEncryptedXml(IOUtils.toString(is, StandardCharsets.UTF_8), wxMpConfigStorage, timestamp, nonce, msgSignature);
     } catch (IOException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java
index c1f43feb01..fe8f6e4043 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpMassGetResult.java
@@ -10,10 +10,10 @@
  * 
  * 查询群发消息发送状态【订阅号与服务号认证后均可用】
  * https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html#%E6%9F%A5%E8%AF%A2%E7%BE%A4%E5%8F%91%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E7%8A%B6%E6%80%81%E3%80%90%E8%AE%A2%E9%98%85%E5%8F%B7%E4%B8%8E%E6%9C%8D%E5%8A%A1%E5%8F%B7%E8%AE%A4%E8%AF%81%E5%90%8E%E5%9D%87%E5%8F%AF%E7%94%A8%E3%80%91
+ * @author S 
  */
 @Data
-public class WxMpMassGetResult extends WxMpResult implements Serializable {
-
+public class WxMpMassGetResult implements Serializable {
   private static final long serialVersionUID = -2909694117357278557L;
 
   @SerializedName("msg_id")
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpResult.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpResult.java
deleted file mode 100644
index 9be4b7e8cb..0000000000
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/bean/result/WxMpResult.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package me.chanjar.weixin.mp.bean.result;
-
-import lombok.Data;
-import me.chanjar.weixin.common.util.json.WxGsonBuilder;
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.Serializable;
-
-/**
- * 基础的微信公众号平台请求结果.
- */
-@Data
-public class WxMpResult implements Serializable {
-  private static final long serialVersionUID = 2101652152604850904L;
-  protected String errcode;
-  protected String errmsg;
-
-  /**
-   * 请求是否成功.
-   */
-  public boolean isSuccess() {
-    return StringUtils.equalsIgnoreCase(errcode, "0");
-  }
-
-  public static WxMpResult fromJson(String json) {
-    return WxGsonBuilder.create().fromJson(json, WxMpResult.class);
-  }
-
-  @Override
-  public String toString() {
-    return WxGsonBuilder.create().toJson(this);
-  }
-}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java
index 4d7ef4beb5..b2e984b0f9 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/constant/WxMpEventConstants.java
@@ -141,4 +141,14 @@ public static class Invoice {
     public static final String CLOUD_INVOICE_INVOICERESULT_EVENT = "cloud_invoice_invoiceresult_event";
   }
 
+  /**
+   * 对话助手相关事件
+   */
+  public static class Guide {
+    /**
+     * 顾问邀请结果通知事件.
+     */
+    public static final String GUIDE_INVITE_RESULT_EVENT = "guide_invite_result_event";
+
+  }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java
index 7e10867658..d050aeb66b 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/enums/WxMpApiUrl.java
@@ -1,6 +1,8 @@
 package me.chanjar.weixin.mp.enums;
 
 import lombok.AllArgsConstructor;
+import lombok.Getter;
+import me.chanjar.weixin.mp.bean.WxMpHostConfig;
 import me.chanjar.weixin.mp.config.WxMpConfigStorage;
 
 import static me.chanjar.weixin.mp.bean.WxMpHostConfig.*;
@@ -21,9 +23,31 @@ public interface WxMpApiUrl {
    * @param config 微信公众号配置
    * @return api地址
    */
-  String getUrl(WxMpConfigStorage config);
+  default String getUrl(WxMpConfigStorage config) {
+    WxMpHostConfig hostConfig = null;
+    if (config != null) {
+      hostConfig = config.getHostConfig();
+    }
+    return buildUrl(hostConfig, this.getPrefix(), this.getPath());
+
+  }
+
+  /**
+   * the path
+   *
+   * @return path
+   */
+  String getPath();
+
+  /**
+   * the prefix
+   *
+   * @return prefix
+   */
+  String getPrefix();
 
   @AllArgsConstructor
+  @Getter
   enum Device implements WxMpApiUrl {
     /**
      * get_bind_device.
@@ -64,15 +88,39 @@ enum Device implements WxMpApiUrl {
 
     private final String prefix;
     private final String path;
+  }
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
+  @AllArgsConstructor
+  @Getter
+  enum OAuth2 implements WxMpApiUrl {
+    /**
+     * 用code换取oauth2的access token.
+     */
+    OAUTH2_ACCESS_TOKEN_URL(API_DEFAULT_HOST_URL, "/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"),
+    /**
+     * 刷新oauth2的access token.
+     */
+    OAUTH2_REFRESH_TOKEN_URL(API_DEFAULT_HOST_URL, "/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s"),
+    /**
+     * 用oauth2获取用户信息.
+     */
+    OAUTH2_USERINFO_URL(API_DEFAULT_HOST_URL, "/sns/userinfo?access_token=%s&openid=%s&lang=%s"),
+    /**
+     * 验证oauth2的access token是否有效.
+     */
+    OAUTH2_VALIDATE_TOKEN_URL(API_DEFAULT_HOST_URL, "/sns/auth?access_token=%s&openid=%s"),
+    /**
+     * oauth2授权的url连接.
+     */
+    CONNECT_OAUTH2_AUTHORIZE_URL(OPEN_DEFAULT_HOST_URL, "/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s&connect_redirect=1#wechat_redirect");
+
+    private final String prefix;
+    private final String path;
 
   }
 
   @AllArgsConstructor
+  @Getter
   enum Other implements WxMpApiUrl {
     /**
      * 获取access_token.
@@ -90,22 +138,6 @@ enum Other implements WxMpApiUrl {
      * 语义查询接口.
      */
     SEMANTIC_SEMPROXY_SEARCH_URL(API_DEFAULT_HOST_URL, "/semantic/semproxy/search"),
-    /**
-     * 用code换取oauth2的access token.
-     */
-    OAUTH2_ACCESS_TOKEN_URL(API_DEFAULT_HOST_URL, "/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code"),
-    /**
-     * 刷新oauth2的access token.
-     */
-    OAUTH2_REFRESH_TOKEN_URL(API_DEFAULT_HOST_URL, "/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s"),
-    /**
-     * 用oauth2获取用户信息.
-     */
-    OAUTH2_USERINFO_URL(API_DEFAULT_HOST_URL, "/sns/userinfo?access_token=%s&openid=%s&lang=%s"),
-    /**
-     * 验证oauth2的access token是否有效.
-     */
-    OAUTH2_VALIDATE_TOKEN_URL(API_DEFAULT_HOST_URL, "/sns/auth?access_token=%s&openid=%s"),
     /**
      * 获取微信服务器IP地址.
      */
@@ -118,10 +150,6 @@ enum Other implements WxMpApiUrl {
      * 第三方使用网站应用授权登录的url.
      */
     QRCONNECT_URL(OPEN_DEFAULT_HOST_URL, "/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect"),
-    /**
-     * oauth2授权的url连接.
-     */
-    CONNECT_OAUTH2_AUTHORIZE_URL(OPEN_DEFAULT_HOST_URL, "/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s&connect_redirect=1#wechat_redirect"),
     /**
      * 获取公众号的自动回复规则.
      */
@@ -134,13 +162,10 @@ enum Other implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum Marketing implements WxMpApiUrl {
     /**
      * sets add.
@@ -162,13 +187,10 @@ enum Marketing implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum Menu implements WxMpApiUrl {
     /**
      * get_current_selfmenu_info.
@@ -202,14 +224,10 @@ enum Menu implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
-
   @AllArgsConstructor
+  @Getter
   enum Qrcode implements WxMpApiUrl {
     /**
      * create.
@@ -227,13 +245,10 @@ enum Qrcode implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum ShakeAround implements WxMpApiUrl {
     /**
      * getshakeinfo.
@@ -255,13 +270,10 @@ enum ShakeAround implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum SubscribeMsg implements WxMpApiUrl {
     /**
      * subscribemsg.
@@ -275,13 +287,10 @@ enum SubscribeMsg implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum TemplateMsg implements WxMpApiUrl {
     /**
      * send.
@@ -311,13 +320,10 @@ enum TemplateMsg implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum UserBlacklist implements WxMpApiUrl {
     /**
      * getblacklist.
@@ -335,13 +341,10 @@ enum UserBlacklist implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum UserTag implements WxMpApiUrl {
     /**
      * create.
@@ -379,13 +382,10 @@ enum UserTag implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum Wifi implements WxMpApiUrl {
     /**
      * list.
@@ -405,13 +405,10 @@ enum Wifi implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum AiOpen implements WxMpApiUrl {
     /**
      * translatecontent.
@@ -429,13 +426,10 @@ enum AiOpen implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum Ocr implements WxMpApiUrl {
     /**
      * 身份证识别.
@@ -496,17 +490,10 @@ enum Ocr implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      if (config == null) {
-        return buildUrl(null, prefix, path);
-      }
-
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum Card implements WxMpApiUrl {
     /**
      * create.
@@ -606,13 +593,10 @@ enum Card implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum DataCube implements WxMpApiUrl {
     /**
      * getusersummary.
@@ -686,13 +670,10 @@ enum DataCube implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum Kefu implements WxMpApiUrl {
     /**
      * send.
@@ -758,13 +739,10 @@ enum Kefu implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum MassMessage implements WxMpApiUrl {
     /**
      * 上传群发用的图文消息.
@@ -812,13 +790,10 @@ enum MassMessage implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum Material implements WxMpApiUrl {
     /**
      * get.
@@ -868,13 +843,10 @@ enum Material implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum MemberCard implements WxMpApiUrl {
     /**
      * create.
@@ -913,13 +885,10 @@ enum MemberCard implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum Store implements WxMpApiUrl {
     /**
      * getwxcategory.
@@ -949,13 +918,10 @@ enum Store implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum User implements WxMpApiUrl {
     /**
      * batchget.
@@ -981,13 +947,10 @@ enum User implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum Comment implements WxMpApiUrl {
     /**
      * 打开已群发文章评论.
@@ -1032,13 +995,10 @@ enum Comment implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum ImgProc implements WxMpApiUrl {
     /**
      * 二维码/条码识别
@@ -1073,16 +1033,10 @@ enum ImgProc implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      if (null == config) {
-        return buildUrl(null, prefix, path);
-      }
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
   }
 
   @AllArgsConstructor
+  @Getter
   enum Invoice implements WxMpApiUrl {
 
     /**
@@ -1148,12 +1102,36 @@ enum Invoice implements WxMpApiUrl {
     private final String prefix;
     private final String path;
 
-    @Override
-    public String getUrl(WxMpConfigStorage config) {
-      if (null == config) {
-        return buildUrl(null, prefix, path);
-      }
-      return buildUrl(config.getHostConfig(), prefix, path);
-    }
+  }
+
+  /**
+   * 对话能力
+   */
+  @AllArgsConstructor
+  @Getter
+  enum Guide implements WxMpApiUrl {
+    /**
+     * 添加顾问
+     */
+    ADD_GUIDE(API_DEFAULT_HOST_URL, "/cgi-bin/guide/addguideacct"),
+    /**
+     * 修改顾问
+     */
+    UPDATE_GUIDE(API_DEFAULT_HOST_URL, "/cgi-bin/guide/updateguideacct"),
+    /**
+     * 获取顾问信息
+     */
+    GET_GUIDE(API_DEFAULT_HOST_URL, "/cgi-bin/guide/getguideacct"),
+    /**
+     * 删除顾问
+     */
+    DEL_GUIDE(API_DEFAULT_HOST_URL, "/cgi-bin/guide/delguideacct"),
+    /**
+     * 获取服务号顾问列表
+     */
+    LIST_GUIDE(API_DEFAULT_HOST_URL, "/cgi-bin/guide/getguideacctlist");
+    private final String prefix;
+    private final String path;
+
   }
 }
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java
index 82e2b36318..5f762cc6dc 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpGsonBuilder.java
@@ -40,7 +40,6 @@ public class WxMpGsonBuilder {
     INSTANCE.registerTypeAdapter(WxMpTemplateMessage.class, new WxMpTemplateMessageGsonAdapter());
     INSTANCE.registerTypeAdapter(WxMpSubscribeMessage.class, new WxMpSubscribeMessageGsonAdapter());
     INSTANCE.registerTypeAdapter(WxMpSemanticQueryResult.class, new WxMpSemanticQueryResultAdapter());
-    INSTANCE.registerTypeAdapter(WxMpOAuth2AccessToken.class, new WxMpOAuth2AccessTokenAdapter());
     INSTANCE.registerTypeAdapter(WxDataCubeUserSummary.class, new WxMpUserSummaryGsonAdapter());
     INSTANCE.registerTypeAdapter(WxDataCubeUserCumulate.class, new WxMpUserCumulateGsonAdapter());
     INSTANCE.registerTypeAdapter(WxMpMaterialUploadResult.class, new WxMpMaterialUploadResultAdapter());
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpKefuMessageGsonAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpKefuMessageGsonAdapter.java
index e9e5112d31..679f8db1ac 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpKefuMessageGsonAdapter.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpKefuMessageGsonAdapter.java
@@ -2,6 +2,7 @@
 
 import com.google.gson.*;
 import me.chanjar.weixin.common.api.WxConsts.KefuMsgType;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
 import org.apache.commons.lang3.StringUtils;
 
@@ -96,7 +97,7 @@ public JsonElement serialize(WxMpKefuMessage message, Type typeOfSrc, JsonSerial
         break;
       }
       default: {
-        throw new RuntimeException("非法消息类型,暂不支持");
+        throw new WxRuntimeException("非法消息类型,暂不支持");
       }
     }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpOAuth2AccessTokenAdapter.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpOAuth2AccessTokenAdapter.java
deleted file mode 100644
index c832ef8dae..0000000000
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/json/WxMpOAuth2AccessTokenAdapter.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package me.chanjar.weixin.mp.util.json;
-
-import com.google.gson.*;
-import me.chanjar.weixin.common.util.json.GsonHelper;
-import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
-
-import java.lang.reflect.Type;
-
-public class WxMpOAuth2AccessTokenAdapter implements JsonDeserializer {
-
-  @Override
-  public WxMpOAuth2AccessToken deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws
-    JsonParseException {
-    WxMpOAuth2AccessToken accessToken = new WxMpOAuth2AccessToken();
-    JsonObject accessTokenJsonObject = json.getAsJsonObject();
-
-    if (accessTokenJsonObject.get("access_token") != null && !accessTokenJsonObject.get("access_token").isJsonNull()) {
-      accessToken.setAccessToken(GsonHelper.getAsString(accessTokenJsonObject.get("access_token")));
-    }
-    if (accessTokenJsonObject.get("expires_in") != null && !accessTokenJsonObject.get("expires_in").isJsonNull()) {
-      accessToken.setExpiresIn(GsonHelper.getAsPrimitiveInt(accessTokenJsonObject.get("expires_in")));
-    }
-    if (accessTokenJsonObject.get("refresh_token") != null && !accessTokenJsonObject.get("refresh_token").isJsonNull()) {
-      accessToken.setRefreshToken(GsonHelper.getAsString(accessTokenJsonObject.get("refresh_token")));
-    }
-    if (accessTokenJsonObject.get("openid") != null && !accessTokenJsonObject.get("openid").isJsonNull()) {
-      accessToken.setOpenId(GsonHelper.getAsString(accessTokenJsonObject.get("openid")));
-    }
-    if (accessTokenJsonObject.get("scope") != null && !accessTokenJsonObject.get("scope").isJsonNull()) {
-      accessToken.setScope(GsonHelper.getAsString(accessTokenJsonObject.get("scope")));
-    }
-    if (accessTokenJsonObject.get("unionid") != null && !accessTokenJsonObject.get("unionid").isJsonNull()) {
-      accessToken.setUnionId(GsonHelper.getAsString(accessTokenJsonObject.get("unionid")));
-    }
-    return accessToken;
-  }
-
-}
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteJoddHttpRequestExecutor.java
index afc99d62c0..318299bb34 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialDeleteJoddHttpRequestExecutor.java
@@ -12,6 +12,7 @@
 import me.chanjar.weixin.common.util.http.RequestHttp;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * Created by ecoolper on 2017/5/5.
@@ -31,7 +32,7 @@ public Boolean execute(String uri, String materialId, WxType wxType) throws WxEr
 
     request.query("media_id", materialId);
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
     String responseContent = response.bodyText();
     WxError error = WxError.fromJson(responseContent, WxType.MP);
     if (error.getErrorCode() != 0) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoJoddHttpRequestExecutor.java
index 59f0710692..780c0734e1 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialNewsInfoJoddHttpRequestExecutor.java
@@ -7,6 +7,7 @@
 import jodd.http.ProxyInfo;
 import jodd.util.StringPool;
 
+import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.enums.WxType;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -18,12 +19,13 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * Created by ecoolper on 2017/5/5.
  */
+@Slf4j
 public class MaterialNewsInfoJoddHttpRequestExecutor extends MaterialNewsInfoRequestExecutor {
-  private final Logger logger = LoggerFactory.getLogger(this.getClass());
   public MaterialNewsInfoJoddHttpRequestExecutor(RequestHttp requestHttp) {
     super(requestHttp);
   }
@@ -38,10 +40,10 @@ public WxMpMaterialNews execute(String uri, String materialId, WxType wxType) th
       .withConnectionProvider(requestHttp.getRequestHttpClient())
       .body(WxGsonBuilder.create().toJson(ImmutableMap.of("media_id", materialId)));
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
 
     String responseContent = response.bodyText();
-    this.logger.debug("响应原始数据:{}", responseContent);
+    log.debug("响应原始数据:{}", responseContent);
     WxError error = WxError.fromJson(responseContent, WxType.MP);
     if (error.getErrorCode() != 0) {
       throw new WxErrorException(error);
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadApacheHttpRequestExecutor.java
index a89687cd22..053ff16cba 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadApacheHttpRequestExecutor.java
@@ -41,7 +41,7 @@ public WxMpMaterialUploadResult execute(String uri, WxMpMaterial material, WxTyp
     }
 
     if (material == null) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("非法请求,material参数为空").build());
+      throw new WxErrorException("非法请求,material参数为空");
     }
 
     File file = material.getFile();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadJoddHttpRequestExecutor.java
index 7699f2f202..d4c4dfbf89 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadJoddHttpRequestExecutor.java
@@ -17,6 +17,7 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 import java.util.Map;
 
 /**
@@ -36,7 +37,7 @@ public WxMpMaterialUploadResult execute(String uri, WxMpMaterial material, WxTyp
     request.withConnectionProvider(requestHttp.getRequestHttpClient());
 
     if (material == null) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("非法请求,material参数为空").build());
+      throw new WxErrorException("非法请求,material参数为空");
     }
 
     File file = material.getFile();
@@ -50,7 +51,7 @@ public WxMpMaterialUploadResult execute(String uri, WxMpMaterial material, WxTyp
     }
 
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
     String responseContent = response.bodyText();
     WxError error = WxError.fromJson(responseContent, WxType.MP);
     if (error.getErrorCode() != 0) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadOkhttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadOkhttpRequestExecutor.java
index f4654f9fb1..7416f94f0e 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadOkhttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialUploadOkhttpRequestExecutor.java
@@ -31,7 +31,7 @@ public MaterialUploadOkhttpRequestExecutor(RequestHttp requestHttp) {
   public WxMpMaterialUploadResult execute(String uri, WxMpMaterial material, WxType wxType) throws WxErrorException, IOException {
     logger.debug("MaterialUploadOkhttpRequestExecutor is running");
     if (material == null) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("非法请求,material参数为空").build());
+      throw new WxErrorException("非法请求,material参数为空");
     }
     File file = material.getFile();
     if (file == null || !file.exists()) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoJoddHttpRequestExecutor.java
index f142c21788..9149d46794 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVideoInfoJoddHttpRequestExecutor.java
@@ -13,6 +13,7 @@
 import me.chanjar.weixin.mp.bean.material.WxMpMaterialVideoInfoResult;
 
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * Created by ecoolper on 2017/5/5.
@@ -32,7 +33,7 @@ public WxMpMaterialVideoInfoResult execute(String uri, String materialId, WxType
 
     request.query("media_id", materialId);
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
     String responseContent = response.bodyText();
     WxError error = WxError.fromJson(responseContent, WxType.MP);
     if (error.getErrorCode() != 0) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadJoddHttpRequestExecutor.java
index 1a4c25590c..e4da2004ec 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/material/MaterialVoiceAndImageDownloadJoddHttpRequestExecutor.java
@@ -37,7 +37,7 @@ public InputStream execute(String uri, String materialId, WxType wxType) throws
 
     request.query("media_id", materialId);
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
     try (InputStream inputStream = new ByteArrayInputStream(response.bodyBytes())) {
       // 下载媒体文件出错
       byte[] responseContent = IOUtils.toByteArray(inputStream);
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadApacheHttpRequestExecutor.java
index 989e388632..b570a1c43b 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadApacheHttpRequestExecutor.java
@@ -32,7 +32,7 @@ public MediaImgUploadApacheHttpRequestExecutor(RequestHttp requestHttp) {
   @Override
   public WxMediaImgUploadResult execute(String uri, File data, WxType wxType) throws WxErrorException, IOException {
     if (data == null) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("文件对象为空").build());
+      throw new WxErrorException("文件对象为空");
     }
 
     HttpPost httpPost = new HttpPost(uri);
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpRequestExecutor.java
index 76c625141e..1ca4c7c8bf 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/media/MediaImgUploadHttpRequestExecutor.java
@@ -14,6 +14,7 @@
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
 
 /**
  * Created by ecoolper on 2017/5/5.
@@ -28,7 +29,7 @@ public MediaImgUploadHttpRequestExecutor(RequestHttp requestHttp) {
   @Override
   public WxMediaImgUploadResult execute(String uri, File data, WxType wxType) throws WxErrorException, IOException {
     if (data == null) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("文件对象为空").build());
+      throw new WxErrorException("文件对象为空");
     }
 
     HttpRequest request = HttpRequest.post(uri);
@@ -39,7 +40,7 @@ public WxMediaImgUploadResult execute(String uri, File data, WxType wxType) thro
 
     request.form("media", data);
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
     String responseContent = response.bodyText();
     WxError error = WxError.fromJson(responseContent, WxType.MP);
     if (error.getErrorCode() != 0) {
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java
index 99621843db..32d3d3ca75 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeJoddHttpRequestExecutor.java
@@ -19,6 +19,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.UUID;
 
 /**
@@ -47,7 +48,7 @@ public File execute(String uri, WxMpQrCodeTicket ticket, WxType wxType) throws W
     request.withConnectionProvider(requestHttp.getRequestHttpClient());
 
     HttpResponse response = request.send();
-    response.charset(StringPool.UTF_8);
+    response.charset(StandardCharsets.UTF_8.name());
     String contentTypeHeader = response.header("Content-Type");
     if (MimeTypes.MIME_TEXT_PLAIN.equals(contentTypeHeader)) {
       String responseContent = response.bodyText();
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeRequestExecutor.java
index 0a65d2abc7..4ca5dbc0c1 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/qrcode/QrCodeRequestExecutor.java
@@ -37,7 +37,7 @@ public static RequestExecutor create(RequestHttp request
       case OK_HTTP:
         return new QrCodeOkhttpRequestExecutor(requestHttp);
       default:
-        throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("不支持的http框架").build());
+        throw new WxErrorException("不支持的http框架");
     }
   }
 
diff --git a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java
index 3c733a126f..07af44b340 100644
--- a/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java
+++ b/weixin-java-mp/src/main/java/me/chanjar/weixin/mp/util/requestexecuter/voice/VoiceUploadApacheHttpRequestExecutor.java
@@ -33,7 +33,7 @@ public VoiceUploadApacheHttpRequestExecutor(RequestHttp requestHttp) {
   @Override
   public Boolean execute(String uri, File data, WxType wxType) throws WxErrorException, IOException {
     if (data == null) {
-      throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("文件对象为空").build());
+      throw new WxErrorException("文件对象为空");
     }
 
     HttpPost httpPost = new HttpPost(uri);
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBusyRetryTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBusyRetryTest.java
index 1f0a01b46e..938eb5c032 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBusyRetryTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpBusyRetryTest.java
@@ -3,6 +3,7 @@
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.error.WxError;
 import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.http.RequestExecutor;
 import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl;
 import org.testng.annotations.*;
@@ -25,7 +26,7 @@ public synchronized  T executeInternal(
         RequestExecutor executor, String uri, E data)
         throws WxErrorException {
         log.info("Executed");
-        throw new WxErrorException(WxError.builder().errorCode(-1).build());
+        throw new WxErrorException("something");
       }
     };
 
@@ -43,18 +44,15 @@ public void testRetry(WxMpService service) throws WxErrorException {
   public void testRetryInThreadPool(final WxMpService service) throws InterruptedException, ExecutionException {
     // 当线程池中的线程复用的时候,还是能保证相同的重试次数
     ExecutorService executorService = Executors.newFixedThreadPool(1);
-    Runnable runnable = new Runnable() {
-      @Override
-      public void run() {
-        try {
-          System.out.println("=====================");
-          System.out.println(Thread.currentThread().getName() + ": testRetry");
-          service.execute(null, (String)null, null);
-        } catch (WxErrorException e) {
-          throw new RuntimeException(e);
-        } catch (RuntimeException e) {
-          // OK
-        }
+    Runnable runnable = () -> {
+      try {
+        System.out.println("=====================");
+        System.out.println(Thread.currentThread().getName() + ": testRetry");
+        service.execute(null, (String)null, null);
+      } catch (WxErrorException e) {
+        throw new WxRuntimeException(e);
+      } catch (RuntimeException e) {
+        // OK
       }
     };
     Future submit1 = executorService.submit(runnable);
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java
index b9424eb023..93f47a70f5 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/WxMpMessageRouterTest.java
@@ -97,14 +97,11 @@ public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map co
     }).end();
 
     final WxMpXmlMessage m = new WxMpXmlMessage();
-    Runnable r = new Runnable() {
-      @Override
-      public void run() {
-        router.route(m);
-        try {
-          Thread.sleep(1000);
-        } catch (InterruptedException e) {
-        }
+    Runnable r = () -> {
+      router.route(m);
+      try {
+        Thread.sleep(1000);
+      } catch (InterruptedException e) {
       }
     };
     for (int i = 0; i < 10; i++) {
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java
index b20d3fe142..1bb8922271 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/BaseWxMpServiceImplTest.java
@@ -82,15 +82,12 @@ public void refreshAccessTokenDuplicatelyTest() throws InterruptedException {
     // 测试多线程刷新accessToken时是否重复刷新
     wxService.getWxMpConfigStorage().expireAccessToken();
     final Set set = Sets.newConcurrentHashSet();
-    Runnable r = new Runnable() {
-      @Override
-      public void run() {
-        try {
-          String accessToken = wxService.getAccessToken();
-          set.add(accessToken);
-        } catch (WxErrorException e) {
-          e.printStackTrace();
-        }
+    Runnable r = () -> {
+      try {
+        String accessToken = wxService.getAccessToken();
+        set.add(accessToken);
+      } catch (WxErrorException e) {
+        e.printStackTrace();
       }
     };
 
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImplTest.java
index ecacc36de5..0f742a6750 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpCardServiceImplTest.java
@@ -10,7 +10,8 @@
 import org.testng.annotations.Guice;
 import org.testng.annotations.Test;
 
-import static org.testng.AssertJUnit.*;
+import static org.testng.AssertJUnit.assertNotNull;
+import static org.testng.AssertJUnit.assertTrue;
 
 /**
  * 测试代码仅供参考,未做严格测试,因原接口作者并未提供单元测试代码
@@ -234,7 +235,7 @@ public void testGetUserCardList() throws WxErrorException {
     String openId = "ou7Gr5sJZgFGgj38sRCNQg5pc3Fc";
     String cardId = "pu7Gr5secJXPkxBeuYUhmp8TYsuY";
     WxUserCardListResult result = this.wxService.getCardService().getUserCardList(openId, cardId);
-    assertTrue(result.isSuccess());
+    assertNotNull(result);
     System.out.println(result);
   }
 }
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpGuideServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpGuideServiceImplTest.java
new file mode 100644
index 0000000000..5742191f91
--- /dev/null
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpGuideServiceImplTest.java
@@ -0,0 +1,56 @@
+package me.chanjar.weixin.mp.api.impl;
+
+import com.google.inject.Inject;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.api.test.ApiTestModule;
+import me.chanjar.weixin.mp.bean.guide.WxMpGuideInfo;
+import me.chanjar.weixin.mp.bean.guide.WxMpGuideList;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * 单元测试.
+ *
+ * @author Binary Wang
+ * @date 2020-10-06
+ */
+@Guice(modules = ApiTestModule.class)
+public class WxMpGuideServiceImplTest {
+  @Inject
+  protected WxMpService wxService;
+
+  @Test
+  public void testAddGuide() throws WxErrorException {
+    this.wxService.getGuideService().addGuide("wx1java", "", null, null);
+  }
+
+  @Test
+  public void testAddGuide_another() throws WxErrorException {
+    this.wxService.getGuideService().addGuide(WxMpGuideInfo.builder().account("wx1java").build());
+  }
+
+  @Test
+  public void testGetGuide() throws WxErrorException {
+    final WxMpGuideInfo guideInfo = this.wxService.getGuideService().getGuide("wx1java", null);
+    assertThat(guideInfo).isNotNull();
+  }
+
+  @Test
+  public void testUpdateGuide() throws WxErrorException {
+    this.wxService.getGuideService().updateGuide(WxMpGuideInfo.builder().account("wx1java").nickName("我是谁").build());
+  }
+
+  @Test
+  public void testDelGuide() throws WxErrorException {
+    this.wxService.getGuideService().delGuide("wx1java", null);
+  }
+
+  @Test
+  public void testListGuide() throws WxErrorException {
+    final WxMpGuideList guideList = this.wxService.getGuideService().listGuide(0, 10);
+    assertThat(guideList).isNotNull();
+  }
+}
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImplTest.java
index 4d2c21bce9..21ca3236f5 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImplTest.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpImgProcServiceImplTest.java
@@ -2,7 +2,7 @@
 
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.common.util.fs.FileUtils;
-import me.chanjar.weixin.common.api.WxImgProcService;
+import me.chanjar.weixin.common.service.WxImgProcService;
 import me.chanjar.weixin.mp.api.WxMpService;
 import me.chanjar.weixin.mp.api.test.ApiTestModule;
 import me.chanjar.weixin.mp.api.test.TestConstants;
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOAuth2ServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOAuth2ServiceImplTest.java
new file mode 100644
index 0000000000..6004d3cbe2
--- /dev/null
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxMpOAuth2ServiceImplTest.java
@@ -0,0 +1,59 @@
+package me.chanjar.weixin.mp.api.impl;
+
+import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
+import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
+import me.chanjar.weixin.common.error.WxErrorException;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.api.test.ApiTestModule;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import javax.inject.Inject;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * 测试类.
+ *
+ * @author Binary Wang
+ * @date 2020-08-09
+ */
+@Test
+@Guice(modules = ApiTestModule.class)
+public class WxMpOAuth2ServiceImplTest {
+  @Inject
+  private WxMpService mpService;
+
+  @Test
+  public void testBuildAuthorizationUrl() {
+    final String url = this.mpService.getOAuth2Service().buildAuthorizationUrl("http://www.baidu.com", "test", "GOD");
+    assertThat(url).isEqualTo("https://open.weixin.qq.com/connect/oauth2/authorize?appid=" +
+      this.mpService.getWxMpConfigStorage().getAppId() +
+      "&redirect_uri=http%3A%2F%2Fwww.baidu.com&response_type=code&scope=test&state=GOD&connect_redirect=1#wechat_redirect");
+  }
+
+  @Test
+  public void testGetAccessToken() throws WxErrorException {
+    final WxOAuth2AccessToken accessToken = this.mpService.getOAuth2Service().getAccessToken("11");
+    assertThat(accessToken).isNotNull();
+  }
+
+  @Test
+  public void testRefreshAccessToken() throws WxErrorException {
+    final WxOAuth2AccessToken accessToken = this.mpService.getOAuth2Service().refreshAccessToken("11");
+    assertThat(accessToken).isNotNull();
+  }
+
+  @Test
+  public void testGetUserInfo() throws WxErrorException {
+    final WxOAuth2AccessToken accessToken = this.mpService.getOAuth2Service().getAccessToken("11");
+    final WxOAuth2UserInfo userInfo = this.mpService.getOAuth2Service().getUserInfo(accessToken, null);
+    assertThat(userInfo).isNotNull();
+  }
+
+  @Test
+  public void testValidateAccessToken() {
+    final boolean result = this.mpService.getOAuth2Service().validateAccessToken(new WxOAuth2AccessToken());
+    assertThat(result).isTrue();
+  }
+}
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxOAuth2ServiceImplTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxOAuth2ServiceImplTest.java
deleted file mode 100644
index 8729f99d2f..0000000000
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/impl/WxOAuth2ServiceImplTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package me.chanjar.weixin.mp.api.impl;
-
-import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.mp.api.test.ApiTestModule;
-import org.testng.annotations.Guice;
-import org.testng.annotations.Test;
-
-import javax.inject.Inject;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * 测试类.
- *
- * @author Binary Wang
- * @date 2020-08-09
- */
-@Test
-@Guice(modules = ApiTestModule.class)
-public class WxOAuth2ServiceImplTest {
-  @Inject
-  private WxMpService mpService;
-
-  @Test
-  public void testBuildAuthorizationUrl() {
-    final String url = this.mpService.getOAuth2Service().buildAuthorizationUrl("http://www.baidu.com", "test", "GOD");
-    assertThat(url).isEqualTo("https://open.weixin.qq.com/connect/oauth2/authorize?appid=" +
-      this.mpService.getWxMpConfigStorage().getAppId() +
-      "&redirect_uri=http%3A%2F%2Fwww.baidu.com&response_type=code&scope=test&state=GOD&connect_redirect=1#wechat_redirect");
-  }
-
-  @Test
-  public void testGetAccessToken() {
-  }
-
-  @Test
-  public void testRefreshAccessToken() {
-  }
-
-  @Test
-  public void testGetUserInfo() {
-  }
-
-  @Test
-  public void testValidateAccessToken() {
-  }
-}
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java
index cc964e80fd..204515934b 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/api/test/ApiTestModule.java
@@ -4,6 +4,7 @@
 import java.io.InputStream;
 import java.util.concurrent.locks.ReentrantLock;
 
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,7 +24,7 @@ public class ApiTestModule implements Module {
   public void configure(Binder binder) {
     try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(TEST_CONFIG_XML)) {
       if (inputStream == null) {
-        throw new RuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成");
+        throw new WxRuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成");
       }
 
       TestConfigStorage config = this.fromXml(TestConfigStorage.class, inputStream);
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/menu/WxMpMenuTest.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/menu/WxMpMenuTest.java
new file mode 100644
index 0000000000..3577306608
--- /dev/null
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/bean/menu/WxMpMenuTest.java
@@ -0,0 +1,152 @@
+package me.chanjar.weixin.mp.bean.menu;
+
+import org.testng.annotations.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * 单元测试.
+ *
+ * @author Binary Wang
+ * @date 2020-11-05
+ */
+public class WxMpMenuTest {
+
+  @Test
+  public void testFromJson() {
+    String json = "{\n" +
+      "    \"menu\": {\n" +
+      "        \"button\": [\n" +
+      "            {\n" +
+      "                \"type\": \"view\",\n" +
+      "                \"name\": \"阅读记录\",\n" +
+      "                \"sub_button\": []\n" +
+      "            },\n" +
+      "            {\n" +
+      "                \"type\": \"view\",\n" +
+      "                \"name\": \"\uD83D\uDC95秦枫\uD83D\uDC95\",\n" +
+      "                \"sub_button\": []\n" +
+      "            },\n" +
+      "            {\n" +
+      "                \"name\": \"签到送礼\",\n" +
+      "                \"sub_button\": [\n" +
+      "                    {\n" +
+      "                        \"type\": \"view\",\n" +
+      "                        \"name\": \"书城首页\",\n" +
+      "                        \"sub_button\": []\n" +
+      "                    },\n" +
+      "                    {\n" +
+      "                        \"type\": \"view\",\n" +
+      "                        \"name\": \"我要充值\",\n" +
+      "                        \"sub_button\": []\n" +
+      "                    },\n" +
+      "                    {\n" +
+      "                        \"type\": \"view\",\n" +
+      "                        \"name\": \"个人中心\",\n" +
+      "                        \"sub_button\": []\n" +
+      "                    },\n" +
+      "                    {\n" +
+      "                        \"type\": \"view\",\n" +
+      "                        \"name\": \"签到送礼\",\n" +
+      "                        \"sub_button\": []\n" +
+      "                    }\n" +
+      "                ]\n" +
+      "            }\n" +
+      "        ],\n" +
+      "        \"menuid\": 449778320\n" +
+      "    },\n" +
+      "    \"conditionalmenu\": [\n" +
+      "        {\n" +
+      "            \"button\": [\n" +
+      "                {\n" +
+      "                    \"type\": \"view\",\n" +
+      "                    \"name\": \"阅读记录\",\n" +
+      "                    \"sub_button\": []\n" +
+      "                },\n" +
+      "                {\n" +
+      "                    \"type\": \"view\",\n" +
+      "                    \"name\": \"\uD83D\uDC95秦枫\uD83D\uDC95\",\n" +
+      "                    \"sub_button\": []\n" +
+      "                },\n" +
+      "                {\n" +
+      "                    \"name\": \"签到送礼\",\n" +
+      "                    \"sub_button\": [\n" +
+      "                        {\n" +
+      "                            \"type\": \"view\",\n" +
+      "                            \"name\": \"书城首页\",\n" +
+      "                            \"sub_button\": []\n" +
+      "                        },\n" +
+      "                        {\n" +
+      "                            \"type\": \"view\",\n" +
+      "                            \"name\": \"我要看书\",\n" +
+      "                            \"sub_button\": []\n" +
+      "                        },\n" +
+      "                        {\n" +
+      "                            \"type\": \"view\",\n" +
+      "                            \"name\": \"个人中心\",\n" +
+      "                            \"sub_button\": []\n" +
+      "                        },\n" +
+      "                        {\n" +
+      "                            \"type\": \"view\",\n" +
+      "                            \"name\": \"签到送礼\",\n" +
+      "                            \"sub_button\": []\n" +
+      "                        }\n" +
+      "                    ]\n" +
+      "                }\n" +
+      "            ],\n" +
+      "            \"matchrule\": {\n" +
+      "                \"client_platform_type\": \"1\"\n" +
+      "            },\n" +
+      "            \"menuid\": 449778326\n" +
+      "        },\n" +
+      "        {\n" +
+      "            \"button\": [\n" +
+      "                {\n" +
+      "                    \"type\": \"view\",\n" +
+      "                    \"name\": \"阅读记录\",\n" +
+      "                    \"sub_button\": []\n" +
+      "                },\n" +
+      "                {\n" +
+      "                    \"type\": \"view\",\n" +
+      "                    \"name\": \"\uD83D\uDC95秦枫\uD83D\uDC95\",\n" +
+      "                    \"sub_button\": []\n" +
+      "                },\n" +
+      "                {\n" +
+      "                    \"name\": \"签到送礼\",\n" +
+      "                    \"sub_button\": [\n" +
+      "                        {\n" +
+      "                            \"type\": \"view\",\n" +
+      "                            \"name\": \"书城首页\",\n" +
+      "                            \"sub_button\": []\n" +
+      "                        },\n" +
+      "                        {\n" +
+      "                            \"type\": \"view\",\n" +
+      "                            \"name\": \"我要充值\",\n" +
+      "                            \"sub_button\": []\n" +
+      "                        },\n" +
+      "                        {\n" +
+      "                            \"type\": \"view\",\n" +
+      "                            \"name\": \"个人中心\",\n" +
+      "                            \"sub_button\": []\n" +
+      "                        },\n" +
+      "                        {\n" +
+      "                            \"type\": \"view\",\n" +
+      "                            \"name\": \"签到送礼\",\n" +
+      "                            \"sub_button\": []\n" +
+      "                        }\n" +
+      "                    ]\n" +
+      "                }\n" +
+      "            ],\n" +
+      "            \"matchrule\": {\n" +
+      "                \"client_platform_type\": \"2\"\n" +
+      "            },\n" +
+      "            \"menuid\": 449778324\n" +
+      "        }\n" +
+      "    ]\n" +
+      "}";
+
+    final WxMpMenu menu = WxMpMenu.fromJson(json);
+    assertThat(menu).isNotNull();
+    assertThat(menu.getConditionalMenu().get(0).getRule().getClientPlatformType()).isEqualTo("1");
+  }
+}
diff --git a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpOAuth2Servlet.java b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpOAuth2Servlet.java
index 476a56a656..d0c5dfcec2 100644
--- a/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpOAuth2Servlet.java
+++ b/weixin-java-mp/src/test/java/me/chanjar/weixin/mp/demo/WxMpOAuth2Servlet.java
@@ -1,8 +1,9 @@
 package me.chanjar.weixin.mp.demo;
 
+import me.chanjar.weixin.common.bean.WxOAuth2UserInfo;
 import me.chanjar.weixin.common.error.WxErrorException;
 import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
+import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;
 import me.chanjar.weixin.mp.bean.result.WxMpUser;
 
 import javax.servlet.http.HttpServlet;
@@ -31,17 +32,17 @@ protected void service(HttpServletRequest request, HttpServletResponse response)
       response.getWriter().println("

code

"); response.getWriter().println(code); - WxMpOAuth2AccessToken wxMpOAuth2AccessToken = this.wxMpService.getOAuth2Service().getAccessToken(code); + WxOAuth2AccessToken oAuth2AccessToken = this.wxMpService.getOAuth2Service().getAccessToken(code); response.getWriter().println("

access token

"); - response.getWriter().println(wxMpOAuth2AccessToken.toString()); + response.getWriter().println(oAuth2AccessToken.toString()); - WxMpUser wxMpUser = this.wxMpService.getOAuth2Service().getUserInfo(wxMpOAuth2AccessToken, null); + WxOAuth2UserInfo wxMpUser = this.wxMpService.getOAuth2Service().getUserInfo(oAuth2AccessToken, null); response.getWriter().println("

user info

"); response.getWriter().println(wxMpUser.toString()); - wxMpOAuth2AccessToken = this.wxMpService.getOAuth2Service().refreshAccessToken(wxMpOAuth2AccessToken.getRefreshToken()); + oAuth2AccessToken = this.wxMpService.getOAuth2Service().refreshAccessToken(oAuth2AccessToken.getRefreshToken()); response.getWriter().println("

after refresh

"); - response.getWriter().println(wxMpOAuth2AccessToken.toString()); + response.getWriter().println(oAuth2AccessToken.toString()); } catch (WxErrorException e) { e.printStackTrace(); diff --git a/weixin-java-open/pom.xml b/weixin-java-open/pom.xml index cf743a4042..a7bd9d05b4 100644 --- a/weixin-java-open/pom.xml +++ b/weixin-java-open/pom.xml @@ -7,7 +7,7 @@ com.github.binarywang wx-java - 3.9.0 + 4.0.0 weixin-java-open @@ -122,7 +122,7 @@ 3.5.1 - cn.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor + com.github.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java index 68dfb3d60b..b0faef7a69 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenComponentService.java @@ -2,8 +2,7 @@ import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; +import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; import me.chanjar.weixin.open.bean.WxOpenCreateResult; import me.chanjar.weixin.open.bean.WxOpenGetResult; import me.chanjar.weixin.open.bean.WxOpenMaCodeTemplate; @@ -133,7 +132,7 @@ public interface WxOpenComponentService { * @param appid the appid * @return the wx mp service by appid */ - WxMpService getWxMpServiceByAppid(String appid); + WxOpenMpService getWxMpServiceByAppid(String appid); /** * 获取指定appid的开放平台小程序服务(继承一般小程序服务能力). @@ -332,7 +331,7 @@ public interface WxOpenComponentService { * @return the wx mp o auth 2 access token * @throws WxErrorException the wx error exception */ - WxMpOAuth2AccessToken oauth2getAccessToken(String appid, String code) throws WxErrorException; + WxOAuth2AccessToken oauth2getAccessToken(String appid, String code) throws WxErrorException; /** * Check signature boolean. @@ -353,7 +352,7 @@ public interface WxOpenComponentService { * @return the wx mp o auth 2 access token * @throws WxErrorException the wx error exception */ - WxMpOAuth2AccessToken oauth2refreshAccessToken(String appid, String refreshToken) throws WxErrorException; + WxOAuth2AccessToken oauth2refreshAccessToken(String appid, String refreshToken) throws WxErrorException; /** * Oauth 2 build authorization url string. diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMpService.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMpService.java new file mode 100644 index 0000000000..098600a07d --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/WxOpenMpService.java @@ -0,0 +1,47 @@ +package me.chanjar.weixin.open.api; + +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.mp.api.WxMpService; +import me.chanjar.weixin.open.bean.mp.FastRegisterResult; + +/** + *
+ *     微信开放平台代公众号实现服务能力
+ *     https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489144594_DhNoV&token=&lang=zh_CN
+ * 
+ *

+ * Created by zpf on 2020/10/15 + */ +public interface WxOpenMpService extends WxMpService { + + /** + * 取复用公众号快速注册小程序的授权链接. + */ + String URL_FAST_REGISTER_AUTH = "https://mp.weixin.qq.com/cgi-bin/fastregisterauth?appid=%s&component_appid=%s©_wx_verify=%s&redirect_uri=%s"; + + /** + * 复用公众号快速注册小程序 + */ + String API_FAST_REGISTER = "https://api.weixin.qq.com/cgi-bin/account/fastregister"; + + /** + * 取复用公众号快速注册小程序的授权链接 + * https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Official_Accounts/fast_registration_of_mini_program.html + * + * @param redirectUri 用户扫码授权后,MP 扫码页面将跳转到该地址(注:1.链接需 urlencode 2.Host 需和第三方平台在微信开放平台上面填写的登 录授权的发起页域名一致) + * @param copyWxVerify 是否复用公众号的资质进行微信认证,可空,默认false + * @return 返回授权链接 ,注意:由于微信开放平台限制,此链接直接使用后端301重定向微信会报错,必须是在第三方平台所在域名的页面的html或js触发跳转才能成功 + */ + String getFastRegisterAuthUrl(String redirectUri, Boolean copyWxVerify); + + /** + * 复用公众号快速注册小程序 + * 注意:调用本接口的第三方平台必须是已经全网发布的,否则微信会报-1服务器繁忙错误,然后再报ticket无效错误,并且接口的使用次数会增加,同时还会生成一个废小程序 + * https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/Official_Accounts/fast_registration_of_mini_program.html + * + * @param ticket 公众号扫码授权的凭证(公众平台扫码页面回跳到第三方平台时携带) + * @return 返回授权码, 然后请使用第三方平台的sdk获得授权, 参考: WxOpenService.getWxOpenComponentService().getQueryAuth( fastRegisterResult.getAuthorizationCode() ); + * @throws WxErrorException the wx error exception + */ + FastRegisterResult fastRegister(String ticket) throws WxErrorException; +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java index d460152dfa..b6fc3a8a32 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenComponentServiceImpl.java @@ -8,12 +8,13 @@ import me.chanjar.weixin.common.api.WxConsts; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.crypto.SHA1; import me.chanjar.weixin.common.util.http.URIUtil; import me.chanjar.weixin.common.util.json.GsonParser; import me.chanjar.weixin.common.util.json.WxGsonBuilder; import me.chanjar.weixin.mp.api.WxMpService; -import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; +import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; import me.chanjar.weixin.open.api.*; import me.chanjar.weixin.open.bean.*; import me.chanjar.weixin.open.bean.auth.WxOpenAuthorizationInfo; @@ -36,14 +37,14 @@ public class WxOpenComponentServiceImpl implements WxOpenComponentService { private static final Map WX_OPEN_MA_SERVICE_MAP = new ConcurrentHashMap<>(); - private static final Map WX_OPEN_MP_SERVICE_MAP = new ConcurrentHashMap<>(); + private static final Map WX_OPEN_MP_SERVICE_MAP = new ConcurrentHashMap<>(); private static final Map WX_OPEN_FAST_MA_SERVICE_MAP = new ConcurrentHashMap<>(); private final WxOpenService wxOpenService; @Override - public WxMpService getWxMpServiceByAppid(String appId) { - WxMpService wxMpService = WX_OPEN_MP_SERVICE_MAP.get(appId); + public WxOpenMpService getWxMpServiceByAppid(String appId) { + WxOpenMpService wxMpService = WX_OPEN_MP_SERVICE_MAP.get(appId); if (wxMpService == null) { synchronized (WX_OPEN_MP_SERVICE_MAP) { wxMpService = WX_OPEN_MP_SERVICE_MAP.get(appId); @@ -381,10 +382,10 @@ public String getAuthorizerAccessToken(String appId, boolean forceRefresh) throw WxOpenAuthorizerAccessToken wxOpenAuthorizerAccessToken = WxOpenAuthorizerAccessToken.fromJson(responseContent); config.updateAuthorizerAccessToken(appId, wxOpenAuthorizerAccessToken); - config.updateAuthorizerRefreshToken(appId,wxOpenAuthorizerAccessToken.getAuthorizerRefreshToken()); + config.updateAuthorizerRefreshToken(appId, wxOpenAuthorizerAccessToken.getAuthorizerRefreshToken()); return config.getAuthorizerAccessToken(appId); } catch (InterruptedException e) { - throw new RuntimeException(e); + throw new WxRuntimeException(e); } finally { if (locked) { lock.unlock(); @@ -393,10 +394,10 @@ public String getAuthorizerAccessToken(String appId, boolean forceRefresh) throw } @Override - public WxMpOAuth2AccessToken oauth2getAccessToken(String appId, String code) throws WxErrorException { + public WxOAuth2AccessToken oauth2getAccessToken(String appId, String code) throws WxErrorException { String url = String.format(OAUTH2_ACCESS_TOKEN_URL, appId, code, getWxOpenConfigStorage().getComponentAppId()); String responseContent = get(url); - return WxMpOAuth2AccessToken.fromJson(responseContent); + return WxOAuth2AccessToken.fromJson(responseContent); } @Override @@ -405,10 +406,10 @@ public boolean checkSignature(String appid, String timestamp, String nonce, Stri } @Override - public WxMpOAuth2AccessToken oauth2refreshAccessToken(String appId, String refreshToken) throws WxErrorException { + public WxOAuth2AccessToken oauth2refreshAccessToken(String appId, String refreshToken) throws WxErrorException { String url = String.format(OAUTH2_REFRESH_TOKEN_URL, appId, refreshToken, getWxOpenConfigStorage().getComponentAppId()); String responseContent = get(url); - return WxMpOAuth2AccessToken.fromJson(responseContent); + return WxOAuth2AccessToken.fromJson(responseContent); } @Override @@ -488,7 +489,7 @@ private String openAccountServicePost(String appId, String appIdType, String req result = maService.post(requestUrl, param.toString()); return result; default: - throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("appIdType类型异常").build()); + throw new WxErrorException("appIdType类型异常"); } } diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java index a8c252bae0..24d9e23414 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMaServiceImpl.java @@ -4,7 +4,7 @@ import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl; import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; import cn.binarywang.wx.miniapp.config.WxMaConfig; -import cn.binarywang.wx.miniapp.util.json.WxMaGsonBuilder; +import cn.binarywang.wx.miniapp.json.WxMaGsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import me.chanjar.weixin.common.error.WxErrorException; diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java index 5efa429ade..19e103fa24 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenMpServiceImpl.java @@ -1,15 +1,22 @@ package me.chanjar.weixin.open.api.impl; +import com.google.common.collect.ImmutableMap; +import lombok.SneakyThrows; import me.chanjar.weixin.common.error.WxErrorException; -import me.chanjar.weixin.mp.config.WxMpConfigStorage; import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; -import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; +import me.chanjar.weixin.mp.config.WxMpConfigStorage; import me.chanjar.weixin.open.api.WxOpenComponentService; +import me.chanjar.weixin.open.api.WxOpenMpService; +import me.chanjar.weixin.open.bean.mp.FastRegisterResult; + +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Objects; /** * @author 007 */ -public class WxOpenMpServiceImpl extends WxMpServiceImpl { +public class WxOpenMpServiceImpl extends WxMpServiceImpl implements WxOpenMpService { private WxOpenComponentService wxOpenComponentService; private WxMpConfigStorage wxMpConfigStorage; private String appId; @@ -31,4 +38,18 @@ public String getAccessToken(boolean forceRefresh) throws WxErrorException { return wxOpenComponentService.getAuthorizerAccessToken(appId, forceRefresh); } + @SneakyThrows + @Override + public String getFastRegisterAuthUrl(String redirectUri, Boolean copyWxVerify) { + String copyInfo = Objects.equals(copyWxVerify, false) ? "0" : "1"; + String componentAppId = wxOpenComponentService.getWxOpenConfigStorage().getComponentAppId(); + String encoded = URLEncoder.encode(redirectUri, "UTF-8"); + return String.format(URL_FAST_REGISTER_AUTH, appId, componentAppId, copyInfo, encoded); + } + + @Override + public FastRegisterResult fastRegister(String ticket) throws WxErrorException { + String json = post(API_FAST_REGISTER, ImmutableMap.of("ticket", ticket)); + return FastRegisterResult.fromJson(json); + } } diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenOAuth2ServiceImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenOAuth2ServiceImpl.java new file mode 100644 index 0000000000..19739e9e44 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenOAuth2ServiceImpl.java @@ -0,0 +1,79 @@ +package me.chanjar.weixin.open.api.impl; + +import lombok.AllArgsConstructor; +import me.chanjar.weixin.common.bean.WxOAuth2UserInfo; +import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; +import me.chanjar.weixin.common.enums.WxType; +import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; +import me.chanjar.weixin.common.service.WxOAuth2Service; +import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; +import me.chanjar.weixin.common.util.http.URIUtil; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; + +import static me.chanjar.weixin.mp.enums.WxMpApiUrl.OAuth2.*; + +/** + * oauth2接口实现. + * + * @author Binary Wang + * @date 2020-10-19 + */ +@AllArgsConstructor +public class WxOpenOAuth2ServiceImpl extends WxOpenServiceImpl implements WxOAuth2Service { + private final String appId; + private final String appSecret; + + @Override + public String buildAuthorizationUrl(String redirectUri, String scope, String state) { + return String.format(CONNECT_OAUTH2_AUTHORIZE_URL.getUrl(null), + this.appId, URIUtil.encodeURIComponent(redirectUri), scope, StringUtils.trimToEmpty(state)); + } + + private WxOAuth2AccessToken getOAuth2AccessToken(String url) throws WxErrorException { + return WxOAuth2AccessToken.fromJson(this.get(url, null)); + } + + @Override + public WxOAuth2AccessToken getAccessToken(String code) throws WxErrorException { + return this.getAccessToken(this.appId, this.appSecret, code); + } + + @Override + public WxOAuth2AccessToken getAccessToken(String appId, String appSecret, String code) throws WxErrorException { + return this.getOAuth2AccessToken(String.format(OAUTH2_ACCESS_TOKEN_URL.getUrl(null), appId, appSecret, code)); + } + + @Override + public WxOAuth2AccessToken refreshAccessToken(String refreshToken) throws WxErrorException { + String url = String.format(OAUTH2_REFRESH_TOKEN_URL.getUrl(null), this.appId, refreshToken); + return this.getOAuth2AccessToken(url); + } + + @Override + public WxOAuth2UserInfo getUserInfo(WxOAuth2AccessToken token, String lang) throws WxErrorException { + if (lang == null) { + lang = "zh_CN"; + } + + String url = String.format(OAUTH2_USERINFO_URL.getUrl(null), token.getAccessToken(), token.getOpenId(), lang); + + return WxOAuth2UserInfo.fromJson(this.get(url, null)); + } + + @Override + public boolean validateAccessToken(WxOAuth2AccessToken token) { + String url = String.format(OAUTH2_VALIDATE_TOKEN_URL.getUrl(null), token.getAccessToken(), token.getOpenId()); + + try { + SimpleGetRequestExecutor.create(this).execute(url, null, WxType.MP); + } catch (IOException e) { + throw new WxRuntimeException(e); + } catch (WxErrorException e) { + return false; + } + return true; + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java index eb4ffbfb2b..fa89d09377 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/api/impl/WxOpenServiceAbstractImpl.java @@ -3,6 +3,7 @@ import me.chanjar.weixin.common.enums.WxType; import me.chanjar.weixin.common.error.WxError; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.http.RequestExecutor; import me.chanjar.weixin.common.util.http.RequestHttp; import me.chanjar.weixin.open.api.WxOpenComponentService; @@ -56,7 +57,7 @@ protected T execute(RequestExecutor executor, String uri, E data) t return null; } catch (IOException e) { this.log.error("\n【请求地址】: {}\n【请求参数】:{}\n【异常信息】:{}", uri, data, e.getMessage()); - throw new RuntimeException(e); + throw new WxRuntimeException(e); } } } diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java index bef7d16d26..7a41355920 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/message/WxOpenXmlMessage.java @@ -4,6 +4,7 @@ import com.thoughtworks.xstream.annotations.XStreamConverter; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage; import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage; @@ -149,7 +150,7 @@ public static WxOpenXmlMessage fromEncryptedXml(InputStream is, WxOpenConfigStor return fromEncryptedXml(IOUtils.toString(is, StandardCharsets.UTF_8), wxOpenConfigStorage, timestamp, nonce, msgSignature); } catch (IOException e) { - throw new RuntimeException(e); + throw new WxRuntimeException(e); } } } diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/mp/FastRegisterResult.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/mp/FastRegisterResult.java new file mode 100644 index 0000000000..17d533e5e4 --- /dev/null +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/bean/mp/FastRegisterResult.java @@ -0,0 +1,39 @@ +package me.chanjar.weixin.open.bean.mp; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import me.chanjar.weixin.common.util.json.WxGsonBuilder; + +import java.io.Serializable; + +/** + * 复用公众号资料快速注册小程序结果 + * + * @author someone + */ +@Data +public class FastRegisterResult implements Serializable { + private static final long serialVersionUID = 9046726183433147089L; + + /** + * 小程序AppId + */ + @SerializedName("appid") + private String appId; + + /** + * 授权码,然后请使用第三方平台的sdk获得授权, 参考: WxOpenService.getWxOpenComponentService().getQueryAuth( this.getAuthorizationCode() ); + */ + @SerializedName("authorization_code") + private String authorizationCode; + + /** + * 是否与公众号关联成功 + */ + @SerializedName("is_wx_verify_succ") + private Boolean isWxVerifySucc; + + public static FastRegisterResult fromJson(String json) { + return WxGsonBuilder.create().fromJson(json, FastRegisterResult.class); + } +} diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java index 7f24674d9f..fc664483e6 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeJoddHttpRequestExecutor.java @@ -19,6 +19,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.UUID; /** @@ -49,7 +50,7 @@ public File execute(String uri, WxMaQrcodeParam qrcodeParam, WxType wxType) thro request.withConnectionProvider(requestHttp.getRequestHttpClient()); HttpResponse response = request.send(); - response.charset(StringPool.UTF_8); + response.charset(StandardCharsets.UTF_8.name()); String contentTypeHeader = response.header("Content-Type"); if (MimeTypes.MIME_TEXT_PLAIN.equals(contentTypeHeader)) { String responseContent = response.bodyText(); diff --git a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeRequestExecutor.java b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeRequestExecutor.java index dfaec08565..ac02c1ec3d 100644 --- a/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeRequestExecutor.java +++ b/weixin-java-open/src/main/java/me/chanjar/weixin/open/executor/MaQrCodeRequestExecutor.java @@ -38,7 +38,7 @@ public static RequestExecutor create(RequestHttp requestH case OK_HTTP: return new MaQrCodeOkhttpRequestExecutor(requestHttp); default: - throw new WxErrorException(WxError.builder().errorCode(-1).errorMsg("不支持的http框架").build()); + throw new WxErrorException("不支持的http框架"); } } diff --git a/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenOAuth2ServiceImplTest.java b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenOAuth2ServiceImplTest.java new file mode 100644 index 0000000000..c32eb1fcfe --- /dev/null +++ b/weixin-java-open/src/test/java/me/chanjar/weixin/open/api/impl/WxOpenOAuth2ServiceImplTest.java @@ -0,0 +1,51 @@ +package me.chanjar.weixin.open.api.impl; + +import me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken; +import me.chanjar.weixin.common.error.WxErrorException; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +/** + * 单元测试. + * + * @author Binary Wang + * @date 2020-10-19 + */ +public class WxOpenOAuth2ServiceImplTest { + private final WxOpenOAuth2ServiceImpl service = new WxOpenOAuth2ServiceImpl("123", ""); + + @BeforeTest + public void init() { + this.service.setWxOpenConfigStorage(new WxOpenInMemoryConfigStorage()); + } + + @Test + public void testBuildAuthorizationUrl() { + this.service.buildAuthorizationUrl("", "", ""); + } + + @Test + public void testGetAccessToken() throws WxErrorException { + this.service.getAccessToken("a"); + } + + @Test + public void testTestGetAccessToken() throws WxErrorException { + this.service.getAccessToken("", "", ""); + } + + @Test + public void testRefreshAccessToken() throws WxErrorException { + this.service.refreshAccessToken(""); + } + + @Test + public void testGetUserInfo() throws WxErrorException { + this.service.getUserInfo(new WxOAuth2AccessToken(), ""); + } + + @Test + public void testValidateAccessToken() { + this.service.validateAccessToken(new WxOAuth2AccessToken()); + } +} diff --git a/weixin-java-pay/pom.xml b/weixin-java-pay/pom.xml index 1a51f3aad2..b992e899ed 100644 --- a/weixin-java-pay/pom.xml +++ b/weixin-java-pay/pom.xml @@ -5,7 +5,7 @@ com.github.binarywang wx-java - 3.9.0 + 4.0.0 4.0.0 @@ -102,7 +102,7 @@ 3.5.1 - cn.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor + com.github.binarywang.wx.graal.GraalProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor,lombok.launch.AnnotationProcessorHider$ClaimingProcessor diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsRequest.java index bd021fc571..00516eabb6 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsRequest.java @@ -792,6 +792,18 @@ public static class SalesSceneInfo implements Serializable { @SerializedName(value = "store_qr_code") private String storeQrCode; + /** + *

+     * 字段名:小程序AppID
+     * 变量名:mini_program_sub_appid
+     * 是否必填:否
+     * 类型:string(256)
+     * 描述:
+     * 
+ */ + @SerializedName(value = "mini_program_sub_appid") + private String miniProgramSubAppid; + } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java index 7defd21452..a12c3d4a8d 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ApplymentsStatusResult.java @@ -7,9 +7,14 @@ import java.io.Serializable; import java.util.List; +/** + * 二级商户进件 查询申请状态结果响应 + * + */ @Data @NoArgsConstructor public class ApplymentsStatusResult implements Serializable { + private static final long serialVersionUID = 1488464536143984732L; /** *
    * 字段名:申请状态
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsNotifyResult.java
new file mode 100644
index 0000000000..dcfae88247
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsNotifyResult.java
@@ -0,0 +1,29 @@
+package com.github.binarywang.wxpay.bean.ecommerce;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+/**
+ * 合单支付 通知结果
+ * 
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_7.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class CombineTransactionsNotifyResult implements Serializable { + + private static final long serialVersionUID = -4710926828683593250L; + /** + * 源数据 + */ + private NotifyResponse rawData; + + /** + * 解密后的数据 + */ + private CombineTransactionsResult result; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsRequest.java new file mode 100644 index 0000000000..9cc0d4b33c --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsRequest.java @@ -0,0 +1,459 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 合单支付API + *
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e-combine.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class CombineTransactionsRequest implements Serializable { + private static final long serialVersionUID = -1242741645939606441L; + /** + *
+   * 字段名:合单商户appid
+   * 变量名:combine_appid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *   合单发起方的appid。
+   *  示例值:wxd678efh567hg6787
+   * 
+ */ + @SerializedName(value = "combine_appid") + private String combineAppid; + + /** + *
+   * 字段名:合单商户号
+   * 变量名:combine_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  合单发起方商户号。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "combine_mchid") + private String combineMchid; + + /** + *
+   * 字段名:合单商户订单号
+   * 变量名:combine_out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  合单支付总订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
+   *  示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "combine_out_trade_no") + private String combineOutTradeNo; + + /** + *
+   * 字段名:+场景信息
+   * 变量名:scene_info
+   * 是否必填:否
+   * 类型:object
+   * 描述:支付场景信息描述
+   * 
+ */ + @SerializedName(value = "scene_info") + private SceneInfo sceneInfo; + + /** + *
+   * 字段名:+子单信息
+   * 变量名:sub_orders
+   * 是否必填:是
+   * 类型:array
+   * 描述:
+   *  最多支持子单条数:50
+   *
+   * 
+ */ + @SerializedName(value = "sub_orders") + private List subOrders; + + /** + *
+   * 字段名:+支付者
+   * 变量名:combine_payer_info
+   * 是否必填:否(JSAPI必填)
+   * 类型:object
+   * 描述:支付者信息
+   * 
+ */ + @SerializedName(value = "combine_payer_info") + private CombinePayerInfo combinePayerInfo; + + /** + *
+   * 字段名:交易起始时间
+   * 变量名:time_start
+   * 是否必填:否
+   * 类型:string(14)
+   * 描述:
+   *  订单生成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+   *  示例值:2019-12-31T15:59:60+08:00
+   * 
+ */ + @SerializedName(value = "time_start") + private String timeStart; + + /** + *
+   * 字段名:交易结束时间
+   * 变量名:time_expire
+   * 是否必填:否
+   * 类型:string(14)
+   * 描述:
+   *  订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+   *  示例值:2019-12-31T15:59:60+08:00
+   * 
+ */ + @SerializedName(value = "time_expire") + private String timeExpire; + + /** + *
+   * 字段名:通知地址
+   * 变量名:notify_url
+   * 是否必填:是
+   * 类型:string(256)
+   * 描述:
+   *  接收微信支付异步通知回调地址,通知url必须为直接可访问的URL,不能携带参数。
+   *  格式: URL
+   *  示例值:https://yourapp.com/notify
+   * 
+ */ + @SerializedName(value = "notify_url") + private String notifyUrl; + + + @Data + @NoArgsConstructor + public static class SceneInfo implements Serializable { + /** + *
+     * 字段名:商户端设备号
+     * 变量名:device_id
+     * 是否必填:否
+     * 类型:string(16)
+     * 描述:
+     *  终端设备号(门店号或收银设备ID)。
+     *  特殊规则:长度最小7个字节
+     *  示例值:POS1:1
+     * 
+ */ + @SerializedName(value = "device_id") + private String deviceId; + + /** + *
+     * 字段名:用户终端IP
+     * 变量名:payer_client_ip
+     * 是否必填:是
+     * 类型:string(45)
+     * 描述:
+     *  用户端实际ip
+     *  格式: ip(ipv4+ipv6)
+     *  示例值:14.17.22.32
+     * 
+ */ + @SerializedName(value = "payer_client_ip") + private String payerClientIp; + + /** + *
+     * 字段名:H5场景信息
+     * 变量名:h5_info
+     * 是否必填:否(H5支付必填)
+     * 类型:object
+     * 描述:
+     *  H5场景信息
+     * 
+ */ + @SerializedName(value = "h5_info") + private H5Info h5Info; + } + + @Data + @NoArgsConstructor + public static class SubOrders implements Serializable { + /** + *
+     * 字段名:子单商户号
+     * 变量名:mchid
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  子单发起方商户号,必须与发起方appid有绑定关系。
+     *  示例值:1900000109
+     *  此处一般填写服务商商户号
+     * 
+ */ + @SerializedName(value = "mchid") + private String mchid; + + /** + *
+     * 字段名:附加信息
+     * 变量名:attach
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
+     *  示例值:深圳分店
+     * 
+ */ + @SerializedName(value = "attach") + private String attach; + + /** + *
+     * 字段名:+订单金额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:object
+     * 描述:
+     * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + + /** + *
+     * 字段名:子单商户订单号
+     * 变量名:out_trade_no
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
+     *  特殊规则:最小字符长度为6
+     *  示例值:20150806125346
+     * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + + /** + *
+     * 字段名:二级商户号
+     * 变量名:sub_mchid
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  二级商户商户号,由微信支付生成并下发。
+     *  注意:仅适用于电商平台 服务商
+     *  示例值:1900000109
+     * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+     * 字段名:商品描述
+     * 变量名:description
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  商品简单描述。需传入应用市场上的APP名字-实际商品名称,例如:天天爱消除-游戏充值。
+     *  示例值:腾讯充值中心-QQ会员充值
+     * 
+ */ + @SerializedName(value = "description") + private String description; + + /** + *
+     * 字段名:+结算信息
+     * 变量名:settle_info
+     * 是否必填:否
+     * 类型:Object
+     * 描述:结算信息
+     * 
+ */ + @SerializedName(value = "settle_info") + private SettleInfo settleInfo; + + } + + @Data + @NoArgsConstructor + public static class CombinePayerInfo implements Serializable { + /** + *
+     * 字段名:用户标识
+     * 变量名:openid
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName(value = "openid") + private String openid; + + } + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + /** + *
+     * 字段名:标价金额
+     * 变量名:total_amount
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  子单金额,单位为分。
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "total_amount") + private Integer totalAmount; + + /** + *
+     * 字段名:标价币种
+     * 变量名:currency
+     * 是否必填:是
+     * 类型:string(8)
+     * 描述:
+     *  符合ISO 4217标准的三位字母代码,人民币:CNY。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + + } + + @Data + @NoArgsConstructor + public static class SettleInfo implements Serializable{ + /** + *
+     * 字段名:是否指定分账
+     * 变量名:profit_sharing
+     * 是否必填:否
+     * 类型:bool
+     * 描述:
+     *  是否分账,与外层profit_sharing同时存在时,以本字段为准。
+     *  true:是
+     *  false:否
+     *  示例值:true
+     * 
+ */ + @SerializedName(value = "profit_sharing") + private Boolean profitSharing; + + /** + *
+     * 字段名:补差金额
+     * 变量名:subsidy_amount
+     * 是否必填:否
+     * 类型:int64
+     * 描述:
+     *  SettleInfo.profit_sharing为true时,该金额才生效。
+     *  示例值:10
+     * 
+ */ + @SerializedName(value = "subsidy_amount") + private Integer subsidyAmount; + + } + + @Data + @NoArgsConstructor + public static class H5Info implements Serializable { + + /** + *
+     * 字段名:场景类型
+     * 变量名:type
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  场景类型,枚举值:
+     *  iOS:IOS移动应用;
+     *  Android:安卓移动应用;
+     *  Wap:WAP网站应用;
+     *  示例值:iOS
+     * 
+ */ + @SerializedName(value = "type") + private String type; + + /** + *
+     * 字段名:应用名称
+     * 变量名:app_name
+     * 是否必填:否
+     * 类型:string(64)
+     * 描述:
+     *  应用名称
+     *  示例值:王者荣耀
+     * 
+ */ + @SerializedName(value = "app_name") + private String appName; + + /** + *
+     * 字段名:网站URL
+     * 变量名:app_url
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  网站URL
+     *  示例值:https://pay.qq.com
+     * 
+ */ + @SerializedName(value = "app_url") + private String appUrl; + + /** + *
+     * 字段名:iOS平台BundleID
+     * 变量名:bundle_id
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  iOS平台BundleID
+     *  示例值:com.tencent.wzryiOS
+     * 
+ */ + @SerializedName(value = "bundle_id") + private String bundleId; + + /** + *
+     * 字段名:Android平台PackageName
+     * 变量名:package_name
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  Android平台PackageName
+     *  示例值:com.tencent.tmgp.sgame
+     * 
+ */ + @SerializedName(value = "package_name") + private String packageName; + + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsResult.java new file mode 100644 index 0000000000..f8d13db88d --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/CombineTransactionsResult.java @@ -0,0 +1,353 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 合单支付 查询结果 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_3.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class CombineTransactionsResult implements Serializable { + + /** + *
+   * 字段名:合单商户appid
+   * 变量名:combine_appid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  合单发起方的appid。(即电商平台appid)
+   *  示例值:wxd678efh567hg6787
+   * 
+ */ + @SerializedName(value = "combine_appid") + private String combineAppid; + + /** + *
+   * 字段名:合单商户号
+   * 变量名:combine_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  合单发起方商户号。(即电商平台mchid)
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "combine_mchid") + private String combineMchid; + + /** + *
+   * 字段名:合单商户订单号
+   * 变量名:combine_out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  合单支付总订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
+   *  示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "combine_out_trade_no") + private String combineOutTradeNo; + + /** + *
+   * 字段名:+场景信息
+   * 变量名:scene_info
+   * 是否必填:否
+   * 类型:object
+   * 描述:支付场景信息描述
+   * 
+ */ + @SerializedName(value = "scene_info") + private SceneInfo sceneInfo; + + /** + *
+   * 字段名:+子单信息
+   * 变量名:sub_orders
+   * 是否必填:是
+   * 类型:array
+   * 描述:
+   *  最多支持子单条数:50
+   *
+   * 
+ */ + @SerializedName(value = "sub_orders") + private List subOrders; + + /** + *
+   * 字段名:+支付者
+   * 变量名:combine_payer_info
+   * 是否必填:否
+   * 类型:object
+   * 描述:示例值:见请求示例
+   * 
+ */ + @SerializedName(value = "combine_payer_info") + private CombinePayerInfo combinePayerInfo; + + @Data + @NoArgsConstructor + public static class SubOrders implements Serializable { + /** + *
+     * 字段名:子单商户号
+     * 变量名:mchid
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  子单发起方商户号,必须与发起方Appid有绑定关系。(即电商平台mchid)
+     *  示例值:1900000109
+     * 
+ */ + @SerializedName(value = "mchid") + private String mchid; + + /** + *
+     * 字段名:交易类型
+     * 变量名:trade_type
+     * 是否必填:是
+     * 类型:string (16)
+     * 描述:
+     *  枚举值:
+     *  NATIVE:扫码支付
+     *  JSAPI:公众号支付
+     *  APP:APP支付
+     *  MWEB:H5支付
+     *  示例值: JSAPI
+     * 
+ */ + @SerializedName(value = "trade_type") + private String tradeType; + + /** + *
+     * 字段名:交易状态
+     * 变量名:trade_state
+     * 是否必填:是
+     * 类型:string (32)
+     * 描述:
+     *  枚举值:
+     *  SUCCESS:支付成功
+     *  REFUND:转入退款
+     *  NOTPAY:未支付
+     *  CLOSED:已关闭
+     *  USERPAYING:用户支付中
+     *  PAYERROR:支付失败(其他原因,如银行返回失败)
+     *  示例值: SUCCESS
+     * 
+ */ + @SerializedName(value = "trade_state") + private String tradeState; + + /** + *
+     * 字段名:付款银行
+     * 变量名:bank_type
+     * 是否必填:否
+     * 类型:string(16)
+     * 描述:
+     *  银行类型,采用字符串类型的银行标识。
+     *  示例值:CMC
+     * 
+ */ + @SerializedName(value = "bank_type") + private String bankType; + + /** + *
+     * 字段名:附加信息
+     * 变量名:attach
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
+     *  示例值:深圳分店
+     * 
+ */ + @SerializedName(value = "attach") + private String attach; + + /** + *
+     * 字段名:支付完成时间
+     * 变量名:success_time
+     * 是否必填:是
+     * 类型:string(16)
+     * 描述:
+     *  订单支付时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日 13点29分35秒。
+     *  示例值:2015-05-20T13:29:35.120+08:00
+     * 
+ */ + @SerializedName(value = "success_time") + private String successTime; + + /** + *
+     * 字段名:微信订单号
+     * 变量名:transaction_id
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  微信支付订单号。
+     *  示例值: 1009660380201506130728806387
+     * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+     * 字段名:子单商户订单号
+     * 变量名:out_trade_no
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  商户系统内部订单号,要求32个字符内,只能是数字、大小写字母_-|*@ ,且在同一个商户号下唯一。
+     *  特殊规则:最小字符长度为6
+     *  示例值:20150806125346
+     * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + + /** + *
+     * 字段名:二级商户号
+     * 变量名:sub_mchid
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  二级商户商户号,由微信支付生成并下发。
+     *  注意:仅适用于电商平台 服务商
+     *  示例值:1900000109
+     * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+     * 字段名:+订单金额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:object
+     * 描述:订单金额信息
+     * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + + } + + @Data + @NoArgsConstructor + public static class SceneInfo implements Serializable { + /** + *
+     * 字段名:商户端设备号
+     * 变量名:device_id
+     * 是否必填:否
+     * 类型:string(16)
+     * 描述:
+     *  终端设备号(门店号或收银设备ID)。
+     *  特殊规则:长度最小7个字节
+     *  示例值:POS1:1
+     * 
+ */ + @SerializedName(value = "device_id") + private String deviceId; + + } + + @Data + @NoArgsConstructor + public static class CombinePayerInfo implements Serializable { + /** + *
+     * 字段名:用户标识
+     * 变量名:openid
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  使用合单appid获取的对应用户openid。是用户在商户appid下的唯一标识。
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName(value = "openid") + private String openid; + + } + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + /** + *
+     * 字段名:标价金额
+     * 变量名:total_amount
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  子单金额,单位为分。
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "total_amount") + private Integer totalAmount; + + /** + *
+     * 字段名:标价币种
+     * 变量名:currency
+     * 是否必填:是
+     * 类型:string(8)
+     * 描述:
+     *  符合ISO 4217标准的三位字母代码,人民币:CNY。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + + /** + *
+     * 字段名:现金支付金额
+     * 变量名:payer_amount
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  订单现金支付金额。
+     *  示例值:10
+     * 
+ */ + @SerializedName(value = "payer_amount") + private Integer payerAmount; + + /** + *
+     * 字段名:现金支付币种
+     * 变量名:payer_currency
+     * 是否必填:是
+     * 类型:string(8)
+     * 描述:
+     *  货币类型,符合ISO 4217标准的三位字母代码,默认人民币:CNY。
+     *  示例值: CNY
+     * 
+ */ + @SerializedName(value = "payer_currency") + private String payerCurrency; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FinishOrderRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FinishOrderRequest.java new file mode 100644 index 0000000000..1b09ba6ffc --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FinishOrderRequest.java @@ -0,0 +1,81 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +import java.io.Serializable; + +/** + * 完结分账 对象 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_5.shtml
+ * 
+ * @author: f00lish + * @date: 2020/09/12 + */ +@Data +@Builder +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class FinishOrderRequest implements Serializable { + + private static final long serialVersionUID = -8662837652326828377L; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  分账出资的电商平台二级商户,填写微信支付分配的商户号。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:微信订单号
+   * 变量名:transaction_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付订单号。
+   *  示例值:4208450740201411110007820472
+   * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+   * 字段名:商户分账单号
+   * 变量名:out_order_no
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  商户系统内部的分账单号,在商户系统内部唯一(单次分账、多次分账、完结分账应使用不同的商户分账单号),同一分账单号多次请求等同一次。
+   *  示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "out_order_no") + private String outOrderNo; + + /** + *
+   * 字段名:分账描述
+   * 变量名:description
+   * 是否必填:是
+   * 类型:string(80)
+   * 描述:
+   *  分账的原因描述,分账账单中需要体现。
+   *  示例值:分给商户1900000109
+   * 
+ */ + @SerializedName(value = "description") + private String description; + + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FundBalanceResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FundBalanceResult.java new file mode 100644 index 0000000000..e53b480c3f --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FundBalanceResult.java @@ -0,0 +1,57 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author: f00lish + * @date: 2020/09/12 + */ +@Data +@NoArgsConstructor +public class FundBalanceResult { + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台二级商户号,由微信支付生成并下发。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName("sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:可用余额
+   * 变量名:available_amount
+   * 是否必填:是
+   * 类型:int64
+   * 描述:
+   *  可用余额(单位:分),此余额可做提现操作。
+   *  示例值:100
+   * 
+ */ + @SerializedName("available_amount") + private Integer availableAmount; + + /** + *
+   * 字段名:不可用余额
+   * 变量名:pending_amount
+   * 是否必填:否
+   * 类型:int64
+   * 描述:
+   *  不可用余额(单位:分)。
+   *  示例值:100
+   * 
+ */ + @SerializedName("pending_amount") + private Integer pendingAmount; + + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FundBillRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FundBillRequest.java new file mode 100644 index 0000000000..9f12bc3781 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FundBillRequest.java @@ -0,0 +1,79 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +/** + * 资金账单请求 + * @author: f00lish + * @date: 2020/09/28 + */ +@Data +@Builder +@ToString +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class FundBillRequest { + + /** + *
+   * 字段名:账单日期
+   * 变量名:bill_date
+   * 是否必填:是
+   * 类型:string(10)
+   * 描述:
+   *  格式YYYY-MM-DD
+   *  仅支持三个月内的账单下载申请。
+   *  示例值:2019-06-11
+   * 
+ */ + @SerializedName(value = "bill_date") + private String billDate; + + + /** + *
+   * 字段名:资金账户类型
+   * 变量名:account_type
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  枚举值:
+   *  ALL:所有账户
+   *  示例值:ALL
+   * 
+ */ + @SerializedName(value = "account_type") + private String accountType; + + /** + *
+   * 字段名:压缩类型
+   * 变量名:tar_type
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  不填则以不压缩的方式返回数据流
+   *  枚举值:
+   *  GZIP:返回格式为.gzip的压缩包账单
+   *  示例值:GZIP
+   * 
+ */ + @SerializedName(value = "tar_type") + private String tarType; + + /** + *
+   * 字段名:加密算法
+   * 变量名:algorithm
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  枚举值:
+   *  AEAD_AES_256_GCM:AEAD_AES_256_GCM加密算法
+   *  示例值:AEAD_AES_256_GCM
+   * 
+ */ + @SerializedName(value = "algorithm") + private String algorithm; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FundBillResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FundBillResult.java new file mode 100644 index 0000000000..de0dcdb890 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/FundBillResult.java @@ -0,0 +1,139 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +/** + * 资金账单结果 + * @author: f00lish + * @date: 2020/09/28 + */ +@Data +@Builder +@ToString +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class FundBillResult { + + /** + *
+   * 字段名:下载信息总数
+   * 变量名:download_bill_count
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  下载信息总数
+   *  示例值:1
+   * 
+ */ + @SerializedName(value = "download_bill_count") + private int downloadBillCount; + + + + /** + *
+   * 字段名:下载信息明细
+   * 变量名:download_bill_list
+   * 是否必填:否
+   * 类型:array
+   * 描述:
+   *  下载信息明细
+   * 
+ */ + @SerializedName(value = "download_bill_list") + private FundBill[] downloadBillList; + + @Data + public static class FundBill { + + /** + *
+     * 字段名:账单文件序号
+     * 变量名:bill_sequence
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  商户将多个文件按账单文件序号的顺序合并为完整的资金账单文件,起始值为1
+     *  示例值:1
+     * 
+ */ + @SerializedName(value = "bill_sequence") + private String billSequence; + + /** + *
+     * 字段名:哈希类型
+     * 变量名:hash_type
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  枚举值:
+     *  SHA1:SHA1值
+     *  示例值:SHA1
+     * 
+ */ + @SerializedName(value = "hash_type") + private String hashType; + + /** + *
+     * 字段名:哈希值
+     * 变量名:hash_value
+     * 是否必填:是
+     * 类型:string(1024)
+     * 描述:
+     *  原始账单(gzip需要解压缩)的摘要值,用于校验文件的完整性。
+     *  示例值:79bb0f45fc4c42234a918000b2668d689e2bde04
+     * 
+ */ + @SerializedName(value = "hash_value") + private String hashValue; + + /** + *
+     * 字段名:账单下载地址
+     * 变量名:download_url
+     * 是否必填:是
+     * 类型:string(2048)
+     * 描述:
+     *  供下一步请求账单文件的下载地址,该地址30s内有效。
+     *  示例值:https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx
+     * 
+ */ + @SerializedName(value = "download_url") + private String downloadUrl; + + + /** + *
+     * 字段名:加密密钥
+     * 变量名:encrypt_key
+     * 是否必填:是
+     * 类型:string(512)
+     * 描述:
+     *  加密账单文件使用的加密密钥。密钥用商户证书的公钥进行加密,然后进行Base64编码
+     *  示例值:YpkbxSne+mDwyXq//xYPmtr9eQ5LsH7zLMZSs+GSEcY4wjhlsfioS4n9X6q1ZBL0wM1v5qd7KhWuj0rFJ4N1FidP7Q8KDy25QDTt46wiKnsPKSCAXWRFNw1D2JmJBqZsc9y5g0DupONWKYB2GfRigRDEBVszj67uOIILPdxOKX1w3N4jvu0U9IFanJa7ldm70KVvYrMWVgQFDPbgjh1gVDbuTAjmPN88AobLdkiegnBUS2woDZW+PfhPo13kweOiR3h1gXIKRlnKnN3Jkkwpna/AFFijXrFphO3voSuiV0CfptfzTtcae4X3DYG3RSroKqmpa+5tuy2aU2VJUSIuFQ==
+     * 
+ */ + @SerializedName(value = "encrypt_key") + private String encryptKey; + + /** + *
+     * 字段名:随机字符串
+     * 变量名:nonce
+     * 是否必填:是
+     * 类型:string(16)
+     * 描述:
+     *  加密账单文件使用的随机字符串
+     *  示例值:a8607ef79034c49c
+     * 
+ */ + @SerializedName(value = "nonce") + private String nonce; + + + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/NotifyResponse.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/NotifyResponse.java new file mode 100644 index 0000000000..4db416bdde --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/NotifyResponse.java @@ -0,0 +1,51 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 通知数据 + */ +@Data +@NoArgsConstructor +public class NotifyResponse implements Serializable { + private static final long serialVersionUID = 341873114458149365L; + @SerializedName(value = "id") + private String id; + + @SerializedName(value = "create_time") + private String createTime; + + @SerializedName(value = "event_type") + private String eventType; + + @SerializedName(value = "resource_type") + private String resourceType; + + @SerializedName(value = "resource") + private Resource resource; + + @SerializedName(value = "summary") + private String summary; + + @Data + @NoArgsConstructor + public static class Resource implements Serializable { + + @SerializedName(value = "algorithm") + private String algorithm; + + @SerializedName(value = "ciphertext") + private String ciphertext; + + @SerializedName(value = "associated_data") + private String associatedData; + + @SerializedName(value = "nonce") + private String nonce; + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsNotifyResult.java new file mode 100644 index 0000000000..03d9535fa8 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsNotifyResult.java @@ -0,0 +1,27 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 普通支付 通知结果 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/e_transactions/chapter3_11.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class PartnerTransactionsNotifyResult implements Serializable { + private static final long serialVersionUID = -6602962275015706689L; + /** + * 源数据 + */ + private NotifyResponse rawData; + + /** + * 解密后的数据 + */ + private PartnerTransactionsResult result; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsQueryRequest.java new file mode 100644 index 0000000000..2b90e432bb --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsQueryRequest.java @@ -0,0 +1,69 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +public class PartnerTransactionsQueryRequest implements Serializable { + + + /** + *
+   * 字段名:服务商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  服务商户号,由微信支付生成并下发
+   * 示例值:1230000109
+   * 
+ */ + @SerializedName(value = "sp_mchid") + private String spMchid; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  二级商户的商户号,有微信支付生成并下发。
+   * 示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:微信支付订单号
+   * 变量名:transaction_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付系统生成的订单号
+   * 示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+   * 特殊规则:最小字符长度为6
+   * 示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsRequest.java new file mode 100644 index 0000000000..ccfcc5f600 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsRequest.java @@ -0,0 +1,667 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 普通支付(电商收付通)API + *
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e_transactions.shtml
+ * 
+ * + * @author cloudX + */ +@Data +@NoArgsConstructor +public class PartnerTransactionsRequest implements Serializable { + private static final long serialVersionUID = -1550405819444680465L; + + /** + *
+   * 字段名:服务商公众号ID
+   * 变量名:sp_appid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  服务商申请的公众号或移动应用appid
+   *  示例值:wx8888888888888888
+   * 
+ */ + @SerializedName(value = "sp_appid") + private String spAppid; + + /** + *
+   * 字段名:服务商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  服务商户号,由微信支付生成并下发
+   *  示例值:1230000109
+   * 
+ */ + @SerializedName(value = "sp_mchid") + private String spMchid; + + /** + *
+   * 字段名:子商户公众号ID
+   * 变量名:sub_appid
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  子商户申请的公众号或移动应用appid。
+   *  示例值:wxd678efh567hg6999
+   * 
+ */ + @SerializedName(value = "sub_appid") + private String subAppid; + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  二级商户的商户号,有微信支付生成并下发。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:商品描述
+   * 变量名:description
+   * 是否必填:是
+   * 类型:string(127)
+   * 描述:
+   *  商品描述
+   *  示例值:Image形象店-深圳腾大-QQ公仔
+   * 
+ */ + @SerializedName(value = "description") + private String description; + + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(127)
+   * 描述:
+   *  商户系统内部订单号, 只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】
+   *  特殊规则:最小字符长度为6
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + + /** + *
+   * 字段名:交易结束时间
+   * 变量名:time_expire
+   * 是否必填:否
+   * 类型:string(14)
+   * 描述:
+   *  订单失效时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+   *  示例值:2019-12-31T15:59:60+08:00
+   * 
+ */ + @SerializedName(value = "time_expire") + private String timeExpire; + + /** + *
+   * 字段名:附加数据
+   * 变量名:attach
+   * 是否必填:否
+   * 类型:string(128)
+   * 描述:
+   *  附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用。
+   *  示例值:自定义数据
+   * 
+ */ + @SerializedName(value = "attach") + private String attach; + + /** + *
+   * 字段名:通知地址
+   * 变量名:notify_url
+   * 是否必填:是
+   * 类型:string(127)
+   * 描述:
+   *  通知URL必须为直接可访问的URL,不允许携带查询串。
+   *  示例值:https://www.weixin.qq.com/wxpay/pay.php
+   * 
+ */ + @SerializedName(value = "notify_url") + private String notifyUrl; + + /** + *
+   * 字段名:订单优惠标记
+   * 变量名:goods_tag
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  订单优惠标记
+   *  示例值:WXG
+   * 
+ */ + @SerializedName(value = "goods_tag") + private String goodsTag; + + /** + *
+   * 字段名:+结算信息
+   * 变量名:settle_info
+   * 是否必填:否
+   * 类型:Object
+   * 描述:结算信息
+   * 
+ */ + @SerializedName(value = "settle_info") + private SettleInfo settleInfo; + + /** + *
+   * 字段名:订单金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  订单金额信息
+   * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + + /** + *
+   * 字段名:优惠功能
+   * 变量名:detail
+   * 是否必填:否
+   * 类型:object
+   * 描述:
+   *  优惠功能
+   * 
+ */ + @SerializedName(value = "detail") + private Discount detail; + + /** + *
+   * 字段名:支付者
+   * 变量名:payer
+   * 是否必填:是(仅JSAPI支付必传)
+   * 类型:object
+   * 描述:
+   *  支付者信息
+   * 
+ */ + @SerializedName(value = "payer") + private Payer payer; + + /** + *
+   * 字段名:场景信息
+   * 变量名:scene_info
+   * 是否必填:是(仅H5支付必传)
+   * 类型:object
+   * 描述:
+   *  支付场景描述
+   * 
+ */ + @SerializedName(value = "scene_info") + private SceneInfo sceneInfo; + + @Data + @NoArgsConstructor + public static class Discount implements Serializable { + private static final long serialVersionUID = 1090134053810201492L; + + /** + *
+     * 字段名:订单原价
+     * 变量名:cost_price
+     * 是否必填:否
+     * 类型:int64
+     * 描述:
+     *  1、商户侧一张小票订单可能被分多次支付,订单原价用于记录整张小票的交易金额。
+     *  2、当订单原价与支付金额不相等,则不享受优惠。
+     *  3、该字段主要用于防止同一张小票分多次支付,以享受多次优惠的情况,正常支付订单不必上传此参数。
+     *  示例值:608800
+     * 
+ */ + @SerializedName(value = "cost_price") + private Integer costPrice; + + /** + *
+     * 字段名:商品小票ID
+     * 变量名:invoice_id
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述:
+     *  商品小票ID
+     *  示例值:微信123
+     * 
+ */ + @SerializedName(value = "invoice_id") + private String invoiceId; + + /** + *
+     * 字段名:单品列表
+     * 变量名:goods_detail
+     * 是否必填:否
+     * 类型:array
+     * 描述:
+     *  单品列表信息
+     *  条目个数限制:【1,undefined】
+     * 
+ */ + @SerializedName(value = "goods_detail") + private List goodsDetails; + } + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + private static final long serialVersionUID = -4967636398225864273L; + + /** + *
+     * 字段名:总金额
+     * 变量名:total
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  订单总金额,单位为分。
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "total") + private Integer total; + + /** + *
+     * 字段名:币类型
+     * 变量名:currency
+     * 是否必填:否
+     * 类型:string(16)
+     * 描述:
+     *  CNY:人民币,境内商户号仅支持人民币。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + + } + + @Data + @NoArgsConstructor + public static class Payer implements Serializable { + private static final long serialVersionUID = -3946401119476159971L; + + /** + *
+     * 字段名:用户服务标识
+     * 变量名:sp_openid
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  用户在服务商appid下的唯一标识。
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName(value = "sp_openid") + private String spOpenid; + + /** + *
+     * 字段名:用户子标识
+     * 变量名:sub_openid
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  用户在子商户appid下的唯一标识。
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName(value = "sub_openid") + private String subOpenid; + + } + + @Data + @NoArgsConstructor + public static class SettleInfo implements Serializable { + private static final long serialVersionUID = 4438958789491671746L; + + /** + *
+     * 字段名:是否指定分账
+     * 变量名:profit_sharing
+     * 是否必填:否
+     * 类型:bool
+     * 描述:
+     *  是否分账,与外层profit_sharing同时存在时,以本字段为准。
+     *  true:是
+     *  false:否
+     *  示例值:true
+     * 
+ */ + @SerializedName(value = "profit_sharing") + private Boolean profitSharing; + + /** + *
+     * 字段名:补差金额
+     * 变量名:subsidy_amount
+     * 是否必填:否
+     * 类型:int64
+     * 描述:
+     *  SettleInfo.profit_sharing为true时,该金额才生效。
+     *    注意:单笔订单最高补差金额为5000元
+     *  示例值:10
+     * 
+ */ + @SerializedName(value = "subsidy_amount") + private BigDecimal subsidyAmount; + + } + + @Data + @NoArgsConstructor + public static class GoodsDetail implements Serializable { + private static final long serialVersionUID = -2574001236925022932L; + + /** + *
+     * 字段名:商户侧商品编码
+     * 变量名:merchant_goods_id
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  由半角的大小写字母、数字、中划线、下划线中的一种或几种组成。
+     * 示例值:商品编码
+     * 
+ */ + @SerializedName(value = "merchant_goods_id") + private String merchantGoodsId; + + /** + *
+     * 字段名:微信侧商品编码
+     * 变量名:wechatpay_goods_id
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述:
+     *  微信支付定义的统一商品编号(没有可不传)
+     * 示例值:1001
+     * 
+ */ + @SerializedName(value = "wechatpay_goods_id") + private String wechatpayGoodsId; + + /** + *
+     * 字段名:商品名称
+     * 变量名:goods_name
+     * 是否必填:否
+     * 类型:string(256)
+     * 描述:
+     *  商品的实际名称
+     * 示例值:iPhoneX 256G
+     * 
+ */ + @SerializedName(value = "goods_name") + private String goodsName; + + /** + *
+     * 字段名:商品数量
+     * 变量名:quantity
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  用户购买的数量
+     * 示例值:1
+     * 
+ */ + @SerializedName(value = "quantity") + private Integer quantity; + + /** + *
+     * 字段名:商品单价
+     * 变量名:unit_price
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  商品单价,单位为分
+     * 示例值:828800
+     * 
+ */ + @SerializedName(value = "unit_price") + private Integer unitPrice; + } + + @Data + @NoArgsConstructor + public static class SceneInfo implements Serializable { + private static final long serialVersionUID = 4678263124015070957L; + + /** + *
+     * 字段名:商户端设备号
+     * 变量名:device_id
+     * 是否必填:否
+     * 类型:string(16)
+     * 描述:
+     *  终端设备号(门店号或收银设备ID)。
+     *  特殊规则:长度最小7个字节
+     *  示例值:POS1:1
+     * 
+ */ + @SerializedName(value = "device_id") + private String deviceId; + + /** + *
+     * 字段名:用户终端IP
+     * 变量名:payer_client_ip
+     * 是否必填:是
+     * 类型:string(45)
+     * 描述:
+     *  用户端实际ip
+     *  格式: ip(ipv4+ipv6)
+     *  示例值:14.17.22.32
+     * 
+ */ + @SerializedName(value = "payer_client_ip") + private String payerClientIp; + + /** + *
+     * 字段名:H5场景信息
+     * 变量名:h5_info
+     * 是否必填:否(H5支付必填)
+     * 类型:object
+     * 描述:
+     *  H5场景信息
+     * 
+ */ + @SerializedName(value = "h5_info") + private H5Info h5Info; + + /** + *
+     * 字段名:商户门店信息
+     * 变量名:store_info
+     * 是否必填:否(H5支付必填)
+     * 类型:object
+     * 描述:
+     *  商户门店信息
+     * 
+ */ + @SerializedName(value = "store_info") + private StoreInfo storeInfo; + + } + + @Data + @NoArgsConstructor + public static class H5Info implements Serializable { + private static final long serialVersionUID = -6865738707329486532L; + + /** + *
+     * 字段名:场景类型
+     * 变量名:type
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  场景类型,枚举值:
+     *  iOS:IOS移动应用;
+     *  Android:安卓移动应用;
+     *  Wap:WAP网站应用;
+     *  示例值:iOS
+     * 
+ */ + @SerializedName(value = "type") + private String type; + + /** + *
+     * 字段名:应用名称
+     * 变量名:app_name
+     * 是否必填:否
+     * 类型:string(64)
+     * 描述:
+     *  应用名称
+     *  示例值:王者荣耀
+     * 
+ */ + @SerializedName(value = "app_name") + private String appName; + + /** + *
+     * 字段名:网站URL
+     * 变量名:app_url
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  网站URL
+     *  示例值:https://pay.qq.com
+     * 
+ */ + @SerializedName(value = "app_url") + private String appUrl; + + /** + *
+     * 字段名:iOS平台BundleID
+     * 变量名:bundle_id
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  iOS平台BundleID
+     *  示例值:com.tencent.wzryiOS
+     * 
+ */ + @SerializedName(value = "bundle_id") + private String bundleId; + + /** + *
+     * 字段名:Android平台PackageName
+     * 变量名:package_name
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  Android平台PackageName
+     *  示例值:com.tencent.tmgp.sgame
+     * 
+ */ + @SerializedName(value = "package_name") + private String packageName; + + } + + @Data + @NoArgsConstructor + public static class StoreInfo implements Serializable { + private static final long serialVersionUID = -8002411737407580701L; + + /** + *
+     * 字段名:门店编号
+     * 变量名:id
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述:
+     *  商户侧门店编号
+     * 示例值:0001
+     * 
+ */ + @SerializedName(value = "id") + private String id; + + /** + *
+     * 字段名:门店名称
+     * 变量名:name
+     * 是否必填:是
+     * 类型:string(256)
+     * 描述:
+     *  商户侧门店名称
+     * 示例值:腾讯大厦分店
+     * 
+ */ + @SerializedName(value = "name") + private String name; + + /** + *
+     * 字段名:地区编码
+     * 变量名:area_code
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  地区编码,详细请见省市区编号对照表(https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter4_1.shtml)。
+     * 示例值:440305
+     * 
+ */ + @SerializedName(value = "area_code") + private String areaCode; + + /** + *
+     * 字段名:详细地址
+     * 变量名:address
+     * 是否必填:是
+     * 类型:string(512)
+     * 描述:
+     *  详细的商户门店地址
+     * 示例值:广东省深圳市南山区科技中一道10000号
+     * 
+ */ + @SerializedName(value = "address") + private String address; + + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsResult.java new file mode 100644 index 0000000000..9524627d79 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/PartnerTransactionsResult.java @@ -0,0 +1,587 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 普通支付 查询结果 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/e_transactions/chapter3_5.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class PartnerTransactionsResult implements Serializable { + + /** + *
+   * 字段名:服务商公众号ID
+   * 变量名:sp_appid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  服务商申请的公众号或移动应用appid。
+   *  示例值:wx8888888888888888
+   * 
+ */ + @SerializedName(value = "sp_appid") + private String spAppid; + + /** + *
+   * 字段名:服务商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  服务商户号,由微信支付生成并下发
+   *  示例值:1230000109
+   * 
+ */ + @SerializedName(value = "sp_mchid") + private String spMchid; + + /** + *
+   * 字段名:二级商户公众号ID
+   * 变量名:sub_appid
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  二级商户申请的公众号或移动应用appid。
+   *  示例值:wxd678efh567hg6999
+   * 
+ */ + @SerializedName(value = "sub_appid") + private String subAppid; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  二级商户的商户号,有微信支付生成并下发。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:+商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:商户系统内部订单号,只能是数字、大小写字母_-*且在同一个商户号下唯一,详见【商户订单号】。
+   * 特殊规则:最小字符长度为6
+   * 示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + + /** + *
+   * 字段名:微信支付订单号
+   * 变量名:transaction_id
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:微信支付系统生成的订单号。
+   * 示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+   * 字段名:交易类型
+   * 变量名:trade_type
+   * 是否必填:否
+   * 类型:string(16)
+   * 描述:交易类型,枚举值:
+   *  JSAPI:公众号支付
+   *  NATIVE:扫码支付
+   *  APP:APP支付
+   *  MICROPAY:付款码支付
+   *  MWEB:H5支付
+   *  FACEPAY:刷脸支付
+   *
+   * 示例值: MICROPAY
+   * 
+ */ + @SerializedName(value = "trade_type") + private String tradeType; + + /** + *
+   * 字段名:交易状态
+   * 变量名:trade_state
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:交易状态,枚举值:
+   *  SUCCESS:支付成功
+   *  REFUND:转入退款
+   *  NOTPAY:未支付
+   *  CLOSED:已关闭
+   *  REVOKED:已撤销(付款码支付)
+   *  USERPAYING:用户支付中(付款码支付)
+   *  PAYERROR:支付失败(其他原因,如银行返回失败)
+   *
+   * 示例值:SUCCESS
+   * 
+ */ + @SerializedName(value = "trade_state") + private String tradeState; + + /** + *
+   * 字段名:交易状态描述
+   * 变量名:trade_state_desc
+   * 是否必填:是
+   * 类型:string(256)
+   * 描述:交易状态描述
+   * 示例值:支付失败,请重新下单支付
+   * 
+ */ + @SerializedName(value = "trade_state_desc") + private String tradeStateDesc; + + /** + *
+   * 字段名:付款银行
+   * 变量名:bank_type
+   * 是否必填:否
+   * 类型:string(16)
+   * 描述:银行类型,采用字符串类型的银行标识。
+   * 示例值:CMC
+   * 
+ */ + @SerializedName(value = "bank_type") + private String bankType; + + /** + *
+   * 字段名:附加数据
+   * 变量名:attach
+   * 是否必填:否
+   * 类型:string(128)
+   * 描述:附加数据,在查询API和支付通知中原样返回,可作为自定义参数使用
+   * 示例值:自定义数据
+   * 
+ */ + @SerializedName(value = "attach") + private String attach; + + /** + *
+   * 字段名:支付完成时间
+   * 变量名:success_time
+   * 是否必填:否
+   * 类型:string(64)
+   * 描述:支付完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日 13点29分35秒。
+   * 示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName(value = "success_time") + private String successTime; + + /** + *
+   * 字段名:+支付者
+   * 变量名:combine_payer_info
+   * 是否必填:否
+   * 类型:object
+   * 描述:示例值:见请求示例
+   * 
+ */ + @SerializedName(value = "combine_payer_info") + private CombinePayerInfo combinePayerInfo; + + /** + *
+   * 字段名:订单金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:订单金额信息
+   * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + + /** + *
+   * 字段名:场景信息
+   * 变量名:scene_info
+   * 是否必填:否
+   * 类型:object
+   * 描述:支付场景信息描述
+   * 
+ */ + @SerializedName(value = "scene_info") + private SceneInfo sceneInfo; + + /** + *
+   * 字段名:优惠功能
+   * 变量名:promotion_detail
+   * 是否必填:否
+   * 类型:array
+   * 描述:优惠功能,享受优惠时返回该字段。
+   * 
+ */ + @SerializedName(value = "promotion_detail") + private List promotionDetails; + + @Data + @NoArgsConstructor + public static class SceneInfo implements Serializable { + /** + *
+     * 字段名:商户端设备号
+     * 变量名:device_id
+     * 是否必填:否
+     * 类型:string(16)
+     * 描述:
+     *  终端设备号(门店号或收银设备ID)。
+     *  特殊规则:长度最小7个字节
+     *  示例值:POS1:1
+     * 
+ */ + @SerializedName(value = "device_id") + private String deviceId; + + } + + @Data + @NoArgsConstructor + public static class CombinePayerInfo implements Serializable { + /** + *
+     * 字段名:用户标识
+     * 变量名:sp_openid
+     * 是否必填:是
+     * 类型:string(128)
+     * 描述:
+     *  用户在服务商appid下的唯一标识。
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName(value = "sp_openid") + private String spOpenid; + + + /** + *
+     * 字段名:二级商户用户标识
+     * 变量名:sub_openid
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:
+     *  用户在二级商户appid下的唯一标识。
+     *  示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+     * 
+ */ + @SerializedName(value = "sub_openid") + private String subOpenid; + + } + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + /** + *
+     * 字段名:总金额
+     * 变量名:total
+     * 是否必填:否
+     * 类型:int
+     * 描述:
+     *  订单总金额,单位为分
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "total") + private Integer total; + + + /** + *
+     * 字段名:用户支付金额
+     * 变量名:payer_total
+     * 是否必填:否
+     * 类型:int
+     * 描述:
+     *  用户支付金额,单位为分。
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "payer_total") + private Integer payerTotal; + + + /** + *
+     * 字段名:货币类型
+     * 变量名:currency
+     * 是否必填:否
+     * 类型:string(16)
+     * 描述:
+     *  CNY:人民币,境内商户号仅支持人民币。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + + + /** + *
+     * 字段名:用户支付币种
+     * 变量名:payer_currency
+     * 是否必填:否
+     * 类型:string(8)
+     * 描述:
+     *  用户支付币种
+     *  示例值: CNY
+     * 
+ */ + @SerializedName(value = "payer_currency") + private String payerCurrency; + } + + @Data + @NoArgsConstructor + public static class PromotionDetail implements Serializable { + + /** + *
+     * 字段名:券ID
+     * 变量名:coupon_id
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述: 券ID
+     * 示例值:109519
+     * 
+ */ + @SerializedName(value = "coupon_id") + private String couponId; + + /** + *
+     * 字段名:优惠名称
+     * 变量名:name
+     * 是否必填:否
+     * 类型:string(64)
+     * 描述: 优惠名称
+     * 示例值:单品惠-6
+     * 
+ */ + @SerializedName(value = "name") + private String name; + /** + *
+     * 字段名:优惠范围
+     * 变量名:scope
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述: 优惠名称
+     * 示例值:
+     *    GLOBAL:全场代金券
+     *    SINGLE:单品优惠
+     * 示例值:GLOBAL
+     * 
+ */ + @SerializedName(value = "scope") + private String scope; + + /** + *
+     * 字段名:优惠类型
+     * 变量名:type
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述:
+     *    CASH:充值
+     *    NOCASH:预充值
+     * 示例值:CASH
+     * 
+ */ + @SerializedName(value = "type") + private String type; + + /** + *
+     * 字段名:优惠券面额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:int
+     * 描述: 优惠券面额
+     * 示例值:100
+     * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+     * 字段名:活动ID
+     * 变量名:stock_id
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述:活动ID
+     * 示例值:931386
+     * 
+ */ + @SerializedName(value = "stock_id") + private String stockId; + + /** + *
+     * 字段名:微信出资
+     * 变量名:wechatpay_contribute
+     * 是否必填:否
+     * 类型:int
+     * 描述:微信出资,单位为分
+     * 示例值:0
+     * 
+ */ + @SerializedName(value = "wechatpay_contribute") + private Integer wechatpayContribute; + + /** + *
+     * 字段名:商户出资
+     * 变量名:merchant_contribute
+     * 是否必填:否
+     * 类型:int
+     * 描述:商户出资,单位为分
+     * 示例值:0
+     * 
+ */ + @SerializedName(value = "merchant_contribute") + private Integer merchantContribute; + + /** + *
+     * 字段名:其他出资
+     * 变量名:other_contribute
+     * 是否必填:否
+     * 类型:int
+     * 描述:其他出资,单位为分
+     * 示例值:0
+     * 
+ */ + @SerializedName(value = "other_contribute") + private Integer otherContribute; + + /** + *
+     * 字段名:优惠币种
+     * 变量名:currency
+     * 是否必填:否
+     * 类型:String(16)
+     * 描述:
+     *    CNY:人民币,境内商户号仅支持人民币。
+     * 示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + + /** + *
+     * 字段名:单品列表
+     * 变量名:goods_detail
+     * 是否必填:否
+     * 类型:array
+     * 描述:单品列表信息
+     * 
+ */ + @SerializedName(value = "goods_detail") + private List goodsDetails; + + + } + + @Data + @NoArgsConstructor + public static class GoodsDetail implements Serializable { + + /** + *
+     * 字段名:商品编码
+     * 变量名:goods_id
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:商品编码
+     * 示例值:M1006
+     * 
+ */ + @SerializedName(value = "goods_id") + private String goodsId; + + /** + *
+     * 字段名:商品数量
+     * 变量名:quantity
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  用户购买的数量
+     * 示例值:1
+     * 
+ */ + @SerializedName(value = "quantity") + private Integer quantity; + + /** + *
+     * 字段名:商品单价
+     * 变量名:unit_price
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  商品单价,单位为分
+     * 示例值:100
+     * 
+ */ + @SerializedName(value = "unit_price") + private Integer unitPrice; + + /** + *
+     * 字段名:商品优惠金额
+     * 变量名:discount_amount
+     * 是否必填:是
+     * 类型:int
+     * 描述:商品优惠金额
+     * 示例值:0
+     * 
+ */ + @SerializedName(value = "discount_amount") + private Integer discountAmount; + + /** + *
+     * 字段名:商品备注
+     * 变量名:goods_remark
+     * 是否必填:否
+     * 类型:string(128)
+     * 描述:商品备注信息
+     * 示例值:商品备注信息
+     * 
+ */ + @SerializedName(value = "goods_remark") + private String goodsRemark; + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingQueryRequest.java new file mode 100644 index 0000000000..c9e1aad2e8 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingQueryRequest.java @@ -0,0 +1,54 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +public class ProfitSharingQueryRequest implements Serializable { + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  分账出资的电商平台二级商户,填写微信支付分配的商户号。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:微信订单号
+   * 变量名:transaction_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付订单号。
+   * 示例值: 4208450740201411110007820472
+   * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+   * 字段名:商户分账单号
+   * 变量名:out_order_no
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  商户系统内部的分账单号,在商户系统内部唯一(单次分账、多次分账、完结分账应使用不同的商户分账单号),同一分账单号多次请求等同一次。
+   * 示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "out_order_no") + private String outOrderNo; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingRequest.java new file mode 100644 index 0000000000..aaec33bd07 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingRequest.java @@ -0,0 +1,192 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 请求分账 对象 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_1.shtml
+ * 
+ * @author: f00lish + * @date: 2020/09/12 + */ +@Data +@NoArgsConstructor +public class ProfitSharingRequest implements Serializable { + + private static final long serialVersionUID = -8662837652326828377L; + /** + *
+   * 字段名:公众账号ID
+   * 变量名:appid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信分配的公众账号ID。
+   *  示例值:wx8888888888888888
+   * 
+ */ + @SerializedName(value = "appid") + private String appid; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  分账出资的电商平台二级商户,填写微信支付分配的商户号。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:微信订单号
+   * 变量名:transaction_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付订单号。
+   *  示例值:4208450740201411110007820472
+   * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+   * 字段名:商户分账单号
+   * 变量名:out_order_no
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  商户系统内部的分账单号,在商户系统内部唯一(单次分账、多次分账、完结分账应使用不同的商户分账单号),同一分账单号多次请求等同一次。
+   *  示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "out_order_no") + private String outOrderNo; + + /** + *
+   * 字段名:分账接收方列表
+   * 变量名:receivers
+   * 是否必填:是
+   * 类型:array
+   * 描述:
+   *  分账接收方列表,支持设置出资商户作为分账接收方,单次分账最多可有5个分账接收方
+   * 
+ */ + @SerializedName(value = "receivers") + private Receiver[] receivers; + + /** + *
+   * 字段名:是否分账完成
+   * 变量名:finish
+   * 是否必填:是
+   * 类型:bool
+   * 描述:
+   *  是否完成分账
+   *  1、如果为true,该笔订单剩余未分账的金额会解冻回电商平台二级商户;
+   *  2、如果为false,该笔订单剩余未分账的金额不会解冻回电商平台二级商户,可以对该笔订单再次进行分账。
+   *  示例值:true
+   * 
+ */ + @SerializedName(value = "finish") + private Boolean finish; + + @Data + @NoArgsConstructor + public static class Receiver implements Serializable { + + private static final long serialVersionUID = 8995144356011793136L; + + /** + *
+     * 字段名:分账接收方类型
+     * 变量名:type
+     * 是否必填:否
+     * 类型:string(32)
+     * 描述:
+     *  分账接收方类型,枚举值:
+     *  MERCHANT_ID:商户
+     *  PERSONAL_OPENID:个人
+     *  示例值:MERCHANT_ID
+     * 
+ */ + @SerializedName(value = "type") + private String type; + + /** + *
+     * 字段名:分账接收方账号
+     * 变量名:receiver_account
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  分账接收方账号:
+     *  类型是MERCHANT_ID时,是商户ID
+     *  类型是PERSONAL_OPENID时,是个人openid,openid获取方法 https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/guide/chapter2_1.shtml#menu1
+     *  示例值:1900000109
+     * 
+ */ + @SerializedName(value = "receiver_account") + private String receiverAccount; + + /** + *
+     * 字段名:分账金额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  分账金额,单位为分,只能为整数,不能超过原订单支付金额及最大分账比例金额。
+     *  示例值:190
+     * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+     * 字段名:分账描述
+     * 变量名:description
+     * 是否必填:是
+     * 类型:string(180)
+     * 描述:
+     *  分账的原因描述,分账账单中需要体现。
+     *  示例值:分给商户1900000109
+     * 
+ */ + @SerializedName(value = "description") + private String description; + + /** + *
+     * 字段名:分账个人姓名
+     * 变量名:receiver_name
+     * 是否必填:否
+     * 类型:string(10240)
+     * 描述:
+     *  可选项,在接收方类型为个人的时可选填,若有值,会检查与 receiver_name 是否实名匹配,不匹配会拒绝分账请求
+     *  1、分账接收方类型是PERSONAL_OPENID时,是个人姓名的密文(选传,传则校验) 此字段的加密方法详见:敏感信息加密说明
+     *  2、使用微信支付平台证书中的公钥
+     *  3、使用RSAES-OAEP算法进行加密
+     *  4、将请求中HTTP头部的Wechatpay-Serial设置为证书序列号
+     *  示例值:hu89ohu89ohu89o
+     * 
+ */ + @SerializedName(value = "receiver_name") + private String receiverName; + + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingResult.java new file mode 100644 index 0000000000..37ff86c25a --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ProfitSharingResult.java @@ -0,0 +1,281 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 请求分账 结果响应 + * @author: f00lish + * @date: 2020/09/12 + */ +@Data +@NoArgsConstructor +public class ProfitSharingResult implements Serializable { + + private static final long serialVersionUID = 9026456165403642050L; + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  分账出资的电商平台二级商户,填写微信支付分配的商户号。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:微信订单号
+   * 变量名:transaction_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付订单号。
+   *  示例值:4208450740201411110007820472
+   * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+   * 字段名:商户分账单号
+   * 变量名:out_order_no
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  商户系统内部的分账单号,在商户系统内部唯一(单次分账、多次分账、完结分账应使用不同的商户分账单号),同一分账单号多次请求等同一次。
+   *  示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "out_order_no") + private String outOrderNo; + + /** + *
+   * 字段名:微信分账单号
+   * 变量名:order_id
+   * 是否必填:是
+   * 类型:string (64)
+   * 描述:
+   *  微信分账单号,微信系统返回的唯一标识。
+   *  示例值:6754760740201411110007865434
+   * 
+ */ + @SerializedName(value = "order_id") + private String orderId; + + /** + *
+   * 字段名:分账单状态
+   * 变量名:status
+   * 是否必填:是
+   * 类型:string (64)
+   * 描述:
+   *  分账单状态,枚举值:
+   *    ACCEPTED:受理成功
+   *    PROCESSING:处理中
+   *    FINISHED:分账成功
+   *    CLOSED:处理失败,已关单
+   *  示例值:FINISHED
+   * 
+ */ + @SerializedName(value = "status") + private String status; + + /** + *
+   * 字段名:分账接收方列表
+   * 变量名:receivers
+   * 是否必填:否
+   * 类型:array
+   * 描述:
+   *  分账接收方列表。当查询分账完结的执行结果时,不返回该字段
+   * 
+ */ + @SerializedName(value = "receivers") + private List receivers; + /** + *
+   * 字段名:关单原因
+   * 变量名:close_reason
+   * 是否必填:否
+   * 类型:string (32)
+   * 描述:
+   *  关单原因描述,当分账单状态status为CLOSED(处理失败,已关单)时,返回该字段。
+   * 枚举值:
+   *    NO_AUTH:分账授权已解除
+   * 示例值:NO_AUTH
+   * 
+ */ + @SerializedName(value = "close_reason") + private String closeReason; + + /** + *
+   * 字段名:分账完结金额
+   * 变量名:finish_amount
+   * 是否必填:否
+   * 类型:int
+   * 描述:
+   *  分账完结的分账金额,单位为分, 仅当查询分账完结的执行结果时,存在本字段。
+   * 示例值:100
+   * 
+ */ + @SerializedName(value = "finish_amount") + private Integer finishAmount; + + /** + *
+   * 字段名:分账完结描述
+   * 变量名:finish_description
+   * 是否必填:否
+   * 类型:string (80)
+   * 描述:
+   *  分账完结的原因描述,仅当查询分账完结的执行结果时,存在本字段。
+   * 示例值:分账完结
+   * 
+ */ + @SerializedName(value = "finish_description") + private String finishDescription; + + @Data + @NoArgsConstructor + public static class Receiver implements Serializable { + + /** + *
+     * 字段名:分账接收商户号
+     * 变量名:receiver_mchid
+     * 是否必填:是
+     * 类型:string (32)
+     * 描述:
+     *  填写微信支付分配的商户号,仅支持通过添加分账接收方接口添加的接收方;电商平台商户已默认添加到分账接收方,无需重复添加。
+     * 示例值:1900000109
+     * 
+ */ + @SerializedName(value = "receiver_mchid") + private String receiverMchid; + + /** + *
+     * 字段名:分账金额
+     * 变量名:amount
+     * 是否必填:否
+     * 类型:int
+     * 描述:
+     *  分账金额,单位为分,只能为整数,不能超过原订单支付金额及最大分账比例金额。
+     * 示例值: 4208450740201411110007820472
+     * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+     * 字段名:分账描述
+     * 变量名:description
+     * 是否必填:是
+     * 类型:string (80)
+     * 描述:
+     *  分账的原因描述,分账账单中需要体现。
+     * 示例值:分帐1900000110
+     * 
+ */ + @SerializedName(value = "description") + private String description; + + /** + *
+     * 字段名:分账结果
+     * 变量名:result
+     * 是否必填:是
+     * 类型:string (32)
+     * 描述:
+     *  分账结果,枚举值:
+     *    PENDING:待分账
+     *    SUCCESS:分账成功
+     *    ADJUST:分账失败待调账
+     *    RETURNED:已转回分账方
+     *    CLOSED:已关闭
+     * 示例值:SUCCESS
+     * 
+ */ + @SerializedName(value = "result") + private String result; + + /** + *
+     * 字段名:完成时间
+     * 变量名:finish_time
+     * 是否必填:是
+     * 类型:string (64)
+     * 描述:
+     *  分账完成时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE,YYYY-MM-DD表示年月日,
+     *  T出现在字符串中,表示time元素的开头,HH:mm:ss.sss表示时分秒毫秒,TIMEZONE表示时区
+     *  (+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日 13点29分35秒。
+     * 示例值: 2015-05-20T13:29:35.120+08:00
+     * 
+ */ + @SerializedName(value = "finish_time") + private String finishTime; + + /** + *
+     * 字段名:分账失败原因
+     * 变量名:fail_reason
+     * 是否必填:否
+     * 类型:string (32)
+     * 描述:
+     *  分账失败原因,当分账结果result为RETURNED(已转回分账方)或CLOSED(已关闭)时,返回该字段
+     * 枚举值:
+     *    ACCOUNT_ABNORMAL:分账接收账户异常
+     *    NO_RELATION:分账关系已解除
+     *    RECEIVER_HIGH_RISK:高风险接收方
+     * 示例值:NO_RELATION
+     * 
+ */ + @SerializedName(value = "fail_reason") + private String failReason; + + /** + *
+     * 字段名:分账接收方类型
+     * 变量名:type
+     * 是否必填:是
+     * 类型:string (32)
+     * 描述:
+     *  分账接收方类型,枚举值:
+     *    MERCHANT_ID:商户
+     *    PERSONAL_OPENID:个人
+     * 示例值:MERCHANT_ID
+     * 
+ */ + @SerializedName(value = "type") + private String type; + + /** + *
+     * 字段名:分账接收方类型
+     * 变量名:receiver_account
+     * 是否必填:是
+     * 类型:string (64)
+     * 描述:
+     *  分账接收方账号:
+     * 类型是MERCHANT_ID时,是商户ID
+     * 类型是PERSONAL_OPENID时,是个人openid
+     * 示例值:1900000109
+     * 
+ */ + @SerializedName(value = "receiver_account") + private String receiverAccount; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundNotifyResult.java new file mode 100644 index 0000000000..a2452a1bad --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundNotifyResult.java @@ -0,0 +1,233 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 退款结果 查询结果 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_3.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class RefundNotifyResult implements Serializable { + + /** + * 源数据 + */ + private NotifyResponse rawData; + + /** + *
+   * 字段名:电商平台商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付分配给电商平台的商户号
+   * 示例值:1900000100
+   * 
+ */ + @SerializedName(value = "sp_mchid") + private String spMchid; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  分账出资的电商平台二级商户,填写微信支付分配的商户号。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  返回的商户订单号
+   * 示例值: 1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + + /** + *
+   * 字段名:微信订单号
+   * 变量名:transaction_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付订单号
+   * 示例值: 1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+   * 字段名:商户退款单号
+   * 变量名:out_refund_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户退款单号
+   * 示例值: 1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "out_refund_no") + private String outRefundNo; + + /** + *
+   * 字段名:微信退款单号
+   * 变量名:refund_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信退款单号
+   * 示例值: 1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "refund_id") + private String refundId; + + /** + *
+   * 字段名:退款状态
+   * 变量名:refund_status
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  退款状态,枚举值:
+   *    SUCCESS:退款成功
+   *    CLOSE:退款关闭
+   *    ABNORMAL:退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往【服务商平台—>交易中心】,手动处理此笔退款
+   * 示例值:SUCCESS
+   * 
+ */ + @SerializedName(value = "refund_status") + private String refundStatus; + + /** + *
+   * 字段名:退款成功时间
+   * 变量名:success_time
+   * 是否必填:否
+   * 类型:string(64)
+   * 描述:
+   *  1、退款成功时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,
+   *  表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+   * 2、当退款状态为退款成功时返回此参数。
+   * 示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName(value = "success_time") + private String successTime; + + /** + *
+   * 字段名:退款入账账户
+   * 变量名:user_received_account
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  取当前退款单的退款入账方。
+   *    退回银行卡:{银行名称}{卡类型}{卡尾号}
+   *    退回支付用户零钱: 支付用户零钱
+   *    退还商户: 商户基本账户、商户结算银行账户
+   *    退回支付用户零钱通:支付用户零钱通
+   * 示例值:招商银行信用卡0403
+   * 
+ */ + @SerializedName(value = "user_received_account") + private String userReceivedAccount; + + /** + *
+   * 字段名:金额信息
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  金额信息
+   * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + /** + *
+     * 字段名:订单金额
+     * 变量名:total
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  订单总金额,单位为分,只能为整数,详见支付金额
+     * 示例值:999
+     * 
+ */ + @SerializedName(value = "total") + private Integer total; + + /** + *
+     * 字段名:退款金额
+     * 变量名:refund
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  退款金额,币种的最小单位,只能为整数,不能超过原订单支付金额,如果有使用券,后台会按比例退。
+     * 示例值:999
+     * 
+ */ + @SerializedName(value = "refund") + private String refund; + + /** + *
+     * 字段名:用户支付金额
+     * 变量名:payer_total
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  用户支付金额,单位为分。
+     *  示例值:100
+     * 
+ */ + @SerializedName(value = "payer_total") + private Integer payerTotal; + + /** + *
+     * 字段名:用户退款金额
+     * 变量名:payer_refund
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  退款给用户的金额,不包含所有优惠券金额
+     * 示例值:999
+     * 
+ */ + @SerializedName(value = "payer_refund") + private String payerRefund; + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundQueryResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundQueryResult.java new file mode 100644 index 0000000000..bbb30ea897 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundQueryResult.java @@ -0,0 +1,325 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.List; + +/** + * 查询退款结果 + * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_2.shtml + */ +@Data +@NoArgsConstructor +public class RefundQueryResult implements Serializable { + + /** + *
+   * 字段名:微信退款单号
+   * 变量名:refund_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信退款单号
+   * 示例值: 1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "refund_id") + private String refundId; + + /** + *
+   * 字段名:商户退款单号
+   * 变量名:out_refund_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户退款单号
+   * 示例值: 1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "out_refund_no") + private String outRefundNo; + + /** + *
+   * 字段名:微信订单号
+   * 变量名:transaction_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付订单号
+   * 示例值: 1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  返回的商户订单号
+   * 示例值: 1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + + /** + *
+   * 字段名:退款渠道
+   * 变量名:channel
+   * 是否必填:否
+   * 类型:string(16)
+   * 描述:
+   *  ORIGINAL:原路退款
+   *  BALANCE:退回到余额
+   *  OTHER_BALANCE:原账户异常退到其他余额账户
+   *  OTHER_BANKCARD:原银行卡异常退到其他银行卡
+   * 示例值: ORIGINAL
+   * 
+ */ + @SerializedName(value = "channel") + private String channel; + + /** + *
+   * 字段名:退款入账账户
+   * 变量名:user_received_account
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  取当前退款单的退款入账方。
+   *    退回银行卡:{银行名称}{卡类型}{卡尾号}
+   *    退回支付用户零钱: 支付用户零钱
+   *    退还商户: 商户基本账户、商户结算银行账户
+   *    退回支付用户零钱通:支付用户零钱通
+   * 示例值:招商银行信用卡0403
+   * 
+ */ + @SerializedName(value = "user_received_account") + private String userReceivedAccount; + + /** + *
+   * 字段名:退款成功时间
+   * 变量名:success_time
+   * 是否必填:否
+   * 类型:string(64)
+   * 描述:
+   *  1、退款成功时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,
+   *  表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+   * 2、当退款状态为退款成功时返回此参数。
+   * 示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName(value = "success_time") + private String successTime; + + /** + *
+   * 字段名:退款创建时间
+   * 变量名:create_time
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  1、退款受理时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,
+   *  表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+   * 2、当退款状态为退款成功时返回此字段。
+   * 示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName(value = "create_time") + private String createTime; + + /** + *
+   * 字段名:退款状态
+   * 变量名:status
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  退款状态,枚举值:
+   *    SUCCESS:退款成功
+   *    REFUNDCLOSE:退款关闭
+   *    PROCESSING:退款处理中
+   *    ABNORMAL:退款异常,退款到银行发现用户的卡作废或者冻结了,导致原路退款银行卡失败,可前往【服务商平台—>交易中心】,手动处理此笔退款
+   * 示例值:SUCCESS
+   * 
+ */ + @SerializedName(value = "status") + private String status; + + /** + *
+   * 字段名:金额信息
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  金额信息
+   * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + + /** + *
+   * 字段名:营销详情
+   * 变量名:promotion_detail
+   * 是否必填:否
+   * 类型:array
+   * 描述:
+   *  优惠退款信息
+   * 
+ */ + public List promotionDetails; + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + + /** + *
+     * 字段名:退款金额
+     * 变量名:refund
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  退款金额,币种的最小单位,只能为整数,不能超过原订单支付金额。
+     * 示例值:888
+     * 
+ */ + @SerializedName(value = "refund") + private String refund; + + /** + *
+     * 字段名:用户退款金额
+     * 变量名:payer_refund
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  退款给用户的金额,不包含所有优惠券金额。
+     * 示例值:888
+     * 
+ */ + @SerializedName(value = "payer_refund") + private String payerRefund; + + /** + *
+     * 字段名:优惠退款金额
+     * 变量名:discount_refund
+     * 是否必填:否
+     * 类型:int
+     * 描述:
+     *  优惠券的退款金额,原支付单的优惠按比例退款。
+     * 示例值:888
+     * 
+ */ + @SerializedName(value = "discount_refund") + private Integer discountRefund; + + + /** + *
+     * 字段名:退款币种
+     * 变量名:currency
+     * 是否必填:否
+     * 类型:string(18)
+     * 描述:
+     *  符合ISO 4217标准的三位字母代码,目前只支持人民币:CNY 。
+     * 示例值: CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + + } + + @Data + @NoArgsConstructor + public static class PromotionDetail implements Serializable { + + /** + *
+     * 字段名:券ID
+     * 变量名:promotion_id
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:券或者立减优惠id 。
+     * 示例值:109519
+     * 
+ */ + @SerializedName(value = "promotion_id") + private String promotionId; + + /** + *
+     * 字段名:优惠范围
+     * 变量名:scope
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述: 优惠范围
+     * 枚举值:
+     *    GLOBAL:全场代金券
+     *    SINGLE:单品优惠
+     * 示例值:GLOBAL
+     * 
+ */ + @SerializedName(value = "scope") + private String scope; + + /** + *
+     * 字段名:优惠类型
+     * 变量名:type
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *   枚举值:
+     *    COUPON:充值型代金券,商户需要预先充值营销经费
+     *    DISCOUNT:免充值型优惠券,商户不需要预先充值营销经费
+     * 示例值:DISCOUNT
+     * 
+ */ + @SerializedName(value = "type") + private String type; + + /** + *
+     * 字段名:优惠券面额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:int
+     * 描述: 用户享受优惠的金额(优惠券面额=微信出资金额+商家出资金额+其他出资方金额 )。
+     * 示例值:5
+     * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+     * 字段名:优惠退款金额
+     * 变量名:refund_amount
+     * 是否必填:是
+     * 类型:int
+     * 描述: 代金券退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见《代金券或立减优惠》。
+     * 示例值:100
+     * 
+ */ + @SerializedName(value = "refund_amount") + private Integer refundAmount; + + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundsRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundsRequest.java new file mode 100644 index 0000000000..68dfd3e004 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundsRequest.java @@ -0,0 +1,206 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +/** + * @author: f00lish + * @date: 2020/09/17 + */ + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +import java.io.Serializable; + +/** + * 退款申请 + * *
+ *  *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_1.shtml
+ *  * 
+ * @author: f00lish + * @date: 2020/09/14 + */ +@Data +@Builder +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class RefundsRequest implements Serializable { + private static final long serialVersionUID = -3186851559004865784L; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付分配二级商户的商户号。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:电商平台APPID
+   * 变量名:sp_appid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台在微信公众平台申请服务号对应的APPID,申请商户功能的时候微信支付会配置绑定关系。
+   *  示例值:wx8888888888888888
+   * 
+ */ + @SerializedName(value = "sp_appid") + private String spAppid; + + /** + *
+   * 字段名:二级商户APPID
+   * 变量名:sub_appid
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  二级商户在微信申请公众号成功后分配的帐号ID,需要电商平台侧配置绑定关系才能传参。
+   *  示例值:wxd678efh567hg6999
+   * 
+ */ + @SerializedName(value = "sub_appid") + private String subAppid; + + /** + *
+   * 字段名:微信订单号
+   * 变量名:transaction_id
+   * 是否必填:与out_order_no二选一
+   * 类型:string(32)
+   * 描述:
+   *  微信支付订单号。
+   *  示例值:4208450740201411110007820472
+   * 
+ */ + @SerializedName(value = "transaction_id") + private String transactionId; + + /** + *
+   * 字段名:商户订单号
+   * 变量名:out_trade_no
+   * 是否必填:与transaction_id二选一
+   * 类型:string(64)
+   * 描述:
+   *   原支付交易对应的商户订单号。
+   *   示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "out_trade_no") + private String outTradeNo; + + /** + *
+   * 字段名:商户退款单号
+   * 变量名:out_refund_no
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *   商户系统内部的退款单号,商户系统内部唯一,只能是数字、大小写字母_-|*@,同一退款单号多次请求只退一笔。
+   *   示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "out_refund_no") + private String outRefundNo; + + /** + *
+   * 字段名:退款原因
+   * 变量名:reason
+   * 是否必填:是
+   * 类型:string(80)
+   * 描述:
+   *   若商户传入,会在下发给用户的退款消息中体现退款原因。
+   *   注意:若订单退款金额≤1元,且属于部分退款,则不会在退款消息中体现退款原因
+   *   示例值:商品已售完
+   * 
+ */ + @SerializedName(value = "reason") + private String reason; + + /** + *
+   * 字段名:订单金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  订单金额信息
+   * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + + /** + *
+   * 字段名:退款结果回调url
+   * 变量名:notify_url
+   * 是否必填:是
+   * 类型:string(256)
+   * 描述:
+   *   异步接收微信支付退款结果通知的回调地址,通知url必须为外网可访问的url,不能携带参数。 如果参数中传了notify_url,则商户平台上配置的回调地址将不会生效,优先回调当前传的地址。
+   *   示例值:https://weixin.qq.com
+   * 
+ */ + @SerializedName(value = "notify_url") + private String notifyUrl; + + @Data + @Builder + @NoArgsConstructor(access = AccessLevel.PRIVATE) + @AllArgsConstructor(access = AccessLevel.PRIVATE) + public static class Amount implements Serializable { + + private static final long serialVersionUID = 7383027142329410399L; + + /** + *
+     * 字段名:退款金额
+     * 变量名:refund
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  退款金额,币种的最小单位,只能为整数,不能超过原订单支付金额。
+     *  示例值:888
+     * 
+ */ + @SerializedName(value = "refund") + private Integer refund; + + /** + *
+     * 字段名:原订单金额
+     * 变量名:total
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  订单总金额,单位为分。
+     *  示例值:888
+     * 
+ */ + @SerializedName(value = "total") + private Integer total; + + /** + *
+     * 字段名:币类型
+     * 变量名:currency
+     * 是否必填:否
+     * 类型:string(18)
+     * 描述:
+     *  符合ISO 4217标准的三位字母代码,目前只支持人民币:CNY。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundsResult.java new file mode 100644 index 0000000000..52eef53bfd --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/RefundsResult.java @@ -0,0 +1,242 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +/** + * @author: f00lish + * @date: 2020/09/17 + */ + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.Date; + +/** + * 退款结果 + * *
+ *  *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_1.shtml
+ *  * 
+ * @author: f00lish + * @date: 2020/09/14 + */ +@Data +@NoArgsConstructor +public class RefundsResult implements Serializable { + private static final long serialVersionUID = -3186851559004865784L; + + /** + *
+   * 字段名:微信退款单号
+   * 变量名:refund_id
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  微信支付退款订单号。
+   *  示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "refund_id") + private String refundId; + + /** + *
+   * 字段名:商户退款单号
+   * 变量名:out_refund_no
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *   商户系统内部的退款单号,商户系统内部唯一,同一退款单号多次请求只退一笔。
+   * 示例值:1217752501201407033233368018
+   * 
+ */ + @SerializedName(value = "out_refund_no") + private String outRefundNo; + + /** + *
+   * 字段名:退款创建时间
+   * 变量名:create_time
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *   退款受理时间,遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss表示时分秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+   *   示例值:2018-06-08T10:34:56+08:00
+   * 
+ */ + @SerializedName(value = "create_time") + private Date createTime; + + /** + *
+   * 字段名:订单金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:object
+   * 描述:
+   *  订单金额信息
+   * 
+ */ + @SerializedName(value = "amount") + private Amount amount; + + /** + *
+   * 字段名:优惠退款详情
+   * 变量名:promotion_detail
+   * 是否必填:否
+   * 类型:array
+   * 描述:
+   *   优惠退款功能信息
+   * 
+ */ + @SerializedName(value = "promotion_detail") + private PromotionDetail[] promotionDetail; + + @Data + @NoArgsConstructor + public static class Amount implements Serializable { + + private static final long serialVersionUID = 7383027142329410399L; + + /** + *
+     * 字段名:退款金额
+     * 变量名:refund
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  退款金额,币种的最小单位,只能为整数,不能超过原订单支付金额。
+     *  示例值:888
+     * 
+ */ + @SerializedName(value = "refund") + private Integer refund; + + /** + *
+     * 字段名:用户退款金额
+     * 变量名:payer_refund
+     * 是否必填:是
+     * 类型:int64
+     * 描述:
+     *  退款给用户的金额,不包含所有优惠券金额。
+     *  示例值:888
+     * 
+ */ + @SerializedName(value = "payer_refund") + private Integer payerRefund; + + /** + *
+     * 字段名:优惠退款金额
+     * 变量名:discount_refund
+     * 是否必填:否
+     * 类型:int64
+     * 描述:
+     *  优惠券的退款金额,原支付单的优惠按比例退款。
+     *  示例值:888
+     * 
+ */ + @SerializedName(value = "discount_refund") + private Integer discountRefund; + + /** + *
+     * 字段名:币类型
+     * 变量名:currency
+     * 是否必填:否
+     * 类型:string(18)
+     * 描述:
+     *  符合ISO 4217标准的三位字母代码,目前只支持人民币:CNY。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "currency") + private String currency; + + } + + @Data + @NoArgsConstructor + public static class PromotionDetail implements Serializable { + + private static final long serialVersionUID = 7383027142329410399L; + + /** + *
+     * 字段名:券ID
+     * 变量名:promotion_id
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  券或者立减优惠id。
+     *  示例值:109519
+     * 
+ */ + @SerializedName(value = "promotion_id") + private String promotionId; + + /** + *
+     * 字段名:优惠范围
+     * 变量名:scope
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  枚举值:
+     *  GLOBAL:全场代金券
+     *  SINGLE:单品优惠
+     *  示例值:SINGLE
+     * 
+ */ + @SerializedName(value = "scope") + private String scope; + + /** + *
+     * 字段名:优惠类型
+     * 变量名:type
+     * 是否必填:是
+     * 类型:string(32)
+     * 描述:
+     *  枚举值:
+     *  COUPON:充值型代金券,商户需要预先充值营销经费
+     *  DISCOUNT:免充值型优惠券,商户不需要预先充值营销经费
+     *  示例值:DISCOUNT
+     * 
+ */ + @SerializedName(value = "type") + private String type; + + /** + *
+     * 字段名:优惠券面额
+     * 变量名:amount
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  用户享受优惠的金额(优惠券面额=微信出资金额+商家出资金额+其他出资方金额 )。
+     *  示例值:5
+     * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+     * 字段名:优惠退款金额
+     * 变量名:refund_amount
+     * 是否必填:是
+     * 类型:int
+     * 描述:
+     *  代金券退款金额<=退款金额,退款金额-代金券或立减优惠退款金额为现金,说明详见《代金券或立减优惠》https://pay.weixin.qq.com/wiki/doc/api/tools/sp_coupon.php?chapter=12_1 。
+     *  示例值:CNY
+     * 
+ */ + @SerializedName(value = "refund_amount") + private Integer refundAmount; + + } + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersRequest.java new file mode 100644 index 0000000000..957b1a8d63 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersRequest.java @@ -0,0 +1,120 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +import java.io.Serializable; + +/** + * 请求分账回退 + * *
+ *  *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_3.shtml
+ *  * 
+ * @author: f00lish + * @date: 2020/09/14 + */ +@Data +@Builder +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ReturnOrdersRequest implements Serializable { + private static final long serialVersionUID = -3674823388136221959L; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  分账出资的电商平台二级商户,填写微信支付分配的商户号。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:微信分账单号
+   * 变量名:order_id
+   * 是否必填:与out_order_no二选一
+   * 类型:string(64)
+   * 描述:
+   *  微信分账单号,微信系统返回的唯一标识。微信分账单号和商户分账单号二选一填写。
+   *  示例值:3008450740201411110007820472
+   * 
+ */ + @SerializedName(value = "order_id") + private String orderId; + + /** + *
+   * 字段名:商户分账单号
+   * 变量名:out_order_no
+   * 是否必填:与order_id二选一
+   * 类型:string(64)
+   * 描述:
+   *   商户系统内部的分账单号,在商户系统内部唯一(单次分账、多次分账、完结分账应使用不同的商户分账单号),同一分账单号多次请求等同一次。
+   *  示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "out_order_no") + private String outOrderNo; + + /** + *
+   * 字段名:商户回退单号
+   * 变量名:out_return_no
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *   此回退单号是商户在自己后台生成的一个新的回退单号,在商户后台唯一。
+   *  示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "out_return_no") + private String outReturnNo; + + /** + *
+   * 字段名:回退商户号
+   * 变量名:return_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  只能对原分账请求中成功分给商户接收方进行回退。
+   *  示例值:86693852
+   * 
+ */ + @SerializedName(value = "return_mchid") + private String returnMchid; + + /** + *
+   * 字段名:回退金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  需要从分账接收方回退的金额,单位为分,只能为整数,不能超过原始分账单分出给该接收方的金额。
+   *  示例值:10
+   * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+   * 字段名:回退描述
+   * 变量名:description
+   * 是否必填:是
+   * 类型:string(80)
+   * 描述:
+   *  分账回退的原因描述
+   *  示例值:分账回退
+   * 
+ */ + @SerializedName(value = "description") + private String description; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java new file mode 100644 index 0000000000..f2110cc5d8 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/ReturnOrdersResult.java @@ -0,0 +1,168 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +import java.io.Serializable; +import java.util.Date; + + +/** + * @author: f00lish + * @date: 2020/09/14 + */ +@Data +@Builder +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class ReturnOrdersResult implements Serializable { + private static final long serialVersionUID = 2296020044225854203L; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  分账出资的电商平台二级商户,填写微信支付分配的商户号。
+   *  示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:微信分账单号
+   * 变量名:order_id
+   * 是否必填:与out_order_no二选一
+   * 类型:string(64)
+   * 描述:
+   *  微信分账单号,微信系统返回的唯一标识。微信分账单号和商户分账单号二选一填写。
+   *  示例值:3008450740201411110007820472
+   * 
+ */ + @SerializedName(value = "order_id") + private String orderId; + + /** + *
+   * 字段名:商户分账单号
+   * 变量名:out_order_no
+   * 是否必填:与order_id二选一
+   * 类型:string(64)
+   * 描述:
+   *   商户系统内部的分账单号,在商户系统内部唯一(单次分账、多次分账、完结分账应使用不同的商户分账单号),同一分账单号多次请求等同一次。
+   *  示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "out_order_no") + private String outOrderNo; + + /** + *
+   * 字段名:商户回退单号
+   * 变量名:out_return_no
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *   此回退单号是商户在自己后台生成的一个新的回退单号,在商户后台唯一。
+   *  示例值:P20150806125346
+   * 
+ */ + @SerializedName(value = "out_return_no") + private String outReturnNo; + + /** + *
+   * 字段名:回退商户号
+   * 变量名:return_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  只能对原分账请求中成功分给商户接收方进行回退。
+   *  示例值:86693852
+   * 
+ */ + @SerializedName(value = "return_mchid") + private String returnMchid; + + /** + *
+   * 字段名:回退金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:int
+   * 描述:
+   *  需要从分账接收方回退的金额,单位为分,只能为整数,不能超过原始分账单分出给该接收方的金额。
+   *  示例值:10
+   * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+   * 字段名:微信回退单号
+   * 变量名:return_no
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  微信分账回退单号,微信系统返回的唯一标识。
+   *  示例值:3008450740201411110007820472
+   * 
+ */ + @SerializedName(value = "return_no") + private String returnNo; + + /** + *
+   * 字段名:回退结果
+   * 变量名:result
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  如果请求返回为处理中,则商户可以通过调用回退结果查询接口获取请求的最终处理结果,枚举值:
+   *  PROCESSING:处理中
+   *  SUCCESS:已成功
+   *  FAIL:已失败
+   *  注意:如果返回为处理中,请勿变更商户回退单号,使用相同的参数再次发起分账回退,否则会出现资金风险 在处理中状态的回退单如果5天没有成功,会因为超时被设置为已失败
+   *  示例值:SUCCESS
+   * 
+ */ + @SerializedName(value = "result") + private String result; + + /** + *
+   * 字段名:失败原因
+   * 变量名:fail_reason
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  回退失败的原因,此字段仅回退结果为FAIL时存在,枚举值:
+   * ACCOUNT_ABNORMAL:分账接收方账户异常
+   * TIME_OUT_CLOSED::超时关单
+   *  示例值:TIME_OUT_CLOSED
+   * 
+ */ + @SerializedName(value = "fail_reason") + private String failReason; + + /** + *
+   * 字段名:完成时间
+   * 变量名:finish_time
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  分账回退完成时间,遵循rfc3339标准格式
+   *  格式为YYYY-MM-DDTHH:mm:ss.sss+TIMEZONE,YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss.sss表示时分秒毫秒,TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。例如:2015-05-20T13:29:35.120+08:00表示,北京时间2015年5月20日 13点29分35秒。
+   *  示例值:2015-05-20T13:29:35.120+08:00
+   * 
+ */ + @SerializedName(value = "finish_time") + private Date finishTime; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SettlementRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SettlementRequest.java new file mode 100644 index 0000000000..81e4bb5cc6 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SettlementRequest.java @@ -0,0 +1,114 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.github.binarywang.wxpay.v3.SpecEncrypt; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + *
+ * 普通服务商(支付机构、银行不可用),可使用本接口修改其进件、已签约的特约商户-结算账户信息。
+ * 文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_4.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class SettlementRequest implements Serializable { + + /** + *
+   * 字段名:账户类型
+   * 变量名:account_type
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  根据特约商户号的主体类型,可选择的账户类型如下:
+   * 1、小微主体:经营者个人银行卡
+   * 2、个体工商户主体:经营者个人银行卡/ 对公银行账户
+   * 3、企业主体:对公银行账户
+   * 4、党政、机关及事业单位主体:对公银行账户
+   * 5、其他组织主体:对公银行账户
+   * 枚举值:
+   *    ACCOUNT_TYPE_BUSINESS:对公银行账户
+   *    ACCOUNT_TYPE_PRIVATE:经营者个人银行卡
+   * 示例值:ACCOUNT_TYPE_BUSINESS
+   * 
+ */ + @SerializedName(value = "account_type") + private String accountType; + + /** + *
+   * 字段名:开户银行
+   * 变量名:account_bank
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  请填写开户银行名称,详细参见《开户银行对照表》https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter4_1.shtml。
+   * 示例值:工商银行
+   * 
+ */ + @SerializedName(value = "account_bank") + private String accountBank; + + /** + *
+   * 字段名:开户银行省市编码
+   * 变量名:bank_address_code
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  需至少精确到市,详细参见《省市区编号对照表》。
+   * 示例值:110000
+   * 
+ */ + @SerializedName(value = "bank_address_code") + private String bankAddressCode; + + /** + *
+   * 字段名:开户银行全称(含支行)
+   * 变量名:bank_name
+   * 是否必填:否
+   * 类型:string(128)
+   * 描述:
+   *  若开户银行为“其他银行”,则需二选一填写“开户银行全称(含支行)”或“开户银行联行号”。
+   * 填写银行全称,如"深圳农村商业银行XXX支行" ,详细参见开户银行全称(含支行)对照表。
+   * 示例值:施秉县农村信用合作联社城关信用社
+   * 
+ */ + @SerializedName(value = "bank_name") + private String bankName; + + /** + *
+   * 字段名:开户银行联行号
+   * 变量名:bank_branch_id
+   * 是否必填:否
+   * 类型:string(128)
+   * 描述:
+   *  若开户银行为“其他银行”,则需二选一填写“开户银行全称(含支行)”或“开户银行联行号”。
+   * 填写银行联行号,详细参见《开户银行全称(含支行)对照表》。
+   * 示例值:402713354941
+   * 
+ */ + @SerializedName(value = "bank_branch_id") + private String bankBranchId; + + /** + *
+   * 字段名:银行账号
+   * 变量名:account_number
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  1、数字,长度遵循系统支持的对公/对私卡号长度要求
+   * 2、该字段需进行加密处理,加密方法详见《敏感信息加密说明》。(提醒:必须在HTTP头中上送Wechatpay-Serial)
+   * 
+ */ + @SpecEncrypt + @SerializedName(value = "account_number") + private String accountNumber; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SettlementResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SettlementResult.java new file mode 100644 index 0000000000..50dfbea77b --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SettlementResult.java @@ -0,0 +1,106 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 查询结算账户结果 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_5.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class SettlementResult implements Serializable { + /** + *
+   * 字段名:账户类型
+   * 变量名:account_type
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   * 枚举值:
+   *    ACCOUNT_TYPE_BUSINESS:对公银行账户
+   *    ACCOUNT_TYPE_PRIVATE:经营者个人银行卡
+   * 示例值:ACCOUNT_TYPE_BUSINESS
+   * 
+ */ + @SerializedName(value = "account_type") + private String accountType; + + /** + *
+   * 字段名:开户银行
+   * 变量名:account_bank
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  返回特约商户的结算账户-开户银行全称。
+   * 示例值:工商银行
+   * 
+ */ + @SerializedName(value = "account_bank") + private String accountBank; + + /** + *
+   * 字段名:开户银行全称(含支行)
+   * 变量名:bank_name
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  返回特约商户的结算账户-开户银行全称(含支行)。
+   * 示例值:施秉县农村信用合作联社城关信用社
+   * 
+ */ + @SerializedName(value = "bank_name") + private String bankName; + + /** + *
+   * 字段名:开户银行联行号
+   * 变量名:bank_branch_id
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  返回特约商户的结算账户-联行号。
+   * 示例值:402713354941
+   * 
+ */ + @SerializedName(value = "bank_branch_id") + private String bankBranchId; + + /** + *
+   * 字段名:银行账号
+   * 变量名:account_number
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  返回特约商户的结算账户-银行账号,掩码显示。
+   * 示例值:62*************78
+   * 
+ */ + @SerializedName(value = "account_number") + private String accountNumber; + + /** + *
+   * 字段名:汇款验证结果
+   * 变量名:verify_result
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  返回特约商户的结算账户-汇款验证结果。
+   *    VERIFYING:系统汇款验证中,商户可发起提现尝试。
+   *    VERIFY_SUCCESS:系统成功汇款,该账户可正常发起提现。
+   *    VERIFY_FAIL:系统汇款失败,该账户无法发起提现,请检查修改。
+   * 示例值:VERIFY_SUCCESS
+   * 
+ */ + @SerializedName(value = "verify_result") + private String verifyResult; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SignatureHeader.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SignatureHeader.java new file mode 100644 index 0000000000..bd50ac89d4 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SignatureHeader.java @@ -0,0 +1,35 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 微信通知接口头部信息,需要做签名验证 + * 文档地址: https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/qian-ming-yan-zheng + */ +@Data +@NoArgsConstructor +public class SignatureHeader implements Serializable { + private static final long serialVersionUID = -6958015499416059949L; + /** + * 时间戳 + */ + private String timeStamp; + + /** + * 随机串 + */ + private String nonce; + + /** + * 已签名字符串 + */ + private String signed; + + /** + * 证书序列号 + */ + private String serialNo; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java new file mode 100644 index 0000000000..0b836366d4 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawRequest.java @@ -0,0 +1,91 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 电商平台提现 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_5.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class SpWithdrawRequest implements Serializable { + /** + *
+   * 字段名:商户提现单号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户提现单号,由商户自定义生成。
+   * 示例值:20190611222222222200000000012122
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+   * 字段名:提现金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:int64
+   * 描述:
+   *  提现金额,单位:分(RMB)
+   * 示例值:1
+   * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+   * 字段名:备注
+   * 变量名:remark
+   * 是否必填:否
+   * 类型:string(56)
+   * 描述:
+   *  商户对提现单的备注
+   * 示例值:交易提现
+   * 
+ */ + @SerializedName(value = "remark") + private String remark; + + /** + *
+   * 字段名:银行附言
+   * 变量名:bank_memo
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  展示在收款银行系统中的附言,数字、字母最长32个汉字(能否成功展示依赖银行系统支持)。
+   * 示例值:xx平台提现
+   * 
+ */ + @SerializedName(value = "bank_memo") + private String bankMemo; + + /** + *
+   * 字段名:账户类型
+   * 变量名:account_type
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  枚举值:
+   *    BASIC:基本账户
+   *    OPERATION:运营账户
+   *    FEES:手续费账户
+   * 示例值:BASIC
+   * 
+ */ + @SerializedName(value = "account_type") + private String accountType; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawResult.java new file mode 100644 index 0000000000..b18e246677 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawResult.java @@ -0,0 +1,46 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 电商平台提现 结果 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_5.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class SpWithdrawResult implements Serializable { + + /** + *
+   * 字段名:微信支付提现单号
+   * 变量名:withdraw_id
+   * 是否必填:否 (文档里面是【否】,理论上应该都有值)
+   * 类型:string(128)
+   * 描述:
+   *  微信支付系统生成的提现单号。
+   * 示例值:12321937198237912739132791732912793127931279317929791239112123
+   * 
+ */ + @SerializedName(value = "withdraw_id") + private String withdrawId; + + /** + *
+   * 字段名:商户提现单号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  必须是字母数字
+   * 示例值: 20190611222222222200000000012122
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawStatusResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawStatusResult.java new file mode 100644 index 0000000000..d4c02443aa --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SpWithdrawStatusResult.java @@ -0,0 +1,195 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 电商平台查询提现状态 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_6.shtml
+ * 
+ * @author: f00lish + * @date: 2020/10/27 + */ +@Data +@NoArgsConstructor +public class SpWithdrawStatusResult implements Serializable { + + + private static final long serialVersionUID = -6013827963506201478L; + /** + *
+   * 字段名:提现单状态
+   * 变量名:status
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  枚举值:
+   *  CREATE_SUCCESS:受理成功
+   *  SUCCESS:提现成功
+   *  FAIL:提现失败
+   *  REFUND:提现退票
+   *  CLOSE:关单
+   *  INIT:业务单已创建
+   * 示例值:CREATE_SUCCESS
+   * 
+ */ + @SerializedName(value = "status") + private String status; + + + /** + *
+   * 字段名:微信支付提现单号
+   * 变量名:withdraw_id
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  电商平台提交二级商户提现申请后,由微信支付返回的申请单号,作为查询申请状态的唯一标识。
+   * 示例值: 12321937198237912739132791732912793127931279317929791239112123
+   * 
+ */ + @SerializedName(value = "withdraw_id") + private String withdrawId; + + /** + *
+   * 字段名:商户提现单号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户提现单号,由商户自定义生成。
+   * 示例值: 20190611222222222200000000012122
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+   * 字段名:提现金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:int)
+   * 描述:
+   *  单位:分
+   * 示例值:1
+   * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + + /** + *
+   * 字段名:发起提现时间
+   * 变量名:create_time
+   * 是否必填:是
+   * 类型:string(29)
+   * 描述:
+   *  遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+   *  YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+   *  TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+   * 示例值:2015-05-20T13:29:35.120+08:00
+   * 
+ */ + @SerializedName(value = "create_time") + private String createTime; + + + /** + *
+   * 字段名:提现状态更新时间
+   * 变量名:update_time
+   * 是否必填:是
+   * 类型:string(29)
+   * 描述:
+   *  遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+   *  YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+   *  TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+   * 示例值:2015-05-20T13:29:35.120+08:00
+   * 
+ */ + @SerializedName(value = "update_time") + private String updateTime; + + + /** + *
+   * 字段名:失败原因
+   * 变量名:reason
+   * 是否必填:是
+   * 类型:string(255)
+   * 描述:
+   *  仅在提现失败、退票、关单时有值
+   * 示例值:卡号错误
+   * 
+ */ + @SerializedName(value = "reason") + private String reason; + + /** + *
+   * 字段名:提现备注
+   * 变量名:remark
+   * 是否必填:是
+   * 类型:string(56)
+   * 描述:
+   *  商户对提现单的备注,若发起提现时未传入相应值或输入不合法,则该值为空
+   * 示例值:交易提现
+   * 
+ */ + @SerializedName(value = "remark") + private String remark; + + /** + *
+   * 字段名:银行附言
+   * 变量名:bank_memo
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  展示在收款银行系统中的附言,由数字、字母、汉字组成(能否成功展示依赖银行系统支持)。若发起提现时未传入相应值或输入不合法,则该值为空
+   * 示例值:微信提现
+   * 
+ */ + @SerializedName(value = "bank_memo") + private String bankMemo; + + /** + *
+   * 字段名:出款账户类型
+   * 变量名:account_type
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  BASIC:基本户
+   *  OPERATION:运营账户
+   *  FEES:手续费账户
+   * 示例值:BASIC
+   * 
+ */ + @SerializedName(value = "account_type") + private String account_type; + + /** + *
+   * 字段名:提现失败解决方案
+   * 变量名:solution
+   * 是否必填:是
+   * 类型:string(255)
+   * 描述:
+   *  仅在提现失败、退票、关单时有值
+   * 示例值:请修改结算银行卡信息
+   * 
+ */ + @SerializedName(value = "solution") + private String solution; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java new file mode 100644 index 0000000000..3c74db24c9 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawRequest.java @@ -0,0 +1,89 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 二级商户账户余额提现 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_3.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class SubWithdrawRequest implements Serializable { + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台二级商户号,由微信支付生成并下发。
+   * 示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:商户提现单号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  必须是字母数字
+   * 示例值: 20190611222222222200000000012122
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+   * 字段名:提现金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:int64
+   * 描述:
+   *  提现金额(单位:分)
+   * 示例值:100
+   * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + /** + *
+   * 字段名:备注
+   * 变量名:remark
+   * 是否必填:否
+   * 类型:string(56)
+   * 描述:
+   *  商户对提现单的备注
+   * 示例值:交易提现
+   * 
+ */ + @SerializedName(value = "remark") + private String remark; + + /** + *
+   * 字段名:银行附言
+   * 变量名:bank_memo
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  展示在收款银行系统中的附言,数字、字母最长32个汉字(能否成功展示依赖银行系统支持)。
+   * 示例值:微信支付提现
+   * 
+ */ + @SerializedName(value = "bank_memo") + private String bankMemo; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawResult.java new file mode 100644 index 0000000000..21213dd42d --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawResult.java @@ -0,0 +1,60 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 二级商户账户余额提现 结果 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_2.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class SubWithdrawResult implements Serializable { + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台二级商户号,由微信支付生成并下发。
+   * 示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:微信支付提现单号
+   * 变量名:withdraw_id
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  电商平台提交二级商户提现申请后,由微信支付返回的申请单号,作为查询申请状态的唯一标识。
+   * 示例值: 12321937198237912739132791732912793127931279317929791239112123
+   * 
+ */ + @SerializedName(value = "withdraw_id") + private String withdrawId; + + /** + *
+   * 字段名:商户提现单号
+   * 变量名:out_request_no
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  必须是字母数字
+   * 示例值: 20190611222222222200000000012122
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawStatusResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawStatusResult.java new file mode 100644 index 0000000000..27d624872b --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/SubWithdrawStatusResult.java @@ -0,0 +1,193 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +/** + * 二级商户查询提现状态 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_3.shtml
+ * 
+ * @author: f00lish + * @date: 2020/10/27 + */ +@Data +@NoArgsConstructor +public class SubWithdrawStatusResult implements Serializable { + + private static final long serialVersionUID = 4692602703819018325L; + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台二级商户号,由微信支付生成并下发。
+   * 示例值:1900000109
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:电商平台商户号
+   * 变量名:sp_mchid
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  电商平台商户号
+   * 示例值:1800000123
+   * 
+ */ + @SerializedName(value = "sp_mchid") + private String spMchid; + + + /** + *
+   * 字段名:提现单状态
+   * 变量名:status
+   * 是否必填:是
+   * 类型:string(16)
+   * 描述:
+   *  枚举值:
+   *  CREATE_SUCCESS:受理成功
+   *  SUCCESS:提现成功
+   *  FAIL:提现失败
+   *  REFUND:提现退票
+   *  CLOSE:关单
+   *  INIT:业务单已创建
+   * 示例值:CREATE_SUCCESS
+   * 
+ */ + @SerializedName(value = "status") + private String status; + + + /** + *
+   * 字段名:微信支付提现单号
+   * 变量名:withdraw_id
+   * 是否必填:是
+   * 类型:string(128)
+   * 描述:
+   *  电商平台提交二级商户提现申请后,由微信支付返回的申请单号,作为查询申请状态的唯一标识。
+   * 示例值: 12321937198237912739132791732912793127931279317929791239112123
+   * 
+ */ + @SerializedName(value = "withdraw_id") + private String withdrawId; + + /** + *
+   * 字段名:商户提现单号
+   * 变量名:out_request_no
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  商户提现单号,由商户自定义生成。
+   * 示例值: 20190611222222222200000000012122
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+   * 字段名:提现金额
+   * 变量名:amount
+   * 是否必填:是
+   * 类型:int)
+   * 描述:
+   *  单位:分
+   * 示例值:1
+   * 
+ */ + @SerializedName(value = "amount") + private Integer amount; + + + /** + *
+   * 字段名:发起提现时间
+   * 变量名:create_time
+   * 是否必填:是
+   * 类型:string(29)
+   * 描述:
+   *  遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+   *  YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+   *  TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+   * 示例值:2015-05-20T13:29:35.120+08:00
+   * 
+ */ + @SerializedName(value = "create_time") + private String createTime; + + + /** + *
+   * 字段名:提现状态更新时间
+   * 变量名:update_time
+   * 是否必填:是
+   * 类型:string(29)
+   * 描述:
+   *  遵循rfc3339标准格式,格式为YYYY-MM-DDTHH:mm:ss:sss+TIMEZONE,
+   *  YYYY-MM-DD表示年月日,T出现在字符串中,表示time元素的开头,HH:mm:ss:sss表示时分秒毫秒,
+   *  TIMEZONE表示时区(+08:00表示东八区时间,领先UTC 8小时,即北京时间)。
+   *  例如:2015-05-20T13:29:35+08:00表示,北京时间2015年5月20日13点29分35秒。
+   * 示例值:2015-05-20T13:29:35.120+08:00
+   * 
+ */ + @SerializedName(value = "update_time") + private String updateTime; + + + /** + *
+   * 字段名:失败原因
+   * 变量名:reason
+   * 是否必填:是
+   * 类型:string(255)
+   * 描述:
+   *  仅在提现失败、退票、关单时有值
+   * 示例值:卡号错误
+   * 
+ */ + @SerializedName(value = "reason") + private String reason; + + /** + *
+   * 字段名:提现备注
+   * 变量名:remark
+   * 是否必填:是
+   * 类型:string(56)
+   * 描述:
+   *  商户对提现单的备注,若发起提现时未传入相应值或输入不合法,则该值为空
+   * 示例值:交易提现
+   * 
+ */ + @SerializedName(value = "remark") + private String remark; + + /** + *
+   * 字段名:银行附言
+   * 变量名:bank_memo
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  展示在收款银行系统中的附言,由数字、字母、汉字组成(能否成功展示依赖银行系统支持)。若发起提现时未传入相应值或输入不合法,则该值为空
+   * 示例值:微信提现
+   * 
+ */ + @SerializedName(value = "bank_memo") + private String bankMemo; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TradeBillRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TradeBillRequest.java new file mode 100644 index 0000000000..ad623edf56 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TradeBillRequest.java @@ -0,0 +1,86 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +/** + * 交易账单请求 + * @author: f00lish + * @date: 2020/09/28 + */ +@Data +@Builder +@ToString +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class TradeBillRequest { + + /** + *
+   * 字段名:账单日期
+   * 变量名:bill_date
+   * 是否必填:是
+   * 类型:string(10)
+   * 描述:
+   *  格式YYYY-MM-DD
+   *  仅支持三个月内的账单下载申请。
+   *  示例值:2019-06-11
+   * 
+ */ + @SerializedName(value = "bill_date") + private String billDate; + + /** + *
+   * 字段名:二级商户号
+   * 变量名:sub_mchid
+   * 是否必填:否
+   * 类型:string(12)
+   * 描述:
+   *  1、若商户是直连商户:无需填写该字段。
+   *  2、若商户是服务商:
+   *  ● 不填则默认返回服务商下的交易或退款数据。
+   *  ● 如需下载某个子商户下的交易或退款数据,则该字段必填。
+   *  特殊规则:最小字符长度为8
+   *  注意:仅适用于电商平台 服务商
+   *  示例值:1900000001
+   * 
+ */ + @SerializedName(value = "sub_mchid") + private String subMchid; + + /** + *
+   * 字段名:账单类型
+   * 变量名:bill_type
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  不填则默认是ALL
+   *  枚举值:
+   *  ALL:返回当日所有订单信息(不含充值退款订单)
+   *  SUCCESS:返回当日成功支付的订单(不含充值退款订单)
+   *  REFUND:返回当日退款订单(不含充值退款订单)
+   *  示例值:ALL
+   * 
+ */ + @SerializedName(value = "bill_type") + private String billType; + + /** + *
+   * 字段名:压缩类型
+   * 变量名:tar_type
+   * 是否必填:否
+   * 类型:string(32)
+   * 描述:
+   *  不填则默认是数据流
+   *  枚举值:
+   *  GZIP:返回格式为.gzip的压缩包账单
+   *  示例值:GZIP
+   * 
+ */ + @SerializedName(value = "tar_type") + private String tarType; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TradeBillResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TradeBillResult.java new file mode 100644 index 0000000000..2fb7b60564 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TradeBillResult.java @@ -0,0 +1,60 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.google.gson.annotations.SerializedName; +import lombok.*; + +/** + * 交易账单结果 + * @author: f00lish + * @date: 2020/09/28 + */ +@Data +@Builder +@ToString +@NoArgsConstructor(access = AccessLevel.PRIVATE) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public class TradeBillResult { + + /** + *
+   * 字段名:哈希类型
+   * 变量名:hash_type
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  原始账单(gzip需要解压缩)的摘要值,用于校验文件的完整性。
+   *  示例值:SHA1
+   * 
+ */ + @SerializedName(value = "hash_type") + private String hashType; + + /** + *
+   * 字段名:哈希值
+   * 变量名:hash_value
+   * 是否必填:是
+   * 类型:string(1024)
+   * 描述:
+   *  原始账单(gzip需要解压缩)的摘要值,用于校验文件的完整性。
+   *  示例值:79bb0f45fc4c42234a918000b2668d689e2bde04
+   * 
+ */ + @SerializedName(value = "hash_value") + private String hashValue; + + /** + *
+   * 字段名:账单下载地址
+   * 变量名:download_url
+   * 是否必填:是
+   * 类型:string(32)
+   * 描述:
+   *  供下一步请求账单文件的下载地址,该地址30s内有效。
+   *  示例值:https://api.mch.weixin.qq.com/v3/billdownload/file?token=xxx
+   * 
+ */ + @SerializedName(value = "download_url") + private String downloadUrl; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TransactionsResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TransactionsResult.java new file mode 100644 index 0000000000..12a22ead74 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/TransactionsResult.java @@ -0,0 +1,114 @@ +package com.github.binarywang.wxpay.bean.ecommerce; + +import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum; +import com.github.binarywang.wxpay.v3.util.SignUtils; +import com.google.gson.annotations.SerializedName; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; +import java.security.PrivateKey; + +/** + * 合单支付 JSAPI支付结果响应 + */ +@Data +@NoArgsConstructor +public class TransactionsResult implements Serializable { + private static final long serialVersionUID = 1760592667519950149L; + /** + *
+   * 字段名:预支付交易会话标识 (APP支付、JSAPI支付 会返回)
+   * 变量名:prepay_id
+   * 是否必填:是
+   * 类型:string(64)
+   * 描述:
+   *  数字和字母。微信生成的预支付会话标识,用于后续接口调用使用。
+   *  示例值:wx201410272009395522657a690389285100
+   * 
+ */ + @SerializedName("prepay_id") + private String prepayId; + + /** + *
+   * 字段名:支付跳转链接   (H5支付 会返回)
+   * 变量名:h5_url
+   * 是否必填:是
+   * 类型:string(512)
+   * 描述:
+   *  支付跳转链接
+   *  示例值:https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx2016121516420242444321ca0631331346&package=1405458241
+   * 
+ */ + @SerializedName("h5_url") + private String h5Url; + + /** + *
+   * 字段名:二维码链接  (NATIVE支付 会返回)
+   * 变量名:h5_url
+   * 是否必填:是
+   * 类型:string(512)
+   * 描述:
+   *  二维码链接
+   * 示例值:weixin://pay.weixin.qq.com/bizpayurl/up?pr=NwY5Mz9&groupid=00
+   * 
+ */ + @SerializedName("code_url") + private String codeUrl; + + @Data + @Accessors(chain = true) + public static class JsapiResult implements Serializable { + private String appId; + private String timeStamp; + private String nonceStr; + private String packageValue; + private String signType; + private String paySign; + + private String getSignStr(){ + return String.format("%s\n%s\n%s\n%s\n", appId, timeStamp, nonceStr, packageValue); + } + } + + @Data + @Accessors(chain = true) + public static class AppResult implements Serializable { + private String appid; + private String partnerid; + private String prepayid; + private String packageValue; + private String noncestr; + private String timestamp; + + } + + public T getPayInfo(TradeTypeEnum tradeType, String appId, String mchId, PrivateKey privateKey){ + String timestamp = String.valueOf(System.currentTimeMillis() / 1000); + String nonceStr = SignUtils.genRandomStr(); + switch (tradeType){ + case JSAPI: + JsapiResult jsapiResult = new JsapiResult(); + jsapiResult.setAppId(appId).setTimeStamp(timestamp) + .setPackageValue("prepay_id=" + this.prepayId).setNonceStr(nonceStr) + //签名类型,默认为RSA,仅支持RSA。 + .setSignType("RSA").setPaySign(SignUtils.sign(jsapiResult.getSignStr(), privateKey)); + return (T) jsapiResult; + case MWEB: + return (T) this.h5Url; + case APP: + AppResult appResult = new AppResult(); + appResult.setAppid(appId).setPrepayid(this.prepayId).setPartnerid(mchId) + .setNoncestr(nonceStr).setTimestamp(timestamp) + //暂填写固定值Sign=WXPay + .setPackageValue("Sign=WXPay"); + return (T) appResult; + case NATIVE: + return (T) this.codeUrl; + } + return null; + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/FundBillTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/FundBillTypeEnum.java new file mode 100644 index 0000000000..72aff3a02b --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/FundBillTypeEnum.java @@ -0,0 +1,29 @@ +package com.github.binarywang.wxpay.bean.ecommerce.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 账单类型 + * @author: f00lish + * @date: 2020/09/28 + */ +@Getter +@AllArgsConstructor +public enum FundBillTypeEnum { + + /** + * 资金账单 + */ + FUND_FLOW_BILL("%s/v3/bill/fundflowbill?%s"), + /** + * 二级商户资金账单 + */ + SUB_FUND_FLOW_BILL("%s/v3/ecommerce/bill/fundflowbill?%s"); + + /** + * url + */ + private final String url; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/SpAccountTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/SpAccountTypeEnum.java new file mode 100644 index 0000000000..2d7067804e --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/SpAccountTypeEnum.java @@ -0,0 +1,32 @@ +package com.github.binarywang.wxpay.bean.ecommerce.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 服务商账户类型 + * @author: f00lish + * @date: 2020/09/12 + */ +@Getter +@AllArgsConstructor +public enum SpAccountTypeEnum { + + /** + * 基本账户 + */ + BASIC("BASIC"), + /** + * 运营账户 + */ + OPERATION("OPERATION"), + /** + * 手续费账户 + */ + FEES("FEES"); + + /** + * 账户类型 + */ + private final String value; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/TradeTypeEnum.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/TradeTypeEnum.java new file mode 100644 index 0000000000..e8bd5ccba4 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/ecommerce/enums/TradeTypeEnum.java @@ -0,0 +1,37 @@ +package com.github.binarywang.wxpay.bean.ecommerce.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * 支付方式 + */ +@Getter +@AllArgsConstructor +public enum TradeTypeEnum { + /** + * APP + */ + APP("/v3/combine-transactions/app", "/v3/pay/partner/transactions/app"), + /** + * JSAPI + */ + JSAPI("/v3/combine-transactions/jsapi", "/v3/pay/partner/transactions/jsapi"), + /** + * NATIVE + */ + NATIVE("/v3/combine-transactions/native", "/v3/pay/partner/transactions/native"), + /** + * MWEB + */ + MWEB("/v3/combine-transactions/h5", "/v3/pay/partner/transactions/h5"); + + /** + * 合单url + */ + private final String combineUrl; + /** + * 单独下单url + */ + private final String partnerUrl; +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/entwxpay/EntWxEmpPayRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/entwxpay/EntWxEmpPayRequest.java new file mode 100644 index 0000000000..37c0d038dd --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/entwxpay/EntWxEmpPayRequest.java @@ -0,0 +1,229 @@ +package com.github.binarywang.wxpay.bean.entwxpay; + +import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest; +import com.github.binarywang.wxpay.exception.WxPayException; +import com.thoughtworks.xstream.annotations.XStreamAlias; +import lombok.*; +import me.chanjar.weixin.common.annotation.Required; + +import java.util.Map; + +/** + * Created on 2020/11/29. + * 向员工付款请求对象 + * @author 拎小壶冲 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Builder(builderMethodName = "newBuilder") +@NoArgsConstructor +@AllArgsConstructor +@XStreamAlias("xml") +public class EntWxEmpPayRequest extends BaseWxPayRequest { + + /** + *
+   * 字段名:商户订单号.
+   * 变量名:partner_trade_no
+   * 是否必填:是
+   * 示例值:10000098201411111234567890
+   * 类型:String
+   * 描述:商户订单号
+   * 
+ */ + @Required + @XStreamAlias("partner_trade_no") + private String partnerTradeNo; + + /** + *
+   * 字段名:需保持唯一性 用户openid.
+   * 变量名:openid
+   * 是否必填:是
+   * 示例值:oxTWIuGaIt6gTKsQRLau2M0yL16E
+   * 类型:String
+   * 描述:商户appid下,某用户的openid
+   * 
+ */ + @Required + @XStreamAlias("openid") + private String openid; + + /** + *
+   * 字段名:设备号.
+   * 变量名:device_info
+   * 是否必填:否
+   * 示例值:13467007045764
+   * 类型:String(32)
+   * 描述:微信支付分配的终端设备号
+   * 
+ */ + @XStreamAlias("device_info") + private String deviceInfo; + + /** + *
+   * 字段名:校验用户姓名选项.
+   * 变量名:check_name
+   * 是否必填:是
+   * 示例值:OPTION_CHECK
+   * 类型:String
+   * 描述:NO_CHECK:不校验真实姓名 
+   * FORCE_CHECK:强校验真实姓名(未实名认证的用户会校验失败,无法转账) 
+   * OPTION_CHECK:针对已实名认证的用户才校验真实姓名(未实名认证用户不校验,可以转账成功)
+   * 
+ */ + @Required + @XStreamAlias("check_name") + private String checkName; + + /** + *
+   * 字段名:收款用户姓名.
+   * 变量名:re_user_name
+   * 是否必填:可选
+   * 示例值:马花花
+   * 类型:String
+   * 描述:收款用户真实姓名。
+   * 如果check_name设置为FORCE_CHECK或OPTION_CHECK,  则必填用户真实姓名
+   * 
+ */ + @XStreamAlias("re_user_name") + private String reUserName; + + /** + *
+   * 字段名:金额.
+   * 变量名:amount
+   * 是否必填:是
+   * 示例值:10099
+   * 类型:int
+   * 描述:企业付款金额, 单位为分
+   * 
+ */ + @Required + @XStreamAlias("amount") + private Integer amount; + + /** + *
+   * 字段名:企业付款描述信息.
+   * 变量名:desc
+   * 是否必填:是
+   * 示例值:理赔
+   * 类型:String
+   * 描述:企业付款操作说明信息。必填。
+   * 
+ */ + @Required + @XStreamAlias("desc") + private String description; + + /** + *
+   * 字段名:Ip地址.
+   * 变量名:spbill_create_ip
+   * 是否必填:是
+   * 示例值:192.168.0.1
+   * 类型:String(32)
+   * 描述:调用接口的机器Ip地址
+   * 
+ */ + @Required + @XStreamAlias("spbill_create_ip") + private String spbillCreateIp; + + /** + *
+   *   字段名: 付款消息类型
+   *   变量名: ww_msg_type
+   *   是否必填: 是
+   *   示例值:NORMAL_MSG
+   *   描述:NORMAL_MSG:普通付款消息 APPROVAL _MSG:审批付款消息
+   * 
+ */ + @Required + @XStreamAlias("ww_msg_type") + private String wwMsgType; + + /** + *
+   *   字段名: 审批单号
+   *   变量名: approval_number
+   *   是否必填: 否
+   *   示例值: 201705160008
+   *   描述:ww_msg_type为APPROVAL _MSG时,需要填写approval_number
+   * 
+ */ + @XStreamAlias("approval_number") + private String approvalNumber; + + /** + *
+   *   字段名: 审批类型
+   *   变量名: approval_type
+   *   是否必填: 否
+   *   示例值: 1
+   *   描述:ww_msg_type为APPROVAL _MSG时,需要填写1
+   * 
+ */ + @XStreamAlias("approval_type") + private Integer approvalType; + + + /** + *
+   *   字段名: 项目名称
+   *   变量名: act_name
+   *   是否必填: 是
+   *   示例值: 产品部门报销
+   *   描述:项目名称,最长50个utf8字符
+   * 
+ */ + @Required + @XStreamAlias("act_name") + private String actName; + + /** + *
+   *   字段名: 付款的应用id
+   *   变量名: agentid
+   *   是否必填: 否
+   *   示例值: 1
+   *   描述:以企业应用的名义付款,企业应用id,整型,可在企业微信管理端应用的设置页面查看。
+   * 
+ */ + @XStreamAlias("agentid") + private Integer agentId; + + + @Override + protected void checkConstraints() throws WxPayException { + + } + + @Override + protected boolean isWxWorkSign() { + return true; + } + + @Override + protected void storeMap(Map map) { + map.put("appid", appid); + map.put("mch_id", mchId); + map.put("device_info", deviceInfo); + map.put("partner_trade_no", partnerTradeNo); + map.put("openid", openid); + map.put("check_name", checkName); + map.put("re_user_name", reUserName); + map.put("amount", amount.toString()); + map.put("desc", description); + map.put("spbill_create_ip", spbillCreateIp); + map.put("act_name", actName); + map.put("ww_msg_type", wwMsgType); + map.put("approval_number", approvalNumber); + map.put("approval_type", approvalType.toString()); + map.put("agentid", agentId.toString()); + } +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PayScoreNotifyData.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PayScoreNotifyData.java index 81d5568bcd..82afdb4ce6 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PayScoreNotifyData.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PayScoreNotifyData.java @@ -18,38 +18,66 @@ public class PayScoreNotifyData implements Serializable { private static final long serialVersionUID = -8538014389773390989L; /** - * id : EV-2018022511223320873 - * create_time : 20180225112233 - * resource_type : encrypt-resource - * event_type : PAYSCORE.USER_CONFIRM - * resource : {"algorithm":"AEAD_AES_256_GCM","ciphertext":"...","nonce":"...","associated_data":""} + * 通知ID */ @SerializedName("id") private String id; + + /** + * 通知创建时间 + */ @SerializedName("create_time") private String createTime; + + /** + * 通知数据类型 + */ @SerializedName("resource_type") private String resourceType; + + /** + * 通知类型 + */ @SerializedName("event_type") private String eventType; + + /** + * 通知数据 + */ @SerializedName("resource") private Resource resource; + /** + * 回调摘要 + * summary + */ + @SerializedName("summary") + private String summary; + @Data public static class Resource implements Serializable { private static final long serialVersionUID = 8530711804335261449L; /** - * algorithm : AEAD_AES_256_GCM - * ciphertext : ... - * nonce : ... - * associated_data : + * 加密算法类型 */ @SerializedName("algorithm") private String algorithm; + + /** + * 数据密文 + */ @SerializedName("ciphertext") private String cipherText; + + /** + * 附加数据 + */ @SerializedName("nonce") private String nonce; + + /** + * 随机串 + */ @SerializedName("associated_data") private String associatedData; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java index fef0b5ab8b..e40960a056 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/PostPayment.java @@ -1,11 +1,12 @@ package com.github.binarywang.wxpay.bean.payscore; +import java.io.Serializable; + import com.google.gson.annotations.SerializedName; + import lombok.Data; import lombok.NoArgsConstructor; -import java.io.Serializable; - /** * 后付费项目. * @@ -25,9 +26,9 @@ public class PostPayment implements Serializable { @SerializedName("name") private String name; @SerializedName("amount") - private int amount; + private Integer amount; @SerializedName("description") private String description; @SerializedName("count") - private int count; + private Integer count; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/UserAuthorizationStatusNotifyResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/UserAuthorizationStatusNotifyResult.java new file mode 100644 index 0000000000..2a97d29738 --- /dev/null +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/UserAuthorizationStatusNotifyResult.java @@ -0,0 +1,138 @@ +package com.github.binarywang.wxpay.bean.payscore; + +import java.io.Serializable; + +import com.google.gson.annotations.SerializedName; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 授权/解除授权服务回调通知结果 + *
+ *   文档地址:https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter4_4.shtml
+ * 
+ */ +@Data +@NoArgsConstructor +public class UserAuthorizationStatusNotifyResult implements Serializable { + + /** + * 源数据 + */ + private PayScoreNotifyData rawData; + + /** + *
+   * 字段名:公众账号ID
+   * 变量名:appid
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  调用授权服务接口提交的公众账号ID。
+   * 示例值:wxd678efh567hg6787
+   * 
+ */ + @SerializedName(value = "appid") + private String appid; + + /** + *
+   * 字段名:商户号
+   * 变量名:mchid
+   * 是否必填:是
+   * 类型:string[1,32]
+   * 描述:
+   *  调用授权服务接口提交的商户号。
+   * 示例值:1230000109
+   * 
+ */ + @SerializedName(value = "mchid") + private String mchid; + + /** + *
+   * 字段名:商户签约单号
+   * 变量名:out_request_no
+   * 是否必填:否
+   * 类型:	string[1,64]
+   * 描述:
+   *  调用授权服务接口提交的商户请求唯一标识(新签约的用户,且在授权签约中上传了该字段,则在解约授权回调通知中有返回)。
+   * 示例值:1234323JKHDFE1243252
+   * 
+ */ + @SerializedName(value = "out_request_no") + private String outRequestNo; + + /** + *
+   * 字段名:服务ID
+   * 变量名:service_id
+   * 是否必填:是
+   * 类型:	string[1,32]
+   * 描述:
+   *  调用授权服务接口提交的服务ID。
+   * 示例值:1234323JKHDFE1243252
+   * 
+ */ + @SerializedName(value = "service_id") + private String serviceId; + + /** + *
+   * 字段名:用户标识
+   * 变量名:openid
+   * 是否必填:是
+   * 类型:	string[1,128]
+   * 描述:
+   *  微信用户在商户对应appid下的唯一标识。
+   * 示例值:oUpF8uMuAJO_M2pxb1Q9zNjWeS6o
+   * 
+ */ + @SerializedName(value = "openid") + private String openid; + + /** + *
+   * 字段名:回调状态
+   * 变量名:user_service_status
+   * 是否必填:否
+   * 类型:	string[1,32]
+   * 描述:
+   *  1、USER_OPEN_SERVICE:授权成功 
+   *  2、USER_CLOSE_SERVICE:解除授权成功
+   * 示例值:USER_OPEN_SERVICE
+   * 
+ */ + @SerializedName(value = "user_service_status") + private String userServiceStatus; + + /** + *
+   * 字段名:服务授权/解除授权时间
+   * 变量名:openorclose_time
+   * 是否必填:否
+   * 类型:	string[1,32]
+   * 描述:
+   *  服务授权/解除授权成功时间。
+   * 示例值:20180225112233
+   * 
+ */ + @SerializedName(value = "openorclose_time") + private String openOrCloseTime; + + /** + *
+   * 字段名:授权协议号
+   * 变量名:authorization_code
+   * 是否必填:否
+   * 类型:	string[1,32]
+   * 描述:
+   *  授权协议号,预授权时返回,非预授权不返回
+   * 示例值:1275342195190894594
+   * 
+ */ + @SerializedName(value = "authorization_code") + private String authorizationCode; + +} diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java index c4fd494382..0f4b92a7b7 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreRequest.java @@ -1,6 +1,10 @@ package com.github.binarywang.wxpay.bean.payscore; +import java.io.Serializable; +import java.util.List; + import com.google.gson.annotations.SerializedName; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -8,9 +12,6 @@ import lombok.experimental.Accessors; import me.chanjar.weixin.common.util.json.WxGsonBuilder; -import java.io.Serializable; -import java.util.List; - /** * @author doger.wang * @date 2020/5/12 16:36 @@ -63,15 +64,15 @@ public String toJson() { @SerializedName("openid") private String openid; @SerializedName("need_user_confirm") - private boolean needUserConfirm; + private Boolean needUserConfirm; @SerializedName("profit_sharing") - private boolean profitSharing; + private Boolean profitSharing; @SerializedName("post_payments") private List postPayments; @SerializedName("post_discounts") private List postDiscounts; @SerializedName("total_amount") - private int totalAmount; + private Integer totalAmount; @SerializedName("reason") private String reason; @SerializedName("goods_tag") @@ -80,5 +81,7 @@ public String toJson() { private String type; @SerializedName("detail") private Detail detail; + @SerializedName("authorization_code") + private String authorizationCode; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreResult.java index 58665bf55e..266440d214 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/payscore/WxPayScoreResult.java @@ -85,6 +85,22 @@ public static WxPayScoreResult fromJson(String json) { @SerializedName("payScoreSignInfo") private Map payScoreSignInfo; + @SerializedName("apply_permissions_token") + private String applyPermissionsToken; + + @SerializedName("authorization_code") + private String authorizationCode; + + @SerializedName("authorization_state") + private String authorizationState; + + @SerializedName("cancel_authorization_time") + private String cancelAuthorizationTime; + + @SerializedName("authorization_success_time") + private String authorizationSuccessTime; + + /** * 收款信息 */ diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingReturnQueryRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingReturnQueryRequest.java index 734c805401..d3c7816027 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingReturnQueryRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingReturnQueryRequest.java @@ -72,6 +72,11 @@ protected void checkConstraints() throws WxPayException { this.setSignType(WxPayConstants.SignType.HMAC_SHA256); } + @Override + protected boolean ignoreSubAppId() { + return true; + } + @Override protected void storeMap(Map map) { map.put("order_id", orderId); diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingReturnResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingReturnResult.java index bfa7353296..311503bc96 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingReturnResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/profitsharing/ProfitSharingReturnResult.java @@ -17,6 +17,19 @@ @XStreamAlias("xml") public class ProfitSharingReturnResult extends BaseWxPayResult { private static final long serialVersionUID = 718554909816994568L; + + /** + * 如果返回状态码为FAIL,则本字段存在,且为失败的错误码. + */ + @XStreamAlias("error_code") + private String errorCode; + + /** + * 如果返回状态码为FAIL,则本字段存在,且为失败的错误信息. + */ + @XStreamAlias("error_msg") + private String errorMsg; + /** * 微信分账单号 */ @@ -87,4 +100,14 @@ protected void loadXml(Document d) { failReason = readXmlString(d, "fail_reason"); finishTime = readXmlString(d, "finish_time"); } + + @Override + public String getErrCode() { + return this.errorCode; + } + + @Override + public String getErrCodeDes() { + return this.errorMsg; + } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java index f19935e7e1..394bc8969b 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/BaseWxPayRequest.java @@ -9,6 +9,7 @@ import lombok.Data; import lombok.experimental.Accessors; import me.chanjar.weixin.common.error.WxErrorException; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.BeanUtils; import me.chanjar.weixin.common.util.json.WxGsonBuilder; import me.chanjar.weixin.common.util.xml.XStreamInitializer; @@ -246,7 +247,7 @@ private String toFastXml() { return document.asXML(); } catch (Exception e) { - throw new RuntimeException("generate xml error", e); + throw new WxRuntimeException("generate xml error", e); } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPaySendRedpackRequest.zip b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPaySendRedpackRequest.zip deleted file mode 100644 index b02696333a..0000000000 Binary files a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/request/WxPaySendRedpackRequest.zip and /dev/null differ diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java index 51865664f4..d83e3d06a5 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/BaseWxPayResult.java @@ -11,6 +11,7 @@ import com.thoughtworks.xstream.XStream; import com.thoughtworks.xstream.annotations.XStreamAlias; import lombok.Data; +import me.chanjar.weixin.common.error.WxRuntimeException; import me.chanjar.weixin.common.util.json.WxGsonBuilder; import me.chanjar.weixin.common.util.xml.XStreamInitializer; import org.apache.commons.lang3.StringUtils; @@ -139,7 +140,7 @@ public static T fromXML(String xmlString, Class c t.loadXml(doc); return (T) t; } catch (Exception e) { - throw new RuntimeException("parse xml error", e); + throw new WxRuntimeException("parse xml error", e); } } XStream xstream = XStreamInitializer.getInstance(); @@ -243,7 +244,7 @@ public String toString() { */ public Map toMap() { if (StringUtils.isBlank(this.xmlString)) { - throw new RuntimeException("xml数据有问题,请核实!"); + throw new WxRuntimeException("xml数据有问题,请核实!"); } Map result = Maps.newHashMap(); @@ -258,7 +259,7 @@ public Map toMap() { result.put(list.item(i).getNodeName(), list.item(i).getTextContent()); } } catch (XPathExpressionException e) { - throw new RuntimeException("非法的xml文本内容:" + xmlString); + throw new WxRuntimeException("非法的xml文本内容:" + xmlString); } return result; @@ -282,7 +283,7 @@ protected Document openXML(String content) { factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); return factory.newDocumentBuilder().parse(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))); } catch (Exception e) { - throw new RuntimeException("非法的xml文本内容:\n" + this.xmlString, e); + throw new WxRuntimeException("非法的xml文本内容:\n" + this.xmlString, e); } } @@ -302,7 +303,7 @@ protected String getXmlValue(String... path) { .compile(expression) .evaluate(doc, XPathConstants.STRING); } catch (XPathExpressionException e) { - throw new RuntimeException("未找到相应路径的文本:" + expression); + throw new WxRuntimeException("未找到相应路径的文本:" + expression); } } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayMicropayResult.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayMicropayResult.java index 1eb1a7a604..6c267a9225 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayMicropayResult.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/bean/result/WxPayMicropayResult.java @@ -48,6 +48,32 @@ public class WxPayMicropayResult extends BaseWxPayResult implements Serializable **/ @XStreamAlias("is_subscribe") private String isSubscribe; + + /** + *
+   * 用户子标识.
+   * sub_openid
+   * 否
+   * String(128)
+   * Y
+   * 子商户appid下用户唯一标识,如需返回则请求时需要传sub_appid
+   * 
+ **/ + @XStreamAlias("sub_openid") + private String subOpenid; + + /** + *
+   * 是否关注子公众账号.
+   * sub_is_subscribe
+   * 否
+   * String(1)
+   * Y
+   * 用户是否关注子公众账号,仅在公众账号类型支付有效,取值范围:Y或N;Y-关注;N-未关注
+   * 
+ **/ + @XStreamAlias("sub_is_subscribe") + private String subIsSubscribe; /** *
@@ -227,6 +253,8 @@ public class WxPayMicropayResult extends BaseWxPayResult implements Serializable
   protected void loadXml(Document d) {
     openid = readXmlString(d, "openid");
     isSubscribe = readXmlString(d, "is_subscribe");
+    subOpenid = readXmlString(d, "sub_openid");
+    subIsSubscribe = readXmlString(d, "sub_is_subscribe");
     tradeType = readXmlString(d, "trade_type");
     bankType = readXmlString(d, "bank_type");
     feeType = readXmlString(d, "fee_type");
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
index 2769e22018..8fed27452e 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java
@@ -6,6 +6,7 @@
 import com.github.binarywang.wxpay.v3.util.PemUtils;
 import jodd.util.ResourcesUtil;
 import lombok.Data;
+import lombok.EqualsAndHashCode;
 import lombok.SneakyThrows;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.lang3.RegExUtils;
@@ -19,6 +20,7 @@
 import java.nio.charset.StandardCharsets;
 import java.security.KeyStore;
 import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
 import java.util.Collections;
 
 /**
@@ -27,6 +29,7 @@
  * @author Binary Wang (https://github.com/binarywang)
  */
 @Data
+@EqualsAndHashCode(exclude = "verifier")
 public class WxPayConfig {
   private static final String DEFAULT_PAY_BASE_URL = "https://api.mch.weixin.qq.com";
   private static final String PROBLEM_MSG = "证书文件【%s】有问题,请核实!";
@@ -125,6 +128,13 @@ public class WxPayConfig {
    */
   private String payScoreNotifyUrl;
 
+
+  /**
+   * 微信支付分授权回调地址
+   */
+  private String payScorePermissionNotifyUrl;
+
+
   private CloseableHttpClient apiV3HttpClient;
   /**
    * 私钥信息
@@ -229,7 +239,7 @@ public SSLContext initSSLContext() throws WxPayException {
   public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
     String privateKeyPath = this.getPrivateKeyPath();
     String privateCertPath = this.getPrivateCertPath();
-    String certSerialNo = this.getCertSerialNo();
+    String serialNo = this.getCertSerialNo();
     String apiV3Key = this.getApiV3Key();
     if (StringUtils.isBlank(privateKeyPath)) {
       throw new WxPayException("请确保privateKeyPath已设置");
@@ -237,9 +247,9 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
     if (StringUtils.isBlank(privateCertPath)) {
       throw new WxPayException("请确保privateCertPath已设置");
     }
-    if (StringUtils.isBlank(certSerialNo)) {
-      throw new WxPayException("请确保certSerialNo证书序列号已设置");
-    }
+//    if (StringUtils.isBlank(certSerialNo)) {
+//      throw new WxPayException("请确保certSerialNo证书序列号已设置");
+//    }
     if (StringUtils.isBlank(apiV3Key)) {
       throw new WxPayException("请确保apiV3Key值已设置");
     }
@@ -248,6 +258,10 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
     InputStream certInputStream = this.loadConfigInputStream(privateCertPath);
     try {
       PrivateKey merchantPrivateKey = PemUtils.loadPrivateKey(keyInputStream);
+      X509Certificate certificate = PemUtils.loadCertificate(certInputStream);
+      if(StringUtils.isBlank(serialNo)){
+        this.certSerialNo = certificate.getSerialNumber().toString(16).toUpperCase();
+      }
 
       AutoUpdateCertificatesVerifier verifier = new AutoUpdateCertificatesVerifier(
         new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
@@ -255,7 +269,7 @@ public CloseableHttpClient initApiV3HttpClient() throws WxPayException {
 
       CloseableHttpClient httpClient = WxPayV3HttpClientBuilder.create()
         .withMerchant(mchId, certSerialNo, merchantPrivateKey)
-        .withWechatpay(Collections.singletonList(PemUtils.loadCertificate(certInputStream)))
+        .withWechatpay(Collections.singletonList(certificate))
         .withValidator(new WxPayValidator(verifier))
         .build();
       this.apiV3HttpClient = httpClient;
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/converter/WxPayOrderNotifyResultConverter.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/converter/WxPayOrderNotifyResultConverter.java
index cc8a80dad1..3fa2e5bf9c 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/converter/WxPayOrderNotifyResultConverter.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/converter/WxPayOrderNotifyResultConverter.java
@@ -112,7 +112,8 @@ private void setFieldValue(UnmarshallingContext context, WxPayOrderNotifyResult
     Object val = context.convertAnother(obj, field.getType());
     try {
       if (val != null) {
-        PropertyDescriptor pd = new PropertyDescriptor(field.getName(), obj.getClass());
+    	//这里加一个看似多余的(String)强转可解决高jdk版本下的编译报错问题,详情见讨论https://github.com/vaadin/framework/issues/10737
+        PropertyDescriptor pd = new PropertyDescriptor((String)field.getName(), obj.getClass());
         pd.getWriteMethod().invoke(obj, val);
       }
     } catch (Exception ignored) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java
index 5c86306b9f..91c58e7ac3 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EcommerceService.java
@@ -1,10 +1,13 @@
 package com.github.binarywang.wxpay.service;
 
-import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsRequest;
-import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsResult;
-import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsStatusResult;
+import com.github.binarywang.wxpay.bean.ecommerce.*;
+import com.github.binarywang.wxpay.bean.ecommerce.enums.FundBillTypeEnum;
+import com.github.binarywang.wxpay.bean.ecommerce.enums.SpAccountTypeEnum;
+import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum;
 import com.github.binarywang.wxpay.exception.WxPayException;
 
+import java.io.InputStream;
+
 /**
  * 
  *  电商收付通相关服务类.
@@ -12,7 +15,7 @@
  * 
* * @author cloudX - * @date 2020/08/17 + * @date 2020 /08/17 */ public interface EcommerceService { /** @@ -55,4 +58,371 @@ public interface EcommerceService { */ ApplymentsStatusResult queryApplyStatusByOutRequestNo(String outRequestNo) throws WxPayException; + /** + *
+   * 合单支付API(APP支付、JSAPI支付、H5支付、NATIVE支付).
+   * 请求URL:https://api.mch.weixin.qq.com/v3/combine-transactions/jsapi
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e-combine.shtml
+   * 
+ * + * @param tradeType 支付方式 + * @param request 请求对象 + * @return 微信合单支付返回 transactions result + * @throws WxPayException the wx pay exception + */ + TransactionsResult combine(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException; + + /** + *
+   * 合单支付API(APP支付、JSAPI支付、H5支付、NATIVE支付).
+   * 请求URL:https://api.mch.weixin.qq.com/v3/combine-transactions/jsapi
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e-combine.shtml
+   * 
+ * + * @param the type parameter + * @param tradeType 支付方式 + * @param request 请求对象 + * @return 调起支付需要的参数 t + * @throws WxPayException the wx pay exception + */ + T combineTransactions(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException; + + /** + *
+   * 合单支付通知回调数据处理
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e-combine.shtml
+   * 
+ * + * @param notifyData 通知数据 + * @param header 通知头部数据,不传则表示不校验头 + * @return 解密后通知数据 combine transactions notify result + * @throws WxPayException the wx pay exception + */ + CombineTransactionsNotifyResult parseCombineNotifyResult(String notifyData, SignatureHeader header) throws WxPayException; + + /** + *
+   * 合单查询订单API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/combine/chapter3_3.shtml
+   * 
+ * + * @param outTradeNo 合单商户订单号 + * @return 支付订单信息 + * @throws WxPayException the wx pay exception + */ + CombineTransactionsResult queryCombineTransactions(String outTradeNo) throws WxPayException; + + /** + *
+   *  服务商模式普通支付API(APP支付、JSAPI支付、H5支付、NATIVE支付).
+   *  请求URL:https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi
+   *  文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/transactions_sl.shtml
+   *  
+ * + * @param tradeType 支付方式 + * @param request 请求对象 + * @return 调起支付需要的参数 transactions result + * @throws WxPayException the wx pay exception + */ + TransactionsResult partner(TradeTypeEnum tradeType, PartnerTransactionsRequest request) throws WxPayException; + + /** + *
+   *  服务商模式普通支付API(APP支付、JSAPI支付、H5支付、NATIVE支付).
+   *  请求URL:https://api.mch.weixin.qq.com/v3/pay/partner/transactions/jsapi
+   *  文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/transactions_sl.shtml
+   *  
+ * + * @param the type parameter + * @param tradeType 支付方式 + * @param request 请求对象 + * @return 调起支付需要的参数 t + * @throws WxPayException the wx pay exception + */ + T partnerTransactions(TradeTypeEnum tradeType, PartnerTransactionsRequest request) throws WxPayException; + + /** + *
+   * 普通支付通知回调数据处理
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/e_transactions.shtml
+   * 
+ * + * @param notifyData 通知数据 + * @param header 通知头部数据,不传则表示不校验头 + * @return 解密后通知数据 partner transactions notify result + * @throws WxPayException the wx pay exception + */ + PartnerTransactionsNotifyResult parsePartnerNotifyResult(String notifyData, SignatureHeader header) throws WxPayException; + + /** + *
+   * 普通查询订单API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/e_transactions/chapter3_5.shtml
+   * 
+ * + * @param request 商户订单信息 + * @return 支付订单信息 + * @throws WxPayException the wx pay exception + */ + PartnerTransactionsResult queryPartnerTransactions(PartnerTransactionsQueryRequest request) throws WxPayException; + + /** + *
+   * 服务商账户实时余额
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/amount.shtml
+   * 
+ * + * @param accountType 服务商账户类型 + * @return 返回数据 fund balance result + * @throws WxPayException the wx pay exception + */ + FundBalanceResult spNowBalance(SpAccountTypeEnum accountType) throws WxPayException; + + /** + *
+   * 服务商账户日终余额
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/amount.shtml
+   * 
+ * + * @param accountType 服务商账户类型 + * @param date 查询日期 2020-09-11 + * @return 返回数据 fund balance result + * @throws WxPayException the wx pay exception + */ + FundBalanceResult spDayEndBalance(SpAccountTypeEnum accountType, String date) throws WxPayException; + + /** + *
+   * 二级商户号账户实时余额
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/amount.shtml
+   * 
+ * + * @param subMchid 二级商户号 + * @return 返回数据 fund balance result + * @throws WxPayException the wx pay exception + */ + FundBalanceResult subNowBalance(String subMchid) throws WxPayException; + + /** + *
+   * 二级商户号账户日终余额
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/amount.shtml
+   * 
+ * + * @param subMchid 二级商户号 + * @param date 查询日期 2020-09-11 + * @return 返回数据 fund balance result + * @throws WxPayException the wx pay exception + */ + FundBalanceResult subDayEndBalance(String subMchid, String date) throws WxPayException; + + /** + *
+   * 请求分账API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_1.shtml
+   * 
+ * + * @param request 分账请求 + * @return 返回数据 profit sharing result + * @throws WxPayException the wx pay exception + */ + ProfitSharingResult profitSharing(ProfitSharingRequest request) throws WxPayException; + + /** + *
+   * 查询分账结果API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_2.shtml
+   * 
+ * + * @param request 查询分账请求 + * @return 返回数据 profit sharing result + * @throws WxPayException the wx pay exception + */ + ProfitSharingResult queryProfitSharing(ProfitSharingQueryRequest request) throws WxPayException; + + /** + *
+   * 请求分账回退API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_3.shtml
+   * 
+ * + * @param request 分账回退请求 + * @return 返回数据 return orders result + * @throws WxPayException the wx pay exception + */ + ReturnOrdersResult returnOrders(ReturnOrdersRequest request) throws WxPayException; + + /** + *
+   * 完结分账API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/profitsharing/chapter3_5.shtml
+   * 
+ * + * @param request 完结分账请求 + * @return 返回数据 return orders result + * @throws WxPayException the wx pay exception + */ + ProfitSharingResult finishOrder(FinishOrderRequest request) throws WxPayException; + + /** + *
+   * 退款申请API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_1.shtml
+   * 
+ * + * @param request 退款请求 + * @return 返回数据 return refunds result + * @throws WxPayException the wx pay exception + */ + RefundsResult refunds(RefundsRequest request) throws WxPayException; + + /** + *
+   * 查询退款API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_2.shtml
+   * 
+ * + * @param subMchid 二级商户号 + * @param refundId 微信退款单号 + * @return 返回数据 return refunds result + * @throws WxPayException the wx pay exception + */ + RefundQueryResult queryRefundByRefundId(String subMchid, String refundId) throws WxPayException; + + /** + *
+   * 查询退款API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_2.shtml
+   * 
+ * + * @param subMchid 二级商户号 + * @param outRefundNo 商户退款单号 + * @return 返回数据 return refunds result + * @throws WxPayException the wx pay exception + */ + RefundQueryResult queryRefundByOutRefundNo(String subMchid, String outRefundNo) throws WxPayException; + + /** + *
+   * 退款通知回调数据处理
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/refunds/chapter3_3.shtml
+   * 
+ * + * @param notifyData 通知数据 + * @param header 通知头部数据,不传则表示不校验头 + * @return 解密后通知数据 partner refund notify result + * @throws WxPayException the wx pay exception + */ + RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHeader header) throws WxPayException; + + /** + *
+   * 二级商户账户余额提现API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_2.shtml
+   * 
+ * + * @param request 提现请求 + * @return 返回数据 return withdraw result + * @throws WxPayException the wx pay exception + */ + SubWithdrawResult subWithdraw(SubWithdrawRequest request) throws WxPayException; + + /** + *
+   * 电商平台提现API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_5.shtml
+   * 
+ * + * @param request 提现请求 + * @return 返回数据 return withdraw result + * @throws WxPayException the wx pay exception + */ + SpWithdrawResult spWithdraw(SpWithdrawRequest request) throws WxPayException; + + /** + *
+   * 二级商户查询提现状态API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_3.shtml
+   * 
+ * + * @param subMchid 二级商户号 + * @param outRequestNo 商户提现单号 + * @return 返回数据 return sub withdraw status result + * @throws WxPayException the wx pay exception + */ + SubWithdrawStatusResult querySubWithdrawByOutRequestNo(String subMchid, String outRequestNo) throws WxPayException; + + /** + *
+   * 电商平台查询提现状态API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/fund/chapter3_6.shtml
+   * 
+ * + * @param outRequestNo 商户提现单号 + * @return 返回数据 return sp withdraw status result + * @throws WxPayException the wx pay exception + */ + SpWithdrawStatusResult querySpWithdrawByOutRequestNo(String outRequestNo) throws WxPayException; + + /** + *
+   * 修改结算帐号API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_4.shtml
+   * 
+ * + * @param subMchid 二级商户号。 + * @param request 结算帐号 + * @throws WxPayException the wx pay exception + */ + void modifySettlement(String subMchid, SettlementRequest request) throws WxPayException; + + /** + *
+   * 查询结算账户API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/ecommerce/applyments/chapter3_5.shtml
+   * 
+ * + * @param subMchid 二级商户号。 + * @return 返回数据 return settlement result + * @throws WxPayException the wx pay exception + */ + SettlementResult querySettlement(String subMchid) throws WxPayException; + + /** + *
+   * 请求账单API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/bill.shtml
+   * 
+ * + * @param request 请求信息。 + * @return 返回数据 return trade bill result + * @throws WxPayException the wx pay exception + */ + TradeBillResult applyBill(TradeBillRequest request) throws WxPayException; + + /** + *
+   * 申请资金账单API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pay/bill/chapter3_2.shtml
+   * 
+ * + * @param billType 账单类型。 + * @param request 请求信息。 + * @return 返回数据 return fund bill result + * @throws WxPayException the wx pay exception + */ + FundBillResult applyFundBill(FundBillTypeEnum billType, FundBillRequest request) throws WxPayException; + + /** + *
+   * 下载账单API
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/pages/bill.shtml
+   * 
+ * + * @param url 微信返回的账单地址。 + * @return 返回数据 return inputStream + * @throws WxPayException the wx pay exception + */ + InputStream downloadBill(String url) throws WxPayException; + } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EntPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EntPayService.java index 1b1b76b154..ed159275bf 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EntPayService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/EntPayService.java @@ -1,6 +1,7 @@ package com.github.binarywang.wxpay.service; import com.github.binarywang.wxpay.bean.entpay.*; +import com.github.binarywang.wxpay.bean.entwxpay.EntWxEmpPayRequest; import com.github.binarywang.wxpay.exception.WxPayException; /** @@ -143,4 +144,15 @@ public interface EntPayService { * @throws WxPayException the wx pay exception */ EntPayRedpackQueryResult queryEnterpriseRedpack(EntPayRedpackQueryRequest request) throws WxPayException; + + /** + * 向员工付款 + * 文档详见 https://work.weixin.qq.com/api/doc/90000/90135/90278 + * 接口链接 https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/paywwsptrans2pocket + * + * @param request 请求对象 + * @return EntPayResult the ent pay result + * @throws WxPayException the wx pay exception + */ + EntPayResult toEmpPay(EntWxEmpPayRequest request) throws WxPayException; } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MerchantMediaService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MerchantMediaService.java index 429ece394d..0e35dbb68b 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MerchantMediaService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/MerchantMediaService.java @@ -5,6 +5,7 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; /** *
@@ -27,5 +28,19 @@ public interface MerchantMediaService {
    */
   ImageUploadResult imageUploadV3(File imageFile) throws WxPayException, IOException;
 
+  /**
+   * 
+   * 通用接口-图片上传API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/tool/chapter3_1.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/merchant/media/upload
+   * 
+ * + * @param inputStream 需要上传的图片文件流 + * @param fileName 需要上传的图片文件名 + * @return ImageUploadResult 微信返回的媒体文件标识Id。示例值:6uqyGjGrCf2GtyXP8bxrbuH9-aAoTjH-rKeSl3Lf4_So6kdkQu4w8BYVP3bzLtvR38lxt4PjtCDXsQpzqge_hQEovHzOhsLleGFQVRF-U_0 + * @throws WxPayException the wx pay exception + */ + ImageUploadResult imageUploadV3(InputStream inputStream, String fileName) throws WxPayException, IOException; + } diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java index ff6aafe9f4..5b4f692033 100644 --- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java +++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/PayScoreService.java @@ -1,6 +1,8 @@ package com.github.binarywang.wxpay.service; +import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader; import com.github.binarywang.wxpay.bean.payscore.PayScoreNotifyData; +import com.github.binarywang.wxpay.bean.payscore.UserAuthorizationStatusNotifyResult; import com.github.binarywang.wxpay.bean.payscore.WxPayScoreRequest; import com.github.binarywang.wxpay.bean.payscore.WxPayScoreResult; import com.github.binarywang.wxpay.exception.WxPayException; @@ -18,6 +20,91 @@ * @author doger.wang */ public interface PayScoreService { + + + + /** + *
+   * 支付分商户预授权API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter5_1.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/permissions
+   * 
+ * + * @param request 请求对象 + * @return WxPayScoreResult wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult permissions(WxPayScoreRequest request) throws WxPayException; + + + /** + *
+   * 支付分查询与用户授权记录(授权协议号)API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter5_2.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/permissions/authorization-code/{authorization_code}
+   * 
+ * + * @param authorizationCode + * @return WxPayScoreResult wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult permissionsQueryByAuthorizationCode(String authorizationCode) throws WxPayException; + + + + /** + *
+   * 解除用户授权关系(授权协议号)API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter5_3.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/permissions/authorization-code/{authorization_code}/terminate
+   * 
+ * + * @param authorizationCode + * @param reason + * @return WxPayScoreResult wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult permissionsTerminateByAuthorizationCode(String authorizationCode,String reason) throws WxPayException; + + + + + + /** + *
+   * 支付分查询与用户授权记录(openid)API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter5_4shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/permissions/openid/{openid}
+   * 
+ * + * @param openId + * @return WxPayScoreResult wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult permissionsQueryByOpenId(String openId) throws WxPayException; + + + + + + /** + *
+   * 解除用户授权关系(openid)API
+   * 文档详见: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter5_5.shtml
+   * 接口链接:https://api.mch.weixin.qq.com/v3/payscore/permissions/openid/{openid}/terminate
+   * 
+ * + * @param openId + * @param reason + * @return WxPayScoreResult wx pay score result + * @throws WxPayException the wx pay exception + */ + WxPayScoreResult permissionsTerminateByOpenId(String openId,String reason) throws WxPayException; + + + + + /** *
    * 支付分创建订单API.
@@ -111,6 +198,19 @@ public interface PayScoreService {
    * @throws WxPayException the wx pay exception
    */
   WxPayScoreResult syncServiceOrder(WxPayScoreRequest request) throws WxPayException;
+  
+  /**
+   * 
+   * 授权/解除授权服务回调数据处理
+   * 文档地址: https://pay.weixin.qq.com/wiki/doc/apiv3/wxpay/payscore/chapter4_4.shtml
+   * 
+ * + * @param notifyData 通知数据 + * @param header 通知头部数据,不传则表示不校验头 + * @return 解密后通知数据 return user authorization status notify result + * @throws WxPayException the wx pay exception + */ + UserAuthorizationStatusNotifyResult parseUserAuthorizationStatusNotifyResult(String notifyData, SignatureHeader header) throws WxPayException; /** *
@@ -121,7 +221,7 @@ public interface PayScoreService {
    * @param data the data
    * @return the wx pay score result
    */
-  PayScoreNotifyData parseNotifyData(String data);
+  PayScoreNotifyData parseNotifyData(String data,SignatureHeader header) throws WxPayException;
 
   /**
    * 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
index 27d86548ca..daa8d35973 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/WxPayService.java
@@ -13,6 +13,7 @@
 import org.apache.http.client.methods.HttpPost;
 
 import java.io.File;
+import java.io.InputStream;
 import java.net.URI;
 import java.util.Date;
 import java.util.Map;
@@ -97,6 +98,15 @@ public interface WxPayService {
    */
   String getV3(URI url) throws WxPayException;
 
+  /**
+   * 发送下载 V3请求,得到响应流.
+   *
+   * @param url 请求地址
+   * @return 返回请求响应流
+   * @throws WxPayException the wx pay exception
+   */
+  InputStream downloadV3(URI url) throws WxPayException;
+
   /**
    * 获取企业付款服务类.
    *
@@ -403,6 +413,17 @@ WxPayRefundQueryResult refundQuery(String transactionId, String outTradeNo, Stri
    */
   WxPayRefundNotifyResult parseRefundNotifyResult(String xmlData) throws WxPayException;
 
+  /**
+   * 解析扫码支付回调通知
+   * 详见https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
+   *
+   * @param xmlData the xml data
+   * @param signType 签名类型
+   * @return the wx scan pay notify result
+   * @throws WxPayException the wx pay exception
+   */
+  WxScanPayNotifyResult parseScanPayNotifyResult(String xmlData, String signType) throws WxPayException;
+
   /**
    * 解析扫码支付回调通知
    * 详见https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=6_4
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
index b5012643ca..9bf9e9a8d2 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java
@@ -23,6 +23,7 @@
 import com.google.common.base.Joiner;
 import com.google.common.collect.Maps;
 import jodd.io.ZipUtil;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import org.apache.commons.lang3.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -235,19 +236,24 @@ public WxPayRefundNotifyResult parseRefundNotifyResult(String xmlData) throws Wx
   }
 
   @Override
-  public WxScanPayNotifyResult parseScanPayNotifyResult(String xmlData) throws WxPayException {
+  public WxScanPayNotifyResult parseScanPayNotifyResult(String xmlData, String signType) throws WxPayException {
     try {
       log.debug("扫码支付回调通知请求参数:{}", xmlData);
       WxScanPayNotifyResult result = BaseWxPayResult.fromXML(xmlData, WxScanPayNotifyResult.class);
       log.debug("扫码支付回调通知解析后的对象:{}", result);
-      result.checkResult(this, this.getConfig().getSignType(), false);
+      result.checkResult(this, signType, false);
       return result;
     } catch (WxPayException e) {
       throw e;
     } catch (Exception e) {
       throw new WxPayException("发生异常," + e.getMessage(), e);
     }
+  }
 
+  @Override
+  public WxScanPayNotifyResult parseScanPayNotifyResult(String xmlData) throws WxPayException {
+    final String signType = this.getConfig().getSignType();
+    return this.parseScanPayNotifyResult(xmlData, signType);
   }
 
   @Override
@@ -409,7 +415,7 @@ public Map getPayInfo(WxPayUnifiedOrderRequest request) throws W
     WxPayUnifiedOrderResult unifiedOrderResult = this.unifiedOrder(request);
     String prepayId = unifiedOrderResult.getPrepayId();
     if (StringUtils.isBlank(prepayId)) {
-      throw new RuntimeException(String.format("无法获取prepay id,错误代码: '%s',信息:%s。",
+      throw new WxRuntimeException(String.format("无法获取prepay id,错误代码: '%s',信息:%s。",
         unifiedOrderResult.getErrCode(), unifiedOrderResult.getErrCodeDes()));
     }
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java
index 9631631272..58a02d8ff8 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImpl.java
@@ -1,20 +1,33 @@
 package com.github.binarywang.wxpay.service.impl;
 
-import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsRequest;
-import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsResult;
-import com.github.binarywang.wxpay.bean.ecommerce.ApplymentsStatusResult;
+import com.github.binarywang.wxpay.bean.ecommerce.*;
+import com.github.binarywang.wxpay.bean.ecommerce.enums.FundBillTypeEnum;
+import com.github.binarywang.wxpay.bean.ecommerce.enums.SpAccountTypeEnum;
+import com.github.binarywang.wxpay.bean.ecommerce.enums.TradeTypeEnum;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.service.EcommerceService;
 import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.v3.util.AesUtils;
 import com.github.binarywang.wxpay.v3.util.RsaCryptoUtil;
+import com.google.common.base.CaseFormat;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import lombok.RequiredArgsConstructor;
+import org.apache.commons.beanutils.BeanMap;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 
 @RequiredArgsConstructor
 public class EcommerceServiceImpl implements EcommerceService {
+
   private static final Gson GSON = new GsonBuilder().create();
   private final WxPayService payService;
 
@@ -41,5 +54,297 @@ public ApplymentsStatusResult queryApplyStatusByOutRequestNo(String outRequestNo
     return GSON.fromJson(result, ApplymentsStatusResult.class);
   }
 
+  @Override
+  public TransactionsResult combine(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException {
+    String url = this.payService.getPayBaseUrl() + tradeType.getCombineUrl();
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, TransactionsResult.class);
+  }
+
+  @Override
+  public  T combineTransactions(TradeTypeEnum tradeType, CombineTransactionsRequest request) throws WxPayException {
+    TransactionsResult result = this.combine(tradeType, request);
+    return result.getPayInfo(tradeType, request.getCombineAppid(),
+      request.getCombineMchid(), payService.getConfig().getPrivateKey());
+  }
+
+  @Override
+  public CombineTransactionsNotifyResult parseCombineNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
+    if(Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)){
+      throw new WxPayException("非法请求,头部信息验证失败");
+    }
+    NotifyResponse response = GSON.fromJson(notifyData, NotifyResponse.class);
+    NotifyResponse.Resource resource = response.getResource();
+    String cipherText = resource.getCiphertext();
+    String associatedData = resource.getAssociatedData();
+    String nonce = resource.getNonce();
+    String apiV3Key = this.payService.getConfig().getApiV3Key();
+    try {
+      String result = AesUtils.decryptToString(associatedData, nonce,cipherText, apiV3Key);
+      CombineTransactionsResult transactionsResult = GSON.fromJson(result, CombineTransactionsResult.class);
+
+      CombineTransactionsNotifyResult notifyResult = new CombineTransactionsNotifyResult();
+      notifyResult.setRawData(response);
+      notifyResult.setResult(transactionsResult);
+      return notifyResult;
+    } catch (GeneralSecurityException | IOException e) {
+      throw new WxPayException("解析报文异常!", e);
+    }
+  }
+
+  @Override
+  public CombineTransactionsResult queryCombineTransactions(String outTradeNo) throws WxPayException {
+    String url = String.format("%s/v3/combine-transactions/out-trade-no/%s", this.payService.getPayBaseUrl(), outTradeNo);
+    String response = this.payService.getV3(URI.create(url));
+    return GSON.fromJson(response, CombineTransactionsResult.class);
+  }
+
+  @Override
+  public TransactionsResult partner(TradeTypeEnum tradeType, PartnerTransactionsRequest request) throws WxPayException {
+    String url = this.payService.getPayBaseUrl() + tradeType.getPartnerUrl();
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, TransactionsResult.class);
+  }
+
+  @Override
+  public  T partnerTransactions(TradeTypeEnum tradeType, PartnerTransactionsRequest request) throws WxPayException {
+    TransactionsResult result = this.partner(tradeType, request);
+    return result.getPayInfo(tradeType, request.getSpAppid(),
+      request.getSpMchid(), payService.getConfig().getPrivateKey());
+  }
+
+  @Override
+  public PartnerTransactionsNotifyResult parsePartnerNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
+    if(Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)){
+      throw new WxPayException("非法请求,头部信息验证失败");
+    }
+    NotifyResponse response = GSON.fromJson(notifyData, NotifyResponse.class);
+    NotifyResponse.Resource resource = response.getResource();
+    String cipherText = resource.getCiphertext();
+    String associatedData = resource.getAssociatedData();
+    String nonce = resource.getNonce();
+    String apiV3Key = this.payService.getConfig().getApiV3Key();
+    try {
+      String result = AesUtils.decryptToString(associatedData, nonce,cipherText, apiV3Key);
+      PartnerTransactionsResult transactionsResult = GSON.fromJson(result, PartnerTransactionsResult.class);
+
+      PartnerTransactionsNotifyResult notifyResult = new PartnerTransactionsNotifyResult();
+      notifyResult.setRawData(response);
+      notifyResult.setResult(transactionsResult);
+      return notifyResult;
+    } catch (GeneralSecurityException | IOException e) {
+      throw new WxPayException("解析报文异常!", e);
+    }
+  }
+
+  @Override
+  public PartnerTransactionsResult queryPartnerTransactions(PartnerTransactionsQueryRequest request) throws WxPayException {
+    String url = String.format("%s/v3/pay/partner/transactions/out-trade-no/%s", this.payService.getPayBaseUrl(), request.getOutTradeNo());
+    if (Objects.isNull(request.getOutTradeNo())) {
+      url = String.format("%s/v3/pay/partner/transactions/id/%s", this.payService.getPayBaseUrl(), request.getTransactionId());
+    }
+    String query = String.format("?sp_mchid=%s&sub_mchid=%s", request.getSpMchid(), request.getSubMchid());
+    URI uri = URI.create(url + query);
+    String response = this.payService.getV3(uri);
+    return GSON.fromJson(response, PartnerTransactionsResult.class);
+  }
+
+  @Override
+  public FundBalanceResult spNowBalance(SpAccountTypeEnum accountType) throws WxPayException {
+    String url = String.format("%s/v3/merchant/fund/balance/%s", this.payService.getPayBaseUrl(), accountType);
+    URI uri = URI.create(url);
+    String response = this.payService.getV3(uri);
+    return GSON.fromJson(response, FundBalanceResult.class);
+  }
+
+  @Override
+  public FundBalanceResult spDayEndBalance(SpAccountTypeEnum accountType, String date) throws WxPayException {
+    String url = String.format("%s/v3/merchant/fund/dayendbalance/%s?date=%s", this.payService.getPayBaseUrl(), accountType, date);
+    URI uri = URI.create(url);
+    String response = this.payService.getV3(uri);
+    return GSON.fromJson(response, FundBalanceResult.class);
+  }
+
+  @Override
+  public FundBalanceResult subNowBalance(String subMchid) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/fund/balance/%s", this.payService.getPayBaseUrl(), subMchid);
+    URI uri = URI.create(url);
+    String response = this.payService.getV3(uri);
+    return GSON.fromJson(response, FundBalanceResult.class);
+  }
+
+  @Override
+  public FundBalanceResult subDayEndBalance(String subMchid, String date) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/fund/enddaybalance/%s?date=%s", this.payService.getPayBaseUrl(), subMchid, date);
+    URI uri = URI.create(url);
+    String response = this.payService.getV3(uri);
+    return GSON.fromJson(response, FundBalanceResult.class);
+  }
+
+  @Override
+  public ProfitSharingResult profitSharing(ProfitSharingRequest request) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/profitsharing/orders", this.payService.getPayBaseUrl());
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, ProfitSharingResult.class);
+  }
+
+  @Override
+  public ProfitSharingResult queryProfitSharing(ProfitSharingQueryRequest request) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/profitsharing/orders?sub_mchid=%s&transaction_id=%s&out_order_no=%s",
+      this.payService.getPayBaseUrl(), request.getSubMchid(), request.getTransactionId(), request.getOutOrderNo());
+    String response = this.payService.getV3(URI.create(url));
+    return GSON.fromJson(response, ProfitSharingResult.class);
+  }
+
+  @Override
+  public ReturnOrdersResult returnOrders(ReturnOrdersRequest request) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/profitsharing/returnorders", this.payService.getPayBaseUrl());
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, ReturnOrdersResult.class);
+  }
+
+  @Override
+  public ProfitSharingResult finishOrder(FinishOrderRequest request) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/profitsharing/finish-order", this.payService.getPayBaseUrl());
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, ProfitSharingResult.class);
+  }
+
+  @Override
+  public RefundsResult refunds(RefundsRequest request) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/refunds/apply", this.payService.getPayBaseUrl());
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, RefundsResult.class);
+  }
+
+  @Override
+  public RefundQueryResult queryRefundByRefundId(String subMchid, String refundId) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/refunds/id/%s?sub_mchid=%s", this.payService.getPayBaseUrl(), refundId, subMchid);
+    String response = this.payService.getV3(URI.create(url));
+    return GSON.fromJson(response, RefundQueryResult.class);
+  }
+
+  @Override
+  public RefundQueryResult queryRefundByOutRefundNo(String subMchid, String outRefundNo) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/refunds/out-refund-no/%s?sub_mchid=%s", this.payService.getPayBaseUrl(), outRefundNo, subMchid);
+    String response = this.payService.getV3(URI.create(url));
+    return GSON.fromJson(response, RefundQueryResult.class);
+  }
+
+  @Override
+  public RefundNotifyResult parseRefundNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
+    if(Objects.nonNull(header) && !this.verifyNotifySign(header, notifyData)){
+      throw new WxPayException("非法请求,头部信息验证失败");
+    }
+    NotifyResponse response = GSON.fromJson(notifyData, NotifyResponse.class);
+    NotifyResponse.Resource resource = response.getResource();
+    String cipherText = resource.getCiphertext();
+    String associatedData = resource.getAssociatedData();
+    String nonce = resource.getNonce();
+    String apiV3Key = this.payService.getConfig().getApiV3Key();
+    try {
+      String result = AesUtils.decryptToString(associatedData, nonce,cipherText, apiV3Key);
+      RefundNotifyResult notifyResult = GSON.fromJson(result, RefundNotifyResult.class);
+      notifyResult.setRawData(response);
+      return notifyResult;
+    } catch (GeneralSecurityException | IOException e) {
+      throw new WxPayException("解析报文异常!", e);
+    }
+  }
+
+  @Override
+  public SubWithdrawResult subWithdraw(SubWithdrawRequest request) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/fund/withdraw", this.payService.getPayBaseUrl());
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, SubWithdrawResult.class);
+  }
+
+  @Override
+  public SpWithdrawResult spWithdraw(SpWithdrawRequest request) throws WxPayException {
+    String url = String.format("%s/v3/merchant/fund/withdraw", this.payService.getPayBaseUrl());
+    String response = this.payService.postV3(url, GSON.toJson(request));
+    return GSON.fromJson(response, SpWithdrawResult.class);
+  }
+
+  @Override
+  public SubWithdrawStatusResult querySubWithdrawByOutRequestNo(String subMchid, String outRequestNo) throws WxPayException {
+    String url = String.format("%s/v3/ecommerce/fund/withdraw/out-request-no/%s?sub_mchid=%s", this.payService.getPayBaseUrl(), outRequestNo, subMchid);
+    String response = this.payService.getV3(URI.create(url));
+    return GSON.fromJson(response, SubWithdrawStatusResult.class);
+  }
+
+  @Override
+  public SpWithdrawStatusResult querySpWithdrawByOutRequestNo(String outRequestNo) throws WxPayException {
+    String url = String.format("%s/v3/merchant/fund/withdraw/out-request-no/%s", this.payService.getPayBaseUrl(), outRequestNo);
+    String response = this.payService.getV3(URI.create(url));
+    return GSON.fromJson(response, SpWithdrawStatusResult.class);
+  }
+
+  @Override
+  public void modifySettlement(String subMchid, SettlementRequest request) throws WxPayException {
+    String url = String.format("%s/v3/apply4sub/sub_merchants/%s/modify-settlement", this.payService.getPayBaseUrl(), subMchid);
+    RsaCryptoUtil.encryptFields(request, this.payService.getConfig().getVerifier().getValidCertificate());
+    this.payService.postV3WithWechatpaySerial(url, GSON.toJson(request));
+  }
+
+  @Override
+  public SettlementResult querySettlement(String subMchid) throws WxPayException {
+    String url = String.format("%s/v3/apply4sub/sub_merchants/%s/settlement", this.payService.getPayBaseUrl(), subMchid);
+    String response = this.payService.getV3(URI.create(url));
+    return GSON.fromJson(response, SettlementResult.class);
+  }
+
+  @Override
+  public TradeBillResult applyBill(TradeBillRequest request) throws WxPayException {
+    String url = String.format("%s/v3/bill/tradebill?%s", this.payService.getPayBaseUrl(), this.parseURLPair(request));
+    String response = this.payService.getV3(URI.create(url));
+    return GSON.fromJson(response, TradeBillResult.class);
+  }
 
-}
+  @Override
+  public FundBillResult applyFundBill(FundBillTypeEnum billType, FundBillRequest request) throws WxPayException {
+    String url = String.format(billType.getUrl(), this.payService.getPayBaseUrl(), this.parseURLPair(request));
+    String response = this.payService.getV3(URI.create(url));
+    return GSON.fromJson(response, FundBillResult.class);
+  }
+
+  @Override
+  public InputStream downloadBill(String url) throws WxPayException {
+    return this.payService.downloadV3(URI.create(url));
+  }
+
+  /**
+   * 校验通知签名
+   * @param header 通知头信息
+   * @param data 通知数据
+   * @return true:校验通过 false:校验不通过
+   */
+  private boolean verifyNotifySign(SignatureHeader header, String data) {
+    String beforeSign = String.format("%s\n%s\n%s\n",
+      header.getTimeStamp(),
+      header.getNonce(),
+      data);
+    return payService.getConfig().getVerifier().verify(header.getSerialNo(),
+      beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned());
+  }
+
+  /**
+   * 对象拼接到url
+   * @param o 转换对象
+   * @return  拼接好的string
+   */
+  private String parseURLPair(Object o) {
+    Map map = new BeanMap(o);
+    Set> set = map.entrySet();
+    Iterator> it = set.iterator();
+    StringBuilder sb = new StringBuilder();
+    while (it.hasNext()) {
+      Map.Entry e = it.next();
+      if ( !"class".equals(e.getKey()) && e.getValue() != null)
+        sb.append(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, String.valueOf(e.getKey()))).append("=").append(e.getValue()).append("&");
+    }
+    return sb.deleteCharAt(sb.length() - 1).toString();
+  }
+
+
+  }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EntPayServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EntPayServiceImpl.java
index 5e768bef99..db464936c7 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EntPayServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/EntPayServiceImpl.java
@@ -1,6 +1,7 @@
 package com.github.binarywang.wxpay.service.impl;
 
 import com.github.binarywang.wxpay.bean.entpay.*;
+import com.github.binarywang.wxpay.bean.entwxpay.EntWxEmpPayRequest;
 import com.github.binarywang.wxpay.bean.request.WxPayDefaultRequest;
 import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
 import com.github.binarywang.wxpay.constant.WxPayConstants;
@@ -8,7 +9,7 @@
 import com.github.binarywang.wxpay.service.EntPayService;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.util.SignUtils;
-import org.apache.commons.codec.binary.Base64;
+import lombok.RequiredArgsConstructor;
 import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openssl.PEMParser;
@@ -22,6 +23,7 @@
 import java.nio.file.Path;
 import java.security.PublicKey;
 import java.security.Security;
+import java.util.Base64;
 
 /**
  * 
@@ -30,17 +32,9 @@
  *
  * @author Binary Wang
  */
+@RequiredArgsConstructor
 public class EntPayServiceImpl implements EntPayService {
-  private WxPayService payService;
-
-  /**
-   * Instantiates a new Ent pay service.
-   *
-   * @param payService the pay service
-   */
-  public EntPayServiceImpl(WxPayService payService) {
-    this.payService = payService;
-  }
+  private final WxPayService payService;
 
   @Override
   public EntPayResult entPay(EntPayRequest request) throws WxPayException {
@@ -158,6 +152,24 @@ public EntPayRedpackQueryResult queryEnterpriseRedpack(EntPayRedpackQueryRequest
     return result;
   }
 
+  @Override
+  public EntPayResult toEmpPay(EntWxEmpPayRequest request) throws WxPayException {
+    //企业微信签名,需要在请求签名之前
+    request.setNonceStr(String.valueOf(System.currentTimeMillis()));
+    request.setWorkWxSign(SignUtils.createEntSign(request.getAmount(), request.getAppid(), request.getDescription(),
+      request.getMchId(), request.getNonceStr(), request.getOpenid(), request.getPartnerTradeNo(),
+      request.getWwMsgType(), payService.getConfig().getEntPayKey(), WxPayConstants.SignType.MD5));
+
+    request.checkAndSign(this.payService.getConfig());
+
+    String url = this.payService.getPayBaseUrl() + "/mmpaymkttransfers/promotion/paywwsptrans2pocket";
+    String responseContent = this.payService.post(url, request.toXML(), true);
+    final EntPayResult result = BaseWxPayResult.fromXML(responseContent, EntPayResult.class);
+
+    result.checkResult(this.payService, request.getSignType(), true);
+    return result;
+  }
+
   private String encryptRSA(File publicKeyFile, String srcString) throws WxPayException {
     try {
       Security.addProvider(new BouncyCastleProvider());
@@ -168,7 +180,7 @@ private String encryptRSA(File publicKeyFile, String srcString) throws WxPayExce
 
         cipher.init(Cipher.ENCRYPT_MODE, publicKey);
         byte[] encrypt = cipher.doFinal(srcString.getBytes(StandardCharsets.UTF_8));
-        return Base64.encodeBase64String(encrypt);
+        return Base64.getEncoder().encodeToString(encrypt);
       }
     } catch (Exception e) {
       throw new WxPayException("加密出错", e);
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImpl.java
index 863b706a28..811d61f6b5 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/MerchantMediaServiceImpl.java
@@ -9,10 +9,7 @@
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.codec.digest.DigestUtils;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
+import java.io.*;
 import java.net.URI;
 
 /**
@@ -41,4 +38,24 @@ public ImageUploadResult imageUploadV3(File imageFile) throws WxPayException,IOE
     }
   }
 
+  @Override
+  public ImageUploadResult imageUploadV3(InputStream inputStream, String fileName) throws WxPayException, IOException {
+    String url = String.format("%s/v3/merchant/media/upload", this.payService.getPayBaseUrl());
+    try(ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
+      byte[] buffer = new byte[2048];
+      int len;
+      while ((len = inputStream.read(buffer)) > -1) {
+        bos.write(buffer, 0, len);
+      }
+      bos.flush();
+      byte[] data = bos.toByteArray();
+      String sha256 = DigestUtils.sha256Hex(data);
+      WechatPayUploadHttpPost request = new WechatPayUploadHttpPost.Builder(URI.create(url))
+        .withImage(fileName, sha256, new ByteArrayInputStream(data))
+        .build();
+      String result = this.payService.postV3(url, request);
+      return ImageUploadResult.fromJson(result);
+    }
+  }
+
 }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java
index 7fa7efa58b..2f4dd76964 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/PayScoreServiceImpl.java
@@ -1,6 +1,19 @@
 package com.github.binarywang.wxpay.service.impl;
 
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.security.GeneralSecurityException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.utils.URIBuilder;
+
+import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
 import com.github.binarywang.wxpay.bean.payscore.PayScoreNotifyData;
+import com.github.binarywang.wxpay.bean.payscore.UserAuthorizationStatusNotifyResult;
 import com.github.binarywang.wxpay.bean.payscore.WxPayScoreRequest;
 import com.github.binarywang.wxpay.bean.payscore.WxPayScoreResult;
 import com.github.binarywang.wxpay.config.WxPayConfig;
@@ -8,16 +21,11 @@
 import com.github.binarywang.wxpay.service.PayScoreService;
 import com.github.binarywang.wxpay.service.WxPayService;
 import com.github.binarywang.wxpay.v3.util.AesUtils;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
 import lombok.RequiredArgsConstructor;
 import me.chanjar.weixin.common.util.json.WxGsonBuilder;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.http.client.utils.URIBuilder;
-
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.security.GeneralSecurityException;
-import java.util.HashMap;
-import java.util.Map;
 
 /**
  * @author doger.wang
@@ -25,11 +33,113 @@
  */
 @RequiredArgsConstructor
 public class PayScoreServiceImpl implements PayScoreService {
+	
+  private static final Gson GSON = new GsonBuilder().create();
   private final WxPayService payService;
 
+  @Override
+  public WxPayScoreResult permissions(WxPayScoreRequest request) throws WxPayException {
+    WxPayConfig config = this.payService.getConfig();
+    String url = this.payService.getPayBaseUrl() + "/v3/payscore/permissions";
+    request.setAppid(config.getAppId());
+    request.setServiceId(config.getServiceId());
+    String permissionNotifyUrl = config.getPayScorePermissionNotifyUrl();
+    if (StringUtils.isBlank(permissionNotifyUrl)){
+      throw new WxPayException("授权回调地址未配置");
+    }
+    String authorizationCode = request.getAuthorizationCode();
+    if (StringUtils.isBlank(authorizationCode)){
+      throw new WxPayException("authorizationCode不允许为空");
+    }
+    request.setNotifyUrl(permissionNotifyUrl);
+    String result = this.payService.postV3(url, request.toJson());
+   return WxPayScoreResult.fromJson(result);
+
+  }
+
+  @Override
+  public WxPayScoreResult permissionsQueryByAuthorizationCode(String authorizationCode) throws WxPayException {
+    WxPayConfig config = this.payService.getConfig();
+    if (StringUtils.isBlank(authorizationCode)){
+      throw new WxPayException("authorizationCode不允许为空");
+    }
+    String url = String.format("%s/v3/payscore/permissions/authorization-code/%s", this.payService.getPayBaseUrl(), authorizationCode);
+    URIBuilder uriBuilder;
+    try {
+      uriBuilder = new URIBuilder(url);
+    } catch (URISyntaxException e) {
+      throw new WxPayException("未知异常!", e);
+    }
+
+    uriBuilder.setParameter("service_id", config.getServiceId());
+    try {
+      String result = payService.getV3(uriBuilder.build());
+      return WxPayScoreResult.fromJson(result);
+    } catch (URISyntaxException e) {
+      throw new WxPayException("未知异常!", e);
+    }
+
+  }
+
+  @Override
+  public WxPayScoreResult permissionsTerminateByAuthorizationCode(String authorizationCode,String reason) throws WxPayException {
+    WxPayConfig config = this.payService.getConfig();
+    if (StringUtils.isBlank(authorizationCode)){
+      throw new WxPayException("authorizationCode不允许为空");
+    }
+    String url = String.format("%s/v3/payscore/permissions/authorization-code/%s/terminate", this.payService.getPayBaseUrl(), authorizationCode);
+    Map map = new HashMap<>(4);
+    map.put("service_id", config.getServiceId());
+    map.put("reason", reason);
+    String result = payService.postV3(url, WxGsonBuilder.create().toJson(map));
+    return WxPayScoreResult.fromJson(result);
+
+  }
+
+  @Override
+  public WxPayScoreResult permissionsQueryByOpenId(String openId) throws WxPayException {
+    WxPayConfig config = this.payService.getConfig();
+    if (StringUtils.isBlank(openId)){
+      throw new WxPayException("openId不允许为空");
+    }
+    String url = String.format("%s/v3/payscore/permissions/openid/%s", this.payService.getPayBaseUrl(), openId);
+    URIBuilder uriBuilder;
+    try {
+      uriBuilder = new URIBuilder(url);
+    } catch (URISyntaxException e) {
+      throw new WxPayException("未知异常!", e);
+    }
+
+    uriBuilder.setParameter("appid", config.getAppId());
+    uriBuilder.setParameter("service_id", config.getServiceId());
+    try {
+      String result = payService.getV3(uriBuilder.build());
+      return WxPayScoreResult.fromJson(result);
+    } catch (URISyntaxException e) {
+      throw new WxPayException("未知异常!", e);
+    }
+
+  }
+
+  @Override
+  public WxPayScoreResult permissionsTerminateByOpenId(String openId, String reason) throws WxPayException {
+    WxPayConfig config = this.payService.getConfig();
+    if (StringUtils.isBlank(openId)){
+      throw new WxPayException("openId不允许为空");
+    }
+    String url = String.format("%s/v3/payscore/permissions/openid/%s/terminate", this.payService.getPayBaseUrl(), openId);
+    Map map = new HashMap<>(4);
+    map.put("service_id", config.getServiceId());
+    map.put("appid", config.getAppId());
+    map.put("reason", reason);
+    String result = payService.postV3(url, WxGsonBuilder.create().toJson(map));
+    return WxPayScoreResult.fromJson(result);
+
+  }
+
   @Override
   public WxPayScoreResult createServiceOrder(WxPayScoreRequest request) throws WxPayException {
-    boolean needUserConfirm = request.isNeedUserConfirm();
+    boolean needUserConfirm = request.getNeedUserConfirm();
     WxPayConfig config = this.payService.getConfig();
     String url = this.payService.getPayBaseUrl() + "/v3/payscore/serviceorder";
     request.setAppid(config.getAppId());
@@ -147,10 +257,31 @@ public WxPayScoreResult syncServiceOrder(WxPayScoreRequest request) throws WxPay
     String result = payService.postV3(url, request.toJson());
     return WxPayScoreResult.fromJson(result);
   }
+  
+  @Override
+  public UserAuthorizationStatusNotifyResult parseUserAuthorizationStatusNotifyResult(String notifyData, SignatureHeader header) throws WxPayException {
+    PayScoreNotifyData response = parseNotifyData(notifyData,header);
+    PayScoreNotifyData.Resource resource = response.getResource();
+    String cipherText = resource.getCipherText();
+    String associatedData = resource.getAssociatedData();
+    String nonce = resource.getNonce();
+    String apiV3Key = this.payService.getConfig().getApiV3Key();
+    try {
+      String result = AesUtils.decryptToString(associatedData, nonce,cipherText, apiV3Key);
+      UserAuthorizationStatusNotifyResult notifyResult = GSON.fromJson(result, UserAuthorizationStatusNotifyResult.class);
+      notifyResult.setRawData(response);
+      return notifyResult;
+    } catch (GeneralSecurityException | IOException e) {
+      throw new WxPayException("解析报文异常!", e);
+    }
+  }
 
   @Override
-  public PayScoreNotifyData parseNotifyData(String data) {
-    return WxGsonBuilder.create().fromJson(data, PayScoreNotifyData.class);
+  public PayScoreNotifyData parseNotifyData(String data,SignatureHeader header) throws WxPayException {
+	if(Objects.nonNull(header) && !this.verifyNotifySign(header, data)){
+	  throw new WxPayException("非法请求,头部信息验证失败");
+	}
+    return GSON.fromJson(data, PayScoreNotifyData.class);
   }
 
   @Override
@@ -166,4 +297,19 @@ public WxPayScoreResult decryptNotifyDataResource(PayScoreNotifyData data) throw
       throw new WxPayException("解析报文异常!", e);
     }
   }
+  
+  /**
+   * 校验通知签名
+   * @param header 通知头信息
+   * @param data 通知数据
+   * @return true:校验通过 false:校验不通过
+   */
+  private boolean verifyNotifySign(SignatureHeader header, String data) {
+    String beforeSign = String.format("%s\n%s\n%s\n",
+      header.getTimeStamp(),
+      header.getNonce(),
+      data);
+    return payService.getConfig().getVerifier().verify(header.getSerialNo(),
+      beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned());
+  }
 }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
index c037134732..6b9adf289c 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java
@@ -3,7 +3,6 @@
 import com.github.binarywang.wxpay.bean.WxPayApiData;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.google.gson.JsonObject;
-import jodd.util.Base64;
 import me.chanjar.weixin.common.util.json.GsonParser;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.http.HttpEntity;
@@ -27,8 +26,10 @@
 import org.apache.http.util.EntityUtils;
 
 import javax.net.ssl.SSLContext;
+import java.io.InputStream;
 import java.net.URI;
 import java.nio.charset.StandardCharsets;
+import java.util.Base64;
 
 /**
  * 
@@ -48,7 +49,7 @@ public byte[] postForBytes(String url, String requestStr, boolean useKey) throws
       try (CloseableHttpClient httpClient = httpClientBuilder.build()) {
         try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
           final byte[] bytes = EntityUtils.toByteArray(response.getEntity());
-          final String responseData = Base64.encodeToString(bytes);
+          final String responseData = Base64.getEncoder().encodeToString(bytes);
           this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据(Base64编码后)】:{}", url, requestStr, responseData);
           wxApiData.set(new WxPayApiData(url, requestStr, responseData, null));
           return bytes;
@@ -207,6 +208,31 @@ public String getV3(URI url) throws WxPayException {
     }
   }
 
+  @Override
+  public InputStream downloadV3(URI url) throws WxPayException {
+    CloseableHttpClient httpClient = this.createApiV3HttpClient();
+    HttpGet httpGet = new HttpGet(url);
+    httpGet.addHeader("Accept", ContentType.WILDCARD.getMimeType());
+    try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
+      //v3已经改为通过状态码判断200 204 成功
+      int statusCode = response.getStatusLine().getStatusCode();
+      if (HttpStatus.SC_OK == statusCode || HttpStatus.SC_NO_CONTENT == statusCode) {
+        this.log.info("\n【请求地址】:{}\n", url);
+        return response.getEntity().getContent();
+      } else {
+        //有错误提示信息返回
+        String responseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
+        JsonObject jsonObject = GsonParser.parse(responseString);
+        throw new WxPayException(jsonObject.get("message").getAsString());
+      }
+    } catch (Exception e) {
+      this.log.error("\n【请求地址】:{}\n【异常信息】:{}", url, e.getMessage());
+      throw new WxPayException(e.getMessage(), e);
+    } finally {
+      httpGet.releaseConnection();
+    }
+  }
+
   private CloseableHttpClient createApiV3HttpClient() throws WxPayException {
     CloseableHttpClient apiV3HttpClient = this.getConfig().getApiV3HttpClient();
     if (null == apiV3HttpClient) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
index b7ef11695e..129c60a29b 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceJoddHttpImpl.java
@@ -1,11 +1,5 @@
 package com.github.binarywang.wxpay.service.impl;
 
-import java.net.URI;
-import java.nio.charset.StandardCharsets;
-import javax.net.ssl.SSLContext;
-
-import org.apache.commons.lang3.StringUtils;
-
 import com.github.binarywang.wxpay.bean.WxPayApiData;
 import com.github.binarywang.wxpay.exception.WxPayException;
 import jodd.http.HttpConnectionProvider;
@@ -15,9 +9,15 @@
 import jodd.http.ProxyInfo.ProxyType;
 import jodd.http.net.SSLSocketHttpConnectionProvider;
 import jodd.http.net.SocketHttpConnectionProvider;
-import jodd.util.Base64;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.http.client.methods.HttpPost;
 
+import javax.net.ssl.SSLContext;
+import java.io.InputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
 /**
  * 微信支付请求实现类,jodd-http实现.
  * Created by Binary Wang on 2016/7/28.
@@ -30,7 +30,7 @@ public byte[] postForBytes(String url, String requestStr, boolean useKey) throws
     try {
       HttpRequest request = this.buildHttpRequest(url, requestStr, useKey);
       byte[] responseBytes = request.send().bodyBytes();
-      final String responseString = Base64.encodeToString(responseBytes);
+      final String responseString = Base64.getEncoder().encodeToString(responseBytes);
       this.log.info("\n【请求地址】:{}\n【请求数据】:{}\n【响应数据(Base64编码后)】:{}", url, requestStr, responseString);
       if (this.getConfig().isIfSaveApiData()) {
         wxApiData.set(new WxPayApiData(url, requestStr, responseString, null));
@@ -81,6 +81,11 @@ public String getV3(URI url) throws WxPayException {
     return null;
   }
 
+  @Override
+  public InputStream downloadV3(URI url) throws WxPayException {
+    return null;
+  }
+
   private HttpRequest buildHttpRequest(String url, String requestStr, boolean useKey) throws WxPayException {
     HttpRequest request = HttpRequest
       .post(url)
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/SignUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/SignUtils.java
index 0ce39a7312..9e005a813e 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/SignUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/util/SignUtils.java
@@ -127,6 +127,30 @@ public static String createEntSign(String actName, String mchBillNo, String mchI
     sortedMap.put("total_amount", totalAmount + "");
     sortedMap.put("wxappid", wxAppId);
 
+    return toSignBuilder(sortedMap, signKey, signType);
+  }
+
+  /**
+   * 企业微信签名
+   * @param signType md5 目前接口要求使用的加密类型
+   */
+  public static String createEntSign(Integer totalAmount, String appId, String description, String mchId,
+                                     String nonceStr, String openid, String partnerTradeNo, String wwMsgType,
+                                     String signKey, String signType) {
+    Map sortedMap = new HashMap<>(8);
+    sortedMap.put("amount", String.valueOf(totalAmount));
+    sortedMap.put("appid", appId);
+    sortedMap.put("desc", description);
+    sortedMap.put("mch_id", mchId);
+    sortedMap.put("nonce_str", nonceStr);
+    sortedMap.put("openid", openid);
+    sortedMap.put("partner_trade_no", partnerTradeNo);
+    sortedMap.put("ww_msg_type", wwMsgType);
+
+    return toSignBuilder(sortedMap, signKey, signType);
+  }
+
+  private static String toSignBuilder(Map sortedMap, String signKey, String signType) {
     Iterator> iterator = new TreeMap<>(sortedMap).entrySet().iterator();
     StringBuilder toSign = new StringBuilder();
     while (iterator.hasNext()) {
@@ -148,7 +172,6 @@ public static String createEntSign(String actName, String mchBillNo, String mchI
     } else {
       return DigestUtils.md5Hex(toSign.toString()).toUpperCase();
     }
-
   }
 
   /**
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java
index a490108146..e93e3cd78b 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java
@@ -10,6 +10,7 @@
 import lombok.Getter;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.impl.client.CloseableHttpClient;
@@ -94,7 +95,7 @@ public AutoUpdateCertificatesVerifier(Credentials credentials, byte[] apiV3Key,
       autoUpdateCert();
       instant = Instant.now();
     } catch (IOException | GeneralSecurityException e) {
-      throw new RuntimeException(e);
+      throw new WxRuntimeException(e);
     }
   }
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/CertificatesVerifier.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/CertificatesVerifier.java
index 7239d9e64d..9ca8b5b836 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/CertificatesVerifier.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/CertificatesVerifier.java
@@ -1,5 +1,7 @@
 package com.github.binarywang.wxpay.v3.auth;
 
+import me.chanjar.weixin.common.error.WxRuntimeException;
+
 import java.math.BigInteger;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
@@ -30,11 +32,11 @@ private boolean verify(X509Certificate certificate, byte[] message, String signa
       sign.update(message);
       return sign.verify(Base64.getDecoder().decode(signature));
     } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
+      throw new WxRuntimeException("当前Java环境不支持SHA256withRSA", e);
     } catch (SignatureException e) {
-      throw new RuntimeException("签名验证过程发生了错误", e);
+      throw new WxRuntimeException("签名验证过程发生了错误", e);
     } catch (InvalidKeyException e) {
-      throw new RuntimeException("无效的证书", e);
+      throw new WxRuntimeException("无效的证书", e);
     }
   }
 
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PrivateKeySigner.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PrivateKeySigner.java
index 37ec51cf58..183e46e260 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PrivateKeySigner.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/PrivateKeySigner.java
@@ -1,5 +1,7 @@
 package com.github.binarywang.wxpay.v3.auth;
 
+import me.chanjar.weixin.common.error.WxRuntimeException;
+
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
@@ -27,11 +29,11 @@ public SignatureResult sign(byte[] message) {
       return new SignatureResult(
           Base64.getEncoder().encodeToString(sign.sign()), certificateSerialNumber);
     } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
+      throw new WxRuntimeException("当前Java环境不支持SHA256withRSA", e);
     } catch (SignatureException e) {
-      throw new RuntimeException("签名计算失败", e);
+      throw new WxRuntimeException("签名计算失败", e);
     } catch (InvalidKeyException e) {
-      throw new RuntimeException("无效的私钥", e);
+      throw new WxRuntimeException("无效的私钥", e);
     }
   }
 }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java
index 9b9b0ad4de..e14d8b5b16 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayValidator.java
@@ -1,15 +1,16 @@
 package com.github.binarywang.wxpay.v3.auth;
 
 
-import java.io.IOException;
-
 import com.github.binarywang.wxpay.v3.Validator;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.http.Header;
 import org.apache.http.HttpEntity;
 import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.entity.ContentType;
 import org.apache.http.util.EntityUtils;
 
+import java.io.IOException;
+
 @Slf4j
 public class WxPayValidator implements Validator {
   private Verifier verifier;
@@ -20,6 +21,9 @@ public WxPayValidator(Verifier verifier) {
 
   @Override
   public final boolean validate(CloseableHttpResponse response) throws IOException {
+    if (!ContentType.APPLICATION_JSON.getMimeType().equals(ContentType.parse(String.valueOf(response.getFirstHeader("Content-Type").getValue())).getMimeType())) {
+      return true;
+    }
     Header serialNo = response.getFirstHeader("Wechatpay-Serial");
     Header sign = response.getFirstHeader("Wechatpay-Signature");
     Header timestamp = response.getFirstHeader("Wechatpay-TimeStamp");
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java
index 4030965ebe..2c8c40252f 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/AesUtils.java
@@ -4,6 +4,11 @@
 import com.google.common.io.BaseEncoding;
 import org.apache.commons.lang3.StringUtils;
 
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.InvalidAlgorithmParameterException;
@@ -13,11 +18,6 @@
 import java.util.Map;
 import java.util.SortedMap;
 import java.util.TreeMap;
-import javax.crypto.Cipher;
-import javax.crypto.Mac;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.spec.GCMParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
 
 public class AesUtils {
 
@@ -32,6 +32,31 @@ public AesUtils(byte[] key) {
     this.aesKey = key;
   }
 
+  public static byte[] decryptToByte(byte[] nonce, byte[] cipherData, byte[] key)
+    throws GeneralSecurityException {
+    return decryptToByte(null, nonce, cipherData, key);
+  }
+
+  public static byte[] decryptToByte(byte[] associatedData, byte[] nonce, byte[] cipherData, byte[] key)
+    throws GeneralSecurityException {
+    try {
+      Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
+
+      SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
+      GCMParameterSpec spec = new GCMParameterSpec(TAG_LENGTH_BIT, nonce);
+
+      cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, spec);
+      if (associatedData != null) {
+        cipher.updateAAD(associatedData);
+      }
+      return cipher.doFinal(cipherData);
+    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+      throw new IllegalStateException(e);
+    } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+      throw new IllegalArgumentException(e);
+    }
+  }
+
   public String decryptToString(byte[] associatedData, byte[] nonce, String ciphertext)
       throws GeneralSecurityException, IOException {
     try {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/PemUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/PemUtils.java
index bf4d2657b5..c039ccb636 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/PemUtils.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/PemUtils.java
@@ -1,5 +1,7 @@
 package com.github.binarywang.wxpay.v3.util;
 
+import me.chanjar.weixin.common.error.WxRuntimeException;
+
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -35,11 +37,11 @@ public static PrivateKey loadPrivateKey(InputStream inputStream) {
       return kf.generatePrivate(
           new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
     } catch (NoSuchAlgorithmException e) {
-      throw new RuntimeException("当前Java环境不支持RSA", e);
+      throw new WxRuntimeException("当前Java环境不支持RSA", e);
     } catch (InvalidKeySpecException e) {
-      throw new RuntimeException("无效的密钥格式");
+      throw new WxRuntimeException("无效的密钥格式");
     } catch (IOException e) {
-      throw new RuntimeException("无效的密钥");
+      throw new WxRuntimeException("无效的密钥");
     }
   }
 
@@ -50,11 +52,11 @@ public static X509Certificate loadCertificate(InputStream inputStream) {
       cert.checkValidity();
       return cert;
     } catch (CertificateExpiredException e) {
-      throw new RuntimeException("证书已过期", e);
+      throw new WxRuntimeException("证书已过期", e);
     } catch (CertificateNotYetValidException e) {
-      throw new RuntimeException("证书尚未生效", e);
+      throw new WxRuntimeException("证书尚未生效", e);
     } catch (CertificateException e) {
-      throw new RuntimeException("无效的证书", e);
+      throw new WxRuntimeException("无效的证书", e);
     }
   }
 }
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java
index d88c67e419..287ac11fcf 100644
--- a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/RsaCryptoUtil.java
@@ -2,6 +2,7 @@
 
 import com.github.binarywang.wxpay.exception.WxPayException;
 import com.github.binarywang.wxpay.v3.SpecEncrypt;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 
 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
@@ -70,7 +71,7 @@ public static String encryptOAEP(String message, X509Certificate certificate)
       byte[] ciphertext = cipher.doFinal(data);
       return Base64.getEncoder().encodeToString(ciphertext);
     } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
-      throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
+      throw new WxRuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
     } catch (InvalidKeyException e) {
       throw new IllegalArgumentException("无效的证书", e);
     } catch (IllegalBlockSizeException | BadPaddingException e) {
@@ -87,7 +88,7 @@ public static String decryptOAEP(String ciphertext, PrivateKey privateKey)
       byte[] data = Base64.getDecoder().decode(ciphertext);
       return new String(cipher.doFinal(data), StandardCharsets.UTF_8);
     } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
-      throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
+      throw new WxRuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
     } catch (InvalidKeyException e) {
       throw new IllegalArgumentException("无效的私钥", e);
     } catch (BadPaddingException | IllegalBlockSizeException e) {
diff --git a/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java
new file mode 100644
index 0000000000..fff68ce280
--- /dev/null
+++ b/weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/util/SignUtils.java
@@ -0,0 +1,50 @@
+package com.github.binarywang.wxpay.v3.util;
+
+import me.chanjar.weixin.common.error.WxRuntimeException;
+
+import java.security.*;
+import java.util.Base64;
+import java.util.Random;
+
+public class SignUtils {
+
+  public static String sign(String string, PrivateKey privateKey) {
+    try {
+      Signature sign = Signature.getInstance("SHA256withRSA");
+      sign.initSign(privateKey);
+      sign.update(string.getBytes());
+
+      return Base64.getEncoder().encodeToString(sign.sign());
+    } catch (NoSuchAlgorithmException e) {
+      throw new WxRuntimeException("当前Java环境不支持SHA256withRSA", e);
+    } catch (SignatureException e) {
+      throw new WxRuntimeException("签名计算失败", e);
+    } catch (InvalidKeyException e) {
+      throw new WxRuntimeException("无效的私钥", e);
+    }
+  }
+
+  /**
+   * 随机生成32位字符串.
+   */
+  public static String genRandomStr() {
+    return genRandomStr(32);
+  }
+
+  /**
+   * 生成随机字符串
+   *
+   * @param length 字符串长度
+   * @return
+   */
+  public static String genRandomStr(int length) {
+    String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+    Random random = new Random();
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < length; i++) {
+      int number = random.nextInt(base.length());
+      sb.append(base.charAt(number));
+    }
+    return sb.toString();
+  }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java
index fb46c58a4d..8b5a621b89 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java
@@ -31,4 +31,10 @@ public void testInitSSLContext() throws Exception {
     this.testInitSSLContext_classpath();
     this.testInitSSLContext_http();
   }
+
+  @Test
+  @SuppressWarnings("ResultOfMethodCallIgnored")
+  public void testHashCode() {
+    payConfig.hashCode();
+  }
 }
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImplTest.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImplTest.java
new file mode 100644
index 0000000000..b56084466b
--- /dev/null
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/service/impl/EcommerceServiceImplTest.java
@@ -0,0 +1,78 @@
+package com.github.binarywang.wxpay.service.impl;
+
+import com.github.binarywang.wxpay.bean.ecommerce.PartnerTransactionsQueryRequest;
+import com.github.binarywang.wxpay.bean.ecommerce.PartnerTransactionsResult;
+import com.github.binarywang.wxpay.bean.ecommerce.SignatureHeader;
+import com.github.binarywang.wxpay.exception.WxPayException;
+import com.github.binarywang.wxpay.service.WxPayService;
+import com.github.binarywang.wxpay.testbase.ApiTestModule;
+import com.google.inject.Inject;
+import lombok.extern.slf4j.Slf4j;
+import org.testng.annotations.Guice;
+import org.testng.annotations.Test;
+
+import java.nio.charset.StandardCharsets;
+
+@Slf4j
+@Test
+@Guice(modules = ApiTestModule.class)
+public class EcommerceServiceImplTest {
+
+  @Inject
+  private  WxPayService wxPayService;
+
+  @Test
+  public void testNotifySign(){
+    //通知报文主体
+    String notifyData = "";
+    //请求头  Wechatpay-Timestamp
+    String timeStamp = "";
+    //请求头  Wechatpay-Nonce
+    String nonce = "";
+    //请求头  Wechatpay-Signature
+    String signed = "";
+    //请求头  Wechatpay-Serial
+    String serialNo = "";
+
+    SignatureHeader header = new SignatureHeader();
+    header.setNonce(nonce);
+    header.setSerialNo(serialNo);
+    header.setTimeStamp(timeStamp);
+    header.setSigned(signed);
+
+    String beforeSign = String.format("%s\n%s\n%s\n",
+      header.getTimeStamp(),
+      header.getNonce(),
+      notifyData);
+    boolean signResult = wxPayService.getConfig().getVerifier().verify(header.getSerialNo(),
+      beforeSign.getBytes(StandardCharsets.UTF_8), header.getSigned());
+    log.info("签名结果:{} \nheader:{} \ndata:{}", signResult, header, notifyData);
+  }
+
+  @Test
+  public void testQueryPartnerTransactions() throws WxPayException {
+    PartnerTransactionsQueryRequest request = new PartnerTransactionsQueryRequest();
+    //服务商商户号
+    request.setSpMchid("");
+    //二级商户号
+    request.setSubMchid("");
+    //商户订单号
+    request.setOutTradeNo("");
+    //微信订单号
+    request.setTransactionId("");
+    wxPayService.getEcommerceService().queryPartnerTransactions(request);
+  }
+
+  @Test
+  public void testSubNowBalance() throws WxPayException {
+    String subMchid = "";
+    wxPayService.getEcommerceService().subNowBalance(subMchid);
+  }
+
+  @Test
+  public void testSubDayEndBalance() throws WxPayException {
+    String subMchid = "";
+    String date = "";
+    wxPayService.getEcommerceService().subDayEndBalance(subMchid,date);
+  }
+}
diff --git a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/testbase/ApiTestModule.java b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/testbase/ApiTestModule.java
index b29b1af16c..7155b544b6 100644
--- a/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/testbase/ApiTestModule.java
+++ b/weixin-java-pay/src/test/java/com/github/binarywang/wxpay/testbase/ApiTestModule.java
@@ -6,6 +6,7 @@
 import com.google.inject.Binder;
 import com.google.inject.Module;
 import com.thoughtworks.xstream.XStream;
+import me.chanjar.weixin.common.error.WxRuntimeException;
 import me.chanjar.weixin.common.util.xml.XStreamInitializer;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -24,7 +25,7 @@ public class ApiTestModule implements Module {
   public void configure(Binder binder) {
     try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(TEST_CONFIG_XML)) {
       if (inputStream == null) {
-        throw new RuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成");
+        throw new WxRuntimeException("测试配置文件【" + TEST_CONFIG_XML + "】未找到,请参照test-config-sample.xml文件生成");
       }
 
       XmlWxPayConfig config = this.fromXml(XmlWxPayConfig.class, inputStream);