| @@ -42,6 +42,7 @@ Icon | |||||
| .Spotlight-V100 | .Spotlight-V100 | ||||
| .TemporaryItems | .TemporaryItems | ||||
| .Trashes | .Trashes | ||||
| .vscode | |||||
| .VolumeIcon.icns | .VolumeIcon.icns | ||||
| .AppleDB | .AppleDB | ||||
| .AppleDesktop | .AppleDesktop | ||||
| @@ -0,0 +1,3 @@ | |||||
| { | |||||
| "java.configuration.updateBuildConfiguration": "interactive" | |||||
| } | |||||
| @@ -6,7 +6,7 @@ | |||||
| <modelVersion>4.0.0</modelVersion> | <modelVersion>4.0.0</modelVersion> | ||||
| <groupId>com.github.binarywang</groupId> | <groupId>com.github.binarywang</groupId> | ||||
| <artifactId>wx-java</artifactId> | <artifactId>wx-java</artifactId> | ||||
| <version>3.3.0</version> | |||||
| <version>3.4.0</version> | |||||
| <packaging>pom</packaging> | <packaging>pom</packaging> | ||||
| <name>WxJava - Weixin/Wechat Java SDK</name> | <name>WxJava - Weixin/Wechat Java SDK</name> | ||||
| <description>微信开发Java SDK</description> | <description>微信开发Java SDK</description> | ||||
| @@ -105,6 +105,8 @@ | |||||
| <module>weixin-java-pay</module> | <module>weixin-java-pay</module> | ||||
| <module>weixin-java-miniapp</module> | <module>weixin-java-miniapp</module> | ||||
| <module>weixin-java-open</module> | <module>weixin-java-open</module> | ||||
| <module>starters/wx-java-pay-starter</module> | |||||
| <module>starters/wx-java-mp-starter</module> | |||||
| <!--module>weixin-java-osgi</module--> | <!--module>weixin-java-osgi</module--> | ||||
| </modules> | </modules> | ||||
| @@ -0,0 +1,32 @@ | |||||
| # wx-java-mp-starter | |||||
| ## 快速开始 | |||||
| 1. 引入依赖 | |||||
| ```xml | |||||
| <dependency> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>wx-java-mp-starter</artifactId> | |||||
| <version>${version}</version> | |||||
| </dependency> | |||||
| ``` | |||||
| 2. 添加配置(application.properties) | |||||
| ```properties | |||||
| # 公众号配置(必填) | |||||
| wx.mp.appId = @appId | |||||
| wx.mp.secret = @secret | |||||
| wx.mp.token = @token | |||||
| wx.mp.aesKey = @aesKey | |||||
| # 存储配置redis(可选) | |||||
| wx.mp.config-storage.type = redis | |||||
| wx.mp.config-storage.redis.host = 127.0.0.1 | |||||
| wx.mp.config-storage.redis.port = 6379 | |||||
| ``` | |||||
| 3. 支持自动注入的类型 | |||||
| `WxMpService`以及相关的服务类, 比如: `wxMpService.getXxxService`。 | |||||
| @@ -0,0 +1,72 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | |||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||||
| <modelVersion>4.0.0</modelVersion> | |||||
| <parent> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>wx-java</artifactId> | |||||
| <version>3.4.0</version> | |||||
| <relativePath>../../</relativePath> | |||||
| </parent> | |||||
| <artifactId>wx-java-mp-starter</artifactId> | |||||
| <name>WxJava - Spring Boot Starter for MP</name> | |||||
| <description>微信公众号开发的Spring Boot Starter</description> | |||||
| <properties> | |||||
| <spring.boot.version>2.1.4.RELEASE</spring.boot.version> | |||||
| </properties> | |||||
| <dependencies> | |||||
| <dependency> | |||||
| <groupId>org.springframework.boot</groupId> | |||||
| <artifactId>spring-boot-autoconfigure</artifactId> | |||||
| <version>${spring.boot.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.springframework.boot</groupId> | |||||
| <artifactId>spring-boot-configuration-processor</artifactId> | |||||
| <version>${spring.boot.version}</version> | |||||
| <optional>true</optional> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>weixin-java-mp</artifactId> | |||||
| <version>${project.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>redis.clients</groupId> | |||||
| <artifactId>jedis</artifactId> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.projectlombok</groupId> | |||||
| <artifactId>lombok</artifactId> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| </dependencies> | |||||
| <build> | |||||
| <plugins> | |||||
| <plugin> | |||||
| <groupId>org.springframework.boot</groupId> | |||||
| <artifactId>spring-boot-maven-plugin</artifactId> | |||||
| <version>${spring.boot.version}</version> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-source-plugin</artifactId> | |||||
| <version>2.2.1</version> | |||||
| <executions> | |||||
| <execution> | |||||
| <id>attach-sources</id> | |||||
| <goals> | |||||
| <goal>jar-no-fork</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| </plugin> | |||||
| </plugins> | |||||
| </build> | |||||
| </project> | |||||
| @@ -0,0 +1,42 @@ | |||||
| package com.binarywang.spring.starter.wxjava.mp; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * Redis配置 | |||||
| */ | |||||
| @Data | |||||
| public class RedisProperties implements Serializable { | |||||
| /** | |||||
| * 主机地址 | |||||
| */ | |||||
| 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; | |||||
| } | |||||
| @@ -0,0 +1,11 @@ | |||||
| package com.binarywang.spring.starter.wxjava.mp; | |||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | |||||
| import org.springframework.context.annotation.Configuration; | |||||
| import org.springframework.context.annotation.Import; | |||||
| @Configuration | |||||
| @EnableConfigurationProperties(WxMpProperties.class) | |||||
| @Import({WxMpStorageAutoConfiguration.class, WxMpServiceAutoConfiguration.class}) | |||||
| public class WxMpAutoConfiguration { | |||||
| } | |||||
| @@ -0,0 +1,58 @@ | |||||
| package com.binarywang.spring.starter.wxjava.mp; | |||||
| import lombok.Data; | |||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | |||||
| import java.io.Serializable; | |||||
| import static com.binarywang.spring.starter.wxjava.mp.WxMpProperties.PREFIX; | |||||
| import static com.binarywang.spring.starter.wxjava.mp.WxMpProperties.StorageType.memory; | |||||
| /** | |||||
| * 微信接入相关配置属性 | |||||
| */ | |||||
| @Data | |||||
| @ConfigurationProperties(PREFIX) | |||||
| public class WxMpProperties { | |||||
| public static final String PREFIX = "wx.mp"; | |||||
| /** | |||||
| * 设置微信公众号的appid | |||||
| */ | |||||
| private String appId; | |||||
| /** | |||||
| * 设置微信公众号的app secret | |||||
| */ | |||||
| private String secret; | |||||
| /** | |||||
| * 设置微信公众号的token | |||||
| */ | |||||
| private String token; | |||||
| /** | |||||
| * 设置微信公众号的EncodingAESKey | |||||
| */ | |||||
| private String aesKey; | |||||
| /** | |||||
| * 存储策略, memory, redis | |||||
| */ | |||||
| private ConfigStorage configStorage = new ConfigStorage(); | |||||
| @Data | |||||
| public static class ConfigStorage implements Serializable { | |||||
| private StorageType type = memory; | |||||
| private RedisProperties redis = new RedisProperties(); | |||||
| } | |||||
| public enum StorageType { | |||||
| memory, redis | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,53 @@ | |||||
| package com.binarywang.spring.starter.wxjava.mp; | |||||
| import me.chanjar.weixin.mp.api.WxMpConfigStorage; | |||||
| import me.chanjar.weixin.mp.api.WxMpService; | |||||
| import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; | |||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; | |||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | |||||
| import org.springframework.context.ApplicationContext; | |||||
| import org.springframework.context.annotation.Bean; | |||||
| import org.springframework.context.annotation.Configuration; | |||||
| /** | |||||
| * 微信公众号相关服务自动注册 | |||||
| */ | |||||
| @Configuration | |||||
| public class WxMpServiceAutoConfiguration { | |||||
| @Autowired | |||||
| private ApplicationContext ctx; | |||||
| @Bean | |||||
| @ConditionalOnMissingBean | |||||
| public WxMpService wxMpService(WxMpConfigStorage configStorage) { | |||||
| WxMpService wxMpService = new WxMpServiceImpl(); | |||||
| wxMpService.setWxMpConfigStorage(configStorage); | |||||
| registerWxMpSubService(wxMpService); | |||||
| return wxMpService; | |||||
| } | |||||
| @ConditionalOnBean(WxMpService.class) | |||||
| public Object registerWxMpSubService(WxMpService wxMpService) { | |||||
| ConfigurableListableBeanFactory factory = (ConfigurableListableBeanFactory) ctx.getAutowireCapableBeanFactory(); | |||||
| factory.registerSingleton("wxMpKefuService", wxMpService.getKefuService()); | |||||
| factory.registerSingleton("wxMpMaterialService", wxMpService.getMaterialService()); | |||||
| factory.registerSingleton("wxMpMenuService", wxMpService.getMenuService()); | |||||
| factory.registerSingleton("wxMpUserService", wxMpService.getUserService()); | |||||
| factory.registerSingleton("wxMpUserTagService", wxMpService.getUserTagService()); | |||||
| factory.registerSingleton("wxMpQrcodeService", wxMpService.getQrcodeService()); | |||||
| factory.registerSingleton("wxMpCardService", wxMpService.getCardService()); | |||||
| factory.registerSingleton("wxMpDataCubeService", wxMpService.getDataCubeService()); | |||||
| factory.registerSingleton("wxMpUserBlacklistService", wxMpService.getBlackListService()); | |||||
| factory.registerSingleton("wxMpStoreService", wxMpService.getStoreService()); | |||||
| factory.registerSingleton("wxMpTemplateMsgService", wxMpService.getTemplateMsgService()); | |||||
| factory.registerSingleton("wxMpSubscribeMsgService", wxMpService.getSubscribeMsgService()); | |||||
| factory.registerSingleton("wxMpDeviceService", wxMpService.getDeviceService()); | |||||
| factory.registerSingleton("wxMpShakeService", wxMpService.getShakeService()); | |||||
| factory.registerSingleton("wxMpMemberCardService", wxMpService.getMemberCardService()); | |||||
| factory.registerSingleton("wxMpMassMessageService", wxMpService.getMassMessageService()); | |||||
| return Boolean.TRUE; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,84 @@ | |||||
| package com.binarywang.spring.starter.wxjava.mp; | |||||
| import me.chanjar.weixin.mp.api.WxMpConfigStorage; | |||||
| import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage; | |||||
| import me.chanjar.weixin.mp.api.WxMpInRedisConfigStorage; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | |||||
| import org.springframework.context.annotation.Bean; | |||||
| import org.springframework.context.annotation.Configuration; | |||||
| import redis.clients.jedis.JedisPool; | |||||
| import redis.clients.jedis.JedisPoolConfig; | |||||
| /** | |||||
| * 微信公众号存储策略自动配置 | |||||
| */ | |||||
| @Configuration | |||||
| public class WxMpStorageAutoConfiguration { | |||||
| @Autowired | |||||
| private WxMpProperties properties; | |||||
| @Autowired(required = false) | |||||
| private JedisPool jedisPool; | |||||
| @Bean | |||||
| @ConditionalOnMissingBean(WxMpConfigStorage.class) | |||||
| public WxMpConfigStorage wxMpInMemoryConfigStorage() { | |||||
| WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); | |||||
| WxMpProperties.StorageType type = storage.getType(); | |||||
| if (type == WxMpProperties.StorageType.redis) { | |||||
| return getWxMpInRedisConfigStorage(); | |||||
| } | |||||
| return getWxMpInMemoryConfigStorage(); | |||||
| } | |||||
| private WxMpInMemoryConfigStorage getWxMpInMemoryConfigStorage() { | |||||
| WxMpInMemoryConfigStorage config = new WxMpInMemoryConfigStorage(); | |||||
| setWxMpInfo(config); | |||||
| return config; | |||||
| } | |||||
| private WxMpInRedisConfigStorage getWxMpInRedisConfigStorage() { | |||||
| JedisPool poolToUse = jedisPool; | |||||
| if (poolToUse == null) { | |||||
| poolToUse = getJedisPool(); | |||||
| } | |||||
| WxMpInRedisConfigStorage config = new WxMpInRedisConfigStorage(poolToUse); | |||||
| setWxMpInfo(config); | |||||
| return config; | |||||
| } | |||||
| private void setWxMpInfo(WxMpInMemoryConfigStorage config) { | |||||
| config.setAppId(properties.getAppId()); | |||||
| config.setSecret(properties.getSecret()); | |||||
| config.setToken(properties.getToken()); | |||||
| config.setAesKey(properties.getAesKey()); | |||||
| } | |||||
| private JedisPool getJedisPool() { | |||||
| WxMpProperties.ConfigStorage storage = properties.getConfigStorage(); | |||||
| RedisProperties redis = storage.getRedis(); | |||||
| JedisPoolConfig config = new JedisPoolConfig(); | |||||
| if (redis.getMaxActive() != null) { | |||||
| config.setMaxTotal(redis.getMaxActive()); | |||||
| } | |||||
| if (redis.getMaxIdle() != null) { | |||||
| config.setMaxIdle(redis.getMaxIdle()); | |||||
| } | |||||
| if (redis.getMaxWaitMillis() != null) { | |||||
| config.setMaxWaitMillis(redis.getMaxWaitMillis()); | |||||
| } | |||||
| if (redis.getMinIdle() != null) { | |||||
| config.setMinIdle(redis.getMinIdle()); | |||||
| } | |||||
| config.setTestOnBorrow(true); | |||||
| config.setTestWhileIdle(true); | |||||
| JedisPool pool = new JedisPool(config, redis.getHost(), redis.getPort(), | |||||
| redis.getTimeout(), redis.getPassword(), redis.getDatabase()); | |||||
| return pool; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.binarywang.spring.starter.wxjava.mp.WxMpAutoConfiguration | |||||
| @@ -0,0 +1,27 @@ | |||||
| # 使用说明 | |||||
| 1. 在自己的Spring Boot项目里,引入maven依赖 | |||||
| ```xml | |||||
| <dependency> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>wx-java-pay-starter</artifactId> | |||||
| <version>${version}</version> | |||||
| </dependency> | |||||
| ``` | |||||
| 2. 添加配置(application.yml) | |||||
| ```yml | |||||
| wx: | |||||
| pay: | |||||
| appId: wx5b69c56ac01ed858 | |||||
| mchId: 1462547202 | |||||
| mchKey: OGL9fvig9y2HrXrQ86tM4jTwyv4ja6G5 | |||||
| subAppId: | |||||
| subMchId: | |||||
| keyPath: | |||||
| ``` | |||||
| @@ -0,0 +1,68 @@ | |||||
| <?xml version="1.0" encoding="UTF-8"?> | |||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | |||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||||
| <parent> | |||||
| <artifactId>wx-java</artifactId> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <version>3.4.0</version> | |||||
| <relativePath>../../</relativePath> | |||||
| </parent> | |||||
| <modelVersion>4.0.0</modelVersion> | |||||
| <artifactId>wx-java-pay-starter</artifactId> | |||||
| <name>WxJava - Spring Boot Starter for WxPay</name> | |||||
| <description>微信支付开发的Spring Boot Starter</description> | |||||
| <properties> | |||||
| <spring.boot.version>2.1.4.RELEASE</spring.boot.version> | |||||
| </properties> | |||||
| <dependencies> | |||||
| <dependency> | |||||
| <groupId>org.springframework.boot</groupId> | |||||
| <artifactId>spring-boot-autoconfigure</artifactId> | |||||
| <version>${spring.boot.version}</version> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.springframework.boot</groupId> | |||||
| <artifactId>spring-boot-configuration-processor</artifactId> | |||||
| <version>${spring.boot.version}</version> | |||||
| <optional>true</optional> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>org.projectlombok</groupId> | |||||
| <artifactId>lombok</artifactId> | |||||
| <scope>provided</scope> | |||||
| </dependency> | |||||
| <dependency> | |||||
| <groupId>com.github.binarywang</groupId> | |||||
| <artifactId>weixin-java-pay</artifactId> | |||||
| <version>${project.version}</version> | |||||
| </dependency> | |||||
| </dependencies> | |||||
| <build> | |||||
| <plugins> | |||||
| <plugin> | |||||
| <groupId>org.springframework.boot</groupId> | |||||
| <artifactId>spring-boot-maven-plugin</artifactId> | |||||
| <version>${spring.boot.version}</version> | |||||
| </plugin> | |||||
| <plugin> | |||||
| <groupId>org.apache.maven.plugins</groupId> | |||||
| <artifactId>maven-source-plugin</artifactId> | |||||
| <version>2.2.1</version> | |||||
| <executions> | |||||
| <execution> | |||||
| <id>attach-sources</id> | |||||
| <goals> | |||||
| <goal>jar-no-fork</goal> | |||||
| </goals> | |||||
| </execution> | |||||
| </executions> | |||||
| </plugin> | |||||
| </plugins> | |||||
| </build> | |||||
| </project> | |||||
| @@ -0,0 +1,57 @@ | |||||
| package com.binarywang.spring.starter.wxjava.pay.config; | |||||
| import com.binarywang.spring.starter.wxjava.pay.properties.WxPayProperties; | |||||
| import com.github.binarywang.wxpay.config.WxPayConfig; | |||||
| import com.github.binarywang.wxpay.service.WxPayService; | |||||
| import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | |||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; | |||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | |||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | |||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | |||||
| import org.springframework.context.annotation.Bean; | |||||
| import org.springframework.context.annotation.Configuration; | |||||
| /** | |||||
| * <pre> | |||||
| * 微信支付自动配置 | |||||
| * Created by BinaryWang on 2019/4/17. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| @Configuration | |||||
| @EnableConfigurationProperties(WxPayProperties.class) | |||||
| @ConditionalOnClass(WxPayService.class) | |||||
| @ConditionalOnProperty(prefix = "wx.pay", value = "enabled", matchIfMissing = true) | |||||
| public class WxPayAutoConfiguration { | |||||
| private WxPayProperties properties; | |||||
| @Autowired | |||||
| public WxPayAutoConfiguration(WxPayProperties properties) { | |||||
| this.properties = properties; | |||||
| } | |||||
| /** | |||||
| * 构造微信支付服务对象. | |||||
| * | |||||
| * @return 微信支付service | |||||
| */ | |||||
| @Bean | |||||
| @ConditionalOnMissingBean(WxPayService.class) | |||||
| public WxPayService wxPayService() { | |||||
| final WxPayServiceImpl wxPayService = new WxPayServiceImpl(); | |||||
| WxPayConfig payConfig = new WxPayConfig(); | |||||
| payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId())); | |||||
| payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId())); | |||||
| payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey())); | |||||
| payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId())); | |||||
| payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId())); | |||||
| payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath())); | |||||
| wxPayService.setConfig(payConfig); | |||||
| return wxPayService; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,46 @@ | |||||
| package com.binarywang.spring.starter.wxjava.pay.properties; | |||||
| import lombok.Data; | |||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | |||||
| /** | |||||
| * <pre> | |||||
| * 微信支付属性配置类 | |||||
| * Created by Binary Wang on 2019/4/17. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| @Data | |||||
| @ConfigurationProperties(prefix = "wx.pay") | |||||
| public class WxPayProperties { | |||||
| /** | |||||
| * 设置微信公众号或者小程序等的appid. | |||||
| */ | |||||
| private String appId; | |||||
| /** | |||||
| * 微信支付商户号. | |||||
| */ | |||||
| private String mchId; | |||||
| /** | |||||
| * 微信支付商户密钥. | |||||
| */ | |||||
| private String mchKey; | |||||
| /** | |||||
| * 服务商模式下的子商户公众账号ID,普通模式请不要配置,请在配置文件中将对应项删除. | |||||
| */ | |||||
| private String subAppId; | |||||
| /** | |||||
| * 服务商模式下的子商户号,普通模式请不要配置,最好是请在配置文件中将对应项删除. | |||||
| */ | |||||
| private String subMchId; | |||||
| /** | |||||
| * apiclient_cert.p12文件的绝对路径,或者如果放在项目中,请以classpath:开头指定. | |||||
| */ | |||||
| private String keyPath; | |||||
| } | |||||
| @@ -0,0 +1 @@ | |||||
| org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.binarywang.spring.starter.wxjava.pay.config.WxPayAutoConfiguration | |||||
| @@ -1,17 +1,16 @@ | |||||
| <?xml version="1.0"?> | <?xml version="1.0"?> | ||||
| <project | |||||
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" | ||||
| xmlns="http://maven.apache.org/POM/4.0.0"> | xmlns="http://maven.apache.org/POM/4.0.0"> | ||||
| <modelVersion>4.0.0</modelVersion> | <modelVersion>4.0.0</modelVersion> | ||||
| <parent> | <parent> | ||||
| <groupId>com.github.binarywang</groupId> | <groupId>com.github.binarywang</groupId> | ||||
| <artifactId>wx-java</artifactId> | <artifactId>wx-java</artifactId> | ||||
| <version>3.3.0</version> | |||||
| <version>3.4.0</version> | |||||
| </parent> | </parent> | ||||
| <artifactId>weixin-java-common</artifactId> | <artifactId>weixin-java-common</artifactId> | ||||
| <name>WxJava - Common</name> | |||||
| <name>WxJava - Common Java SDK</name> | |||||
| <description>微信开发Java SDK公共模块</description> | <description>微信开发Java SDK公共模块</description> | ||||
| <dependencies> | <dependencies> | ||||
| @@ -62,6 +62,11 @@ public class WxConsts { | |||||
| * 图文消息(点击跳转到图文消息页面). | * 图文消息(点击跳转到图文消息页面). | ||||
| */ | */ | ||||
| public static final String MPNEWS = "mpnews"; | public static final String MPNEWS = "mpnews"; | ||||
| /** | |||||
| * markdown消息. | |||||
| * (目前仅支持markdown语法的子集,微工作台(原企业号)不支持展示markdown消息) | |||||
| */ | |||||
| public static final String MARKDOWN = "markdown"; | |||||
| /** | /** | ||||
| * 发送文件(CP专用). | * 发送文件(CP专用). | ||||
| */ | */ | ||||
| @@ -83,6 +88,11 @@ public class WxConsts { | |||||
| * 小程序卡片(要求小程序与公众号已关联) | * 小程序卡片(要求小程序与公众号已关联) | ||||
| */ | */ | ||||
| public static final String MINIPROGRAMPAGE = "miniprogrampage"; | public static final String MINIPROGRAMPAGE = "miniprogrampage"; | ||||
| /** | |||||
| * 任务卡片消息 | |||||
| */ | |||||
| public static final String TASKCARD = "taskcard"; | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -39,6 +39,7 @@ public class WxCryptUtil { | |||||
| try { | try { | ||||
| final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | ||||
| factory.setExpandEntityReferences(false); | factory.setExpandEntityReferences(false); | ||||
| factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); | |||||
| return factory.newDocumentBuilder(); | return factory.newDocumentBuilder(); | ||||
| } catch (ParserConfigurationException exc) { | } catch (ParserConfigurationException exc) { | ||||
| throw new IllegalArgumentException(exc); | throw new IllegalArgumentException(exc); | ||||
| @@ -57,7 +57,7 @@ public class ApacheMediaDownloadRequestExecutor extends BaseMediaDownloadRequest | |||||
| String fileName = new HttpResponseProxy(response).getFileName(); | String fileName = new HttpResponseProxy(response).getFileName(); | ||||
| if (StringUtils.isBlank(fileName)) { | if (StringUtils.isBlank(fileName)) { | ||||
| return null; | |||||
| fileName = String.valueOf(System.currentTimeMillis()); | |||||
| } | } | ||||
| return FileUtils.createTmpFile(inputStream, FilenameUtils.getBaseName(fileName), FilenameUtils.getExtension(fileName), | return FileUtils.createTmpFile(inputStream, FilenameUtils.getBaseName(fileName), FilenameUtils.getExtension(fileName), | ||||
| @@ -19,6 +19,7 @@ package me.chanjar.weixin.common.util.res; | |||||
| import java.text.MessageFormat; | import java.text.MessageFormat; | ||||
| import java.util.*; | import java.util.*; | ||||
| import java.util.concurrent.ConcurrentHashMap; | |||||
| /** | /** | ||||
| * An internationalization / localization helper class which reduces | * An internationalization / localization helper class which reduces | ||||
| @@ -46,7 +47,7 @@ import java.util.*; | |||||
| */ | */ | ||||
| public class StringManager { | public class StringManager { | ||||
| private static final Map<String, Map<Locale, StringManager>> MANAGERS = new Hashtable<>(); | |||||
| private static final Map<String, Map<Locale, StringManager>> MANAGERS = new ConcurrentHashMap<>(); | |||||
| private static int LOCALE_CACHE_SIZE = 10; | private static int LOCALE_CACHE_SIZE = 10; | ||||
| /** | /** | ||||
| * The ResourceBundle for this StringManager. | * The ResourceBundle for this StringManager. | ||||
| @@ -40,6 +40,7 @@ public class WxCryptUtilTest { | |||||
| DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | ||||
| documentBuilderFactory.setExpandEntityReferences(false); | documentBuilderFactory.setExpandEntityReferences(false); | ||||
| documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); | |||||
| DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | ||||
| Document document = documentBuilder.parse(new InputSource(new StringReader(encryptedXml))); | Document document = documentBuilder.parse(new InputSource(new StringReader(encryptedXml))); | ||||
| @@ -83,6 +84,8 @@ public class WxCryptUtilTest { | |||||
| String afterEncrpt = pc.encrypt(this.replyMsg); | String afterEncrpt = pc.encrypt(this.replyMsg); | ||||
| DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); | ||||
| dbf.setExpandEntityReferences(false); | dbf.setExpandEntityReferences(false); | ||||
| dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); | |||||
| DocumentBuilder db = dbf.newDocumentBuilder(); | DocumentBuilder db = dbf.newDocumentBuilder(); | ||||
| StringReader sr = new StringReader(afterEncrpt); | StringReader sr = new StringReader(afterEncrpt); | ||||
| InputSource is = new InputSource(sr); | InputSource is = new InputSource(sr); | ||||
| @@ -7,11 +7,11 @@ | |||||
| <parent> | <parent> | ||||
| <groupId>com.github.binarywang</groupId> | <groupId>com.github.binarywang</groupId> | ||||
| <artifactId>wx-java</artifactId> | <artifactId>wx-java</artifactId> | ||||
| <version>3.3.0</version> | |||||
| <version>3.4.0</version> | |||||
| </parent> | </parent> | ||||
| <artifactId>weixin-java-cp</artifactId> | <artifactId>weixin-java-cp</artifactId> | ||||
| <name>WxJava - CP</name> | |||||
| <name>WxJava - CP Java SDK</name> | |||||
| <description>微信企业号/企业微信 Java SDK</description> | <description>微信企业号/企业微信 Java SDK</description> | ||||
| <dependencies> | <dependencies> | ||||
| @@ -84,6 +84,11 @@ public class WxCpConsts { | |||||
| */ | */ | ||||
| public static final String LOCATION_SELECT = "location_select"; | public static final String LOCATION_SELECT = "location_select"; | ||||
| /** | |||||
| * 任务卡片事件推送. | |||||
| */ | |||||
| public static final String TASKCARD_CLICK = "taskcard_click"; | |||||
| } | } | ||||
| /** | /** | ||||
| @@ -126,4 +131,46 @@ public class WxCpConsts { | |||||
| public static final String UPDATE_TAG = "update_tag"; | public static final String UPDATE_TAG = "update_tag"; | ||||
| } | } | ||||
| /** | |||||
| * 应用推送消息的消息类型. | |||||
| */ | |||||
| public static class AppChatMsgType { | |||||
| /** | |||||
| * 文本消息. | |||||
| */ | |||||
| public static final String TEXT = "text"; | |||||
| /** | |||||
| * 图片消息. | |||||
| */ | |||||
| public static final String IMAGE = "image"; | |||||
| /** | |||||
| * 语音消息. | |||||
| */ | |||||
| public static final String VOICE = "voice"; | |||||
| /** | |||||
| * 视频消息. | |||||
| */ | |||||
| public static final String VIDEO = "video"; | |||||
| /** | |||||
| * 发送文件(CP专用). | |||||
| */ | |||||
| public static final String FILE = "file"; | |||||
| /** | |||||
| * 文本卡片消息(CP专用). | |||||
| */ | |||||
| public static final String TEXTCARD = "textcard"; | |||||
| /** | |||||
| * 图文消息(点击跳转到外链). | |||||
| */ | |||||
| public static final String NEWS = "news"; | |||||
| /** | |||||
| * 图文消息(点击跳转到图文消息页面). | |||||
| */ | |||||
| public static final String MPNEWS = "mpnews"; | |||||
| /** | |||||
| * markdown消息. | |||||
| */ | |||||
| public static final String MARKDOWN = "markdown"; | |||||
| } | |||||
| } | } | ||||
| @@ -3,46 +3,75 @@ package me.chanjar.weixin.cp.api; | |||||
| import java.util.List; | import java.util.List; | ||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import me.chanjar.weixin.cp.bean.WxCpAppChatMessage; | |||||
| import me.chanjar.weixin.cp.bean.WxCpChat; | import me.chanjar.weixin.cp.bean.WxCpChat; | ||||
| /** | /** | ||||
| * 群聊服务 | |||||
| * 群聊服务. | |||||
| * | * | ||||
| * @author gaigeshen | * @author gaigeshen | ||||
| */ | */ | ||||
| public interface WxCpChatService { | public interface WxCpChatService { | ||||
| String APPCHAT_CREATE = "https://qyapi.weixin.qq.com/cgi-bin/appchat/create"; | |||||
| String APPCHAT_UPDATE = "https://qyapi.weixin.qq.com/cgi-bin/appchat/update"; | |||||
| String APPCHAT_GET_CHATID = "https://qyapi.weixin.qq.com/cgi-bin/appchat/get?chatid="; | |||||
| /** | /** | ||||
| * 创建群聊会话,注意:刚创建的群,如果没有下发消息,在企业微信不会出现该群。 | |||||
| * 创建群聊会话,注意:刚创建的群,如果没有下发消息,在企业微信不会出现该群. | |||||
| * | * | ||||
| * @param name 群聊名,最多50个utf8字符,超过将截断 | |||||
| * @param owner 指定群主的id。如果不指定,系统会随机从userlist中选一人作为群主 | |||||
| * @param users 群成员id列表。至少2人,至多500人 | |||||
| * @param name 群聊名,最多50个utf8字符,超过将截断 | |||||
| * @param owner 指定群主的id。如果不指定,系统会随机从userlist中选一人作为群主 | |||||
| * @param users 群成员id列表。至少2人,至多500人 | |||||
| * @param chatId 群聊的唯一标志,不能与已有的群重复;字符串类型,最长32个字符。只允许字符0-9及字母a-zA-Z。如果不填,系统会随机生成群id | * @param chatId 群聊的唯一标志,不能与已有的群重复;字符串类型,最长32个字符。只允许字符0-9及字母a-zA-Z。如果不填,系统会随机生成群id | ||||
| * @return 创建群聊会话的结果,群聊的唯一标志 | |||||
| * @return 创建的群聊会话chatId | |||||
| * @throws WxErrorException 发生异常 | * @throws WxErrorException 发生异常 | ||||
| */ | */ | ||||
| String chatCreate(String name, String owner, List<String> users, String chatId) throws WxErrorException; | String chatCreate(String name, String owner, List<String> users, String chatId) throws WxErrorException; | ||||
| /** | /** | ||||
| * 修改群聊会话 | |||||
| * | |||||
| * @param chatId 群聊id | |||||
| * @param name 新的群聊名。若不需更新,请忽略此参数(null or empty)。最多50个utf8字符,超过将截断 | |||||
| * @param owner 新群主的id。若不需更新,请忽略此参数(null or empty) | |||||
| * @param usersToAdd 添加成员的id列表,若不需要更新,则传递空对象或者空集合 | |||||
| * chatCreate 同名方法 | |||||
| */ | |||||
| String create(String name, String owner, List<String> users, String chatId) throws WxErrorException; | |||||
| /** | |||||
| * 修改群聊会话. | |||||
| * | |||||
| * @param chatId 群聊id | |||||
| * @param name 新的群聊名。若不需更新,请忽略此参数(null or empty)。最多50个utf8字符,超过将截断 | |||||
| * @param owner 新群主的id。若不需更新,请忽略此参数(null or empty) | |||||
| * @param usersToAdd 添加成员的id列表,若不需要更新,则传递空对象或者空集合 | |||||
| * @param usersToDelete 踢出成员的id列表,若不需要更新,则传递空对象或者空集合 | * @param usersToDelete 踢出成员的id列表,若不需要更新,则传递空对象或者空集合 | ||||
| * @throws WxErrorException 发生异常 | * @throws WxErrorException 发生异常 | ||||
| */ | */ | ||||
| void chatUpdate(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException; | void chatUpdate(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException; | ||||
| /** | /** | ||||
| * 获取群聊会话 | |||||
| * | |||||
| * chatUpdate 同名方法 | |||||
| */ | |||||
| void update(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException; | |||||
| /** | |||||
| * 获取群聊会话. | |||||
| * | |||||
| * @param chatId 群聊编号 | * @param chatId 群聊编号 | ||||
| * @return 群聊会话 | * @return 群聊会话 | ||||
| * @throws WxErrorException 发生异常 | * @throws WxErrorException 发生异常 | ||||
| */ | */ | ||||
| WxCpChat chatGet(String chatId) throws WxErrorException; | WxCpChat chatGet(String chatId) throws WxErrorException; | ||||
| /** | |||||
| * chatGet 同名方法 | |||||
| */ | |||||
| WxCpChat get(String chatId) throws WxErrorException; | |||||
| /** | |||||
| * 应用支持推送文本、图片、视频、文件、图文等类型. | |||||
| * 请求方式: POST(HTTPS) | |||||
| * 请求地址: https://qyapi.weixin.qq.com/cgi-bin/appchat/send?access_token=ACCESS_TOKEN | |||||
| * 文档地址:https://work.weixin.qq.com/api/doc#90000/90135/90248 | |||||
| * | |||||
| * @param message 要发送的消息内容对象 | |||||
| */ | |||||
| void sendMsg(WxCpAppChatMessage message) throws WxErrorException; | |||||
| } | } | ||||
| @@ -26,7 +26,7 @@ public interface WxCpDepartmentService { | |||||
| * @return 部门id | * @return 部门id | ||||
| * @throws WxErrorException 异常 | * @throws WxErrorException 异常 | ||||
| */ | */ | ||||
| Integer create(WxCpDepart depart) throws WxErrorException; | |||||
| Long create(WxCpDepart depart) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| @@ -0,0 +1,66 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.cp.bean.WxCpApprovalDataResult; | |||||
| import me.chanjar.weixin.cp.bean.WxCpCheckinData; | |||||
| import me.chanjar.weixin.cp.bean.WxCpCheckinOption; | |||||
| import me.chanjar.weixin.cp.bean.WxCpDialRecord; | |||||
| import java.util.Date; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @author Element | |||||
| * @Package me.chanjar.weixin.cp.api | |||||
| * @date 2019-04-06 10:52 | |||||
| * @Description: <pre> | |||||
| * 企业微信OA相关接口 | |||||
| * | |||||
| * </pre> | |||||
| */ | |||||
| public interface WxCpOAService { | |||||
| /** | |||||
| * <pre> | |||||
| * 获取打卡数据 | |||||
| * API doc : https://work.weixin.qq.com/api/doc#90000/90135/90262 | |||||
| * </pre> | |||||
| * | |||||
| * @param openCheckinDataType 打卡类型。1:上下班打卡;2:外出打卡;3:全部打卡 | |||||
| * @param starttime 获取打卡记录的开始时间 | |||||
| * @param endtime 获取打卡记录的结束时间 | |||||
| * @param userIdList 需要获取打卡记录的用户列表 | |||||
| */ | |||||
| List<WxCpCheckinData> getCheckinData(Integer openCheckinDataType, Date starttime, Date endtime, List<String> userIdList) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 获取打卡规则 | |||||
| * API doc : https://work.weixin.qq.com/api/doc#90000/90135/90263 | |||||
| * </pre> | |||||
| * | |||||
| * @param datetime 需要获取规则的当天日期 | |||||
| * @param userIdList 需要获取打卡规则的用户列表 | |||||
| * @return | |||||
| * @throws WxErrorException | |||||
| */ | |||||
| List<WxCpCheckinOption> getCheckinOption(Date datetime, List<String> userIdList) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 获取审批数据 | |||||
| * 通过本接口来获取公司一段时间内的审批记录。一次拉取调用最多拉取10000个审批记录,可以通过多次拉取的方式来满足需求,但调用频率不可超过600次/分。 | |||||
| * API doc : https://work.weixin.qq.com/api/doc#90000/90135/91530 | |||||
| * </pre> | |||||
| * | |||||
| * @param starttime 获取审批记录的开始时间 | |||||
| * @param endtime 获取审批记录的结束时间 | |||||
| * @param nextSpnum 第一个拉取的审批单号,不填从该时间段的第一个审批单拉取 | |||||
| * @return | |||||
| * @throws WxErrorException | |||||
| */ | |||||
| WxCpApprovalDataResult getApprovalData(Date starttime, Date endtime, Long nextSpnum) throws WxErrorException; | |||||
| List<WxCpDialRecord> getDialRecord(Date starttime, Date endtime, Integer offset, Integer limit) throws WxErrorException; | |||||
| } | |||||
| @@ -7,6 +7,7 @@ import me.chanjar.weixin.common.session.WxSessionManager; | |||||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | ||||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | import me.chanjar.weixin.common.util.http.RequestExecutor; | ||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | 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.WxCpMessage; | ||||
| import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | ||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | import me.chanjar.weixin.cp.config.WxCpConfigStorage; | ||||
| @@ -16,6 +17,15 @@ import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||||
| * @author chanjaster | * @author chanjaster | ||||
| */ | */ | ||||
| public interface WxCpService { | public interface WxCpService { | ||||
| String GET_JSAPI_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket"; | |||||
| String GET_AGENT_CONFIG_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?&type=agent_config"; | |||||
| String MESSAGE_SEND = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; | |||||
| String GET_CALLBACK_IP = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip"; | |||||
| String BATCH_REPLACE_PARTY = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty"; | |||||
| String BATCH_REPLACE_USER = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser"; | |||||
| String BATCH_GET_RESULT = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid="; | |||||
| String JSCODE_TO_SESSION_URL = "https://qyapi.weixin.qq.com/cgi-bin/miniprogram/jscode2session"; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 验证推送过来的消息的正确性 | * 验证推送过来的消息的正确性 | ||||
| @@ -68,6 +78,33 @@ public interface WxCpService { | |||||
| */ | */ | ||||
| String getJsapiTicket(boolean forceRefresh) throws WxErrorException; | String getJsapiTicket(boolean forceRefresh) throws WxErrorException; | ||||
| /** | |||||
| * 获得jsapi_ticket,不强制刷新jsapi_ticket | |||||
| * 应用的jsapi_ticket用于计算agentConfig(参见“通过agentConfig注入应用的权限”)的签名,签名计算方法与上述介绍的config的签名算法完全相同,但需要注意以下区别: | |||||
| * | |||||
| * 签名的jsapi_ticket必须使用以下接口获取。且必须用wx.agentConfig中的agentid对应的应用secret去获取access_token。 | |||||
| * 签名用的noncestr和timestamp必须与wx.agentConfig中的nonceStr和timestamp相同。 | |||||
| * @see #getJsapiTicket(boolean) | |||||
| */ | |||||
| String getAgentJsapiTicket() throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 获取应用的jsapi_ticket | |||||
| * 应用的jsapi_ticket用于计算agentConfig(参见“通过agentConfig注入应用的权限”)的签名,签名计算方法与上述介绍的config的签名算法完全相同,但需要注意以下区别: | |||||
| * | |||||
| * 签名的jsapi_ticket必须使用以下接口获取。且必须用wx.agentConfig中的agentid对应的应用secret去获取access_token。 | |||||
| * 签名用的noncestr和timestamp必须与wx.agentConfig中的nonceStr和timestamp相同。 | |||||
| * | |||||
| * 获得时会检查jsapiToken是否过期,如果过期了,那么就刷新一下,否则就什么都不干 | |||||
| * | |||||
| * 详情请见:https://work.weixin.qq.com/api/doc#10029/%E8%8E%B7%E5%8F%96%E5%BA%94%E7%94%A8%E7%9A%84jsapi_ticket | |||||
| * </pre> | |||||
| * | |||||
| * @param forceRefresh 强制刷新 | |||||
| */ | |||||
| String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 创建调用jsapi时所需要的签名 | * 创建调用jsapi时所需要的签名 | ||||
| @@ -89,6 +126,13 @@ public interface WxCpService { | |||||
| */ | */ | ||||
| WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException; | WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException; | ||||
| /** | |||||
| * 小程序登录凭证校验 | |||||
| * | |||||
| * @param jsCode 登录时获取的 code | |||||
| */ | |||||
| WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 获取微信服务器的ip段 | * 获取微信服务器的ip段 | ||||
| @@ -165,6 +209,13 @@ public interface WxCpService { | |||||
| */ | */ | ||||
| WxSession getSession(String id, boolean create); | WxSession getSession(String id, boolean create); | ||||
| /** | |||||
| * 获取WxSessionManager 对象 | |||||
| * | |||||
| * @return WxSessionManager | |||||
| */ | |||||
| WxSessionManager getSessionManager(); | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法, | * 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法, | ||||
| @@ -250,8 +301,17 @@ public interface WxCpService { | |||||
| */ | */ | ||||
| WxCpChatService getChatService(); | WxCpChatService getChatService(); | ||||
| /** | |||||
| * 获取任务卡片服务 | |||||
| * | |||||
| * @return 任务卡片服务 | |||||
| */ | |||||
| WxCpTaskCardService getTaskCardService(); | |||||
| WxCpAgentService getAgentService(); | WxCpAgentService getAgentService(); | ||||
| WxCpOAService getOAService(); | |||||
| /** | /** | ||||
| * http请求对象 | * http请求对象 | ||||
| */ | */ | ||||
| @@ -0,0 +1,30 @@ | |||||
| package me.chanjar.weixin.cp.api; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 任务卡片管理接口. | |||||
| * Created by Jeff on 2019-05-16. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/domainname">Jeff</a> | |||||
| * @date 2019-05-16 | |||||
| */ | |||||
| public interface WxCpTaskCardService { | |||||
| /** | |||||
| * <pre> | |||||
| * 更新任务卡片消息状态 | |||||
| * 详情请见: https://work.weixin.qq.com/api/doc#90000/90135/91579 | |||||
| * | |||||
| * 注意: 这个方法使用WxCpConfigStorage里的agentId | |||||
| * </pre> | |||||
| * | |||||
| * @param userIds 企业的成员ID列表 | |||||
| * @param taskId 任务卡片ID | |||||
| * @param clickedKey 已点击按钮的Key | |||||
| */ | |||||
| void update(List<String> userIds, String taskId, String clickedKey) throws WxErrorException; | |||||
| } | |||||
| @@ -1,16 +1,10 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | package me.chanjar.weixin.cp.api.impl; | ||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import com.google.common.base.Joiner; | |||||
| import com.google.gson.JsonArray; | import com.google.gson.JsonArray; | ||||
| import com.google.gson.JsonElement; | import com.google.gson.JsonElement; | ||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||
| import com.google.gson.JsonParser; | import com.google.gson.JsonParser; | ||||
| import me.chanjar.weixin.common.bean.WxJsapiSignature; | import me.chanjar.weixin.common.bean.WxJsapiSignature; | ||||
| import me.chanjar.weixin.common.error.WxError; | import me.chanjar.weixin.common.error.WxError; | ||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| @@ -24,20 +18,23 @@ import me.chanjar.weixin.common.util.http.RequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | import me.chanjar.weixin.common.util.http.RequestHttp; | ||||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | ||||
| import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | ||||
| import me.chanjar.weixin.cp.api.WxCpAgentService; | |||||
| import me.chanjar.weixin.cp.api.WxCpChatService; | |||||
| import me.chanjar.weixin.cp.api.WxCpDepartmentService; | |||||
| import me.chanjar.weixin.cp.api.WxCpMediaService; | |||||
| import me.chanjar.weixin.cp.api.WxCpMenuService; | |||||
| import me.chanjar.weixin.cp.api.WxCpOAuth2Service; | |||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import me.chanjar.weixin.cp.api.WxCpTagService; | |||||
| import me.chanjar.weixin.cp.api.WxCpUserService; | |||||
| 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.WxCpMessage; | ||||
| import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | ||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | import me.chanjar.weixin.cp.config.WxCpConfigStorage; | ||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.io.File; | |||||
| import java.io.IOException; | |||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, RequestHttp<H, P> { | |||||
| /** | |||||
| * @author chanjarster | |||||
| */ | |||||
| public abstract class BaseWxCpServiceImpl<H, P> implements WxCpService, RequestHttp<H, P> { | |||||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | protected final Logger log = LoggerFactory.getLogger(this.getClass()); | ||||
| private WxCpUserService userService = new WxCpUserServiceImpl(this); | private WxCpUserService userService = new WxCpUserServiceImpl(this); | ||||
| @@ -48,6 +45,8 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||||
| private WxCpOAuth2Service oauth2Service = new WxCpOAuth2ServiceImpl(this); | private WxCpOAuth2Service oauth2Service = new WxCpOAuth2ServiceImpl(this); | ||||
| private WxCpTagService tagService = new WxCpTagServiceImpl(this); | private WxCpTagService tagService = new WxCpTagServiceImpl(this); | ||||
| private WxCpAgentService agentService = new WxCpAgentServiceImpl(this); | private WxCpAgentService agentService = new WxCpAgentServiceImpl(this); | ||||
| private WxCpOAService oaService = new WxCpOAServiceImpl(this); | |||||
| private WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this); | |||||
| /** | /** | ||||
| * 全局的是否正在刷新access token的锁 | * 全局的是否正在刷新access token的锁 | ||||
| @@ -59,14 +58,19 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||||
| */ | */ | ||||
| protected final Object globalJsapiTicketRefreshLock = new Object(); | protected final Object globalJsapiTicketRefreshLock = new Object(); | ||||
| /** | |||||
| * 全局的是否正在刷新agent的jsapi_ticket的锁 | |||||
| */ | |||||
| protected final Object globalAgentJsapiTicketRefreshLock = new Object(); | |||||
| protected WxCpConfigStorage configStorage; | protected WxCpConfigStorage configStorage; | ||||
| private WxSessionManager sessionManager = new StandardSessionManager(); | |||||
| protected WxSessionManager sessionManager = new StandardSessionManager(); | |||||
| /** | /** | ||||
| * 临时文件目录 | * 临时文件目录 | ||||
| */ | */ | ||||
| protected File tmpDirFile; | |||||
| private File tmpDirFile; | |||||
| private int retrySleepMillis = 1000; | private int retrySleepMillis = 1000; | ||||
| private int maxRetryTimes = 5; | private int maxRetryTimes = 5; | ||||
| @@ -86,6 +90,30 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||||
| return getAccessToken(false); | return getAccessToken(false); | ||||
| } | } | ||||
| @Override | |||||
| public String getAgentJsapiTicket() throws WxErrorException { | |||||
| return this.getAgentJsapiTicket(false); | |||||
| } | |||||
| @Override | |||||
| public String getAgentJsapiTicket(boolean forceRefresh) throws WxErrorException { | |||||
| if (forceRefresh) { | |||||
| this.configStorage.expireAgentJsapiTicket(); | |||||
| } | |||||
| if (this.configStorage.isAgentJsapiTicketExpired()) { | |||||
| synchronized (this.globalAgentJsapiTicketRefreshLock) { | |||||
| if (this.configStorage.isAgentJsapiTicketExpired()) { | |||||
| String responseContent = this.get(WxCpService.GET_AGENT_CONFIG_TICKET, null); | |||||
| JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); | |||||
| this.configStorage.updateAgentJsapiTicket(jsonObject.get("ticket").getAsString(), | |||||
| jsonObject.get("expires_in").getAsInt()); | |||||
| } | |||||
| } | |||||
| } | |||||
| return this.configStorage.getAgentJsapiTicket(); | |||||
| } | |||||
| @Override | @Override | ||||
| public String getJsapiTicket() throws WxErrorException { | public String getJsapiTicket() throws WxErrorException { | ||||
| @@ -97,20 +125,18 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||||
| if (forceRefresh) { | if (forceRefresh) { | ||||
| this.configStorage.expireJsapiTicket(); | this.configStorage.expireJsapiTicket(); | ||||
| } | } | ||||
| if (this.configStorage.isJsapiTicketExpired()) { | if (this.configStorage.isJsapiTicketExpired()) { | ||||
| synchronized (this.globalJsapiTicketRefreshLock) { | synchronized (this.globalJsapiTicketRefreshLock) { | ||||
| if (this.configStorage.isJsapiTicketExpired()) { | if (this.configStorage.isJsapiTicketExpired()) { | ||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket"; | |||||
| String responseContent = execute(SimpleGetRequestExecutor.create(this), url, null); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||||
| String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||||
| int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||||
| this.configStorage.updateJsapiTicket(jsapiTicket, | |||||
| expiresInSeconds); | |||||
| String responseContent = this.get(WxCpService.GET_JSAPI_TICKET, null); | |||||
| JsonObject tmpJsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); | |||||
| this.configStorage.updateJsapiTicket(tmpJsonObject.get("ticket").getAsString(), | |||||
| tmpJsonObject.get("expires_in").getAsInt()); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| return this.configStorage.getJsapiTicket(); | return this.configStorage.getJsapiTicket(); | ||||
| } | } | ||||
| @@ -139,18 +165,27 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||||
| @Override | @Override | ||||
| public WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException { | public WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException { | ||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; | |||||
| Integer agentId = message.getAgentId(); | Integer agentId = message.getAgentId(); | ||||
| if(null == agentId){ | |||||
| if (null == agentId) { | |||||
| message.setAgentId(this.getWxCpConfigStorage().getAgentId()); | message.setAgentId(this.getWxCpConfigStorage().getAgentId()); | ||||
| } | } | ||||
| return WxCpMessageSendResult.fromJson(this.post(url, message.toJson())); | |||||
| return WxCpMessageSendResult.fromJson(this.post(WxCpService.MESSAGE_SEND, message.toJson())); | |||||
| } | |||||
| @Override | |||||
| public WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException { | |||||
| Map<String, String> params = new HashMap<>(2); | |||||
| params.put("js_code", jsCode); | |||||
| params.put("grant_type", "authorization_code"); | |||||
| String result = this.get(JSCODE_TO_SESSION_URL, Joiner.on("&").withKeyValueSeparator("=").join(params)); | |||||
| return WxCpMaJsCode2SessionResult.fromJson(result); | |||||
| } | } | ||||
| @Override | @Override | ||||
| public String[] getCallbackIp() throws WxErrorException { | public String[] getCallbackIp() throws WxErrorException { | ||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/getcallbackip"; | |||||
| String responseContent = get(url, null); | |||||
| String responseContent = get(WxCpService.GET_CALLBACK_IP, null); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | ||||
| JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray(); | JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray(); | ||||
| String[] ips = new String[jsonArray.size()]; | String[] ips = new String[jsonArray.size()]; | ||||
| @@ -171,7 +206,7 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||||
| } | } | ||||
| /** | /** | ||||
| * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | |||||
| * 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求. | |||||
| */ | */ | ||||
| @Override | @Override | ||||
| public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException { | ||||
| @@ -285,25 +320,28 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||||
| this.sessionManager = sessionManager; | this.sessionManager = sessionManager; | ||||
| } | } | ||||
| @Override | |||||
| public WxSessionManager getSessionManager() { | |||||
| return this.sessionManager; | |||||
| } | |||||
| @Override | @Override | ||||
| public String replaceParty(String mediaId) throws WxErrorException { | public String replaceParty(String mediaId) throws WxErrorException { | ||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty"; | |||||
| JsonObject jsonObject = new JsonObject(); | JsonObject jsonObject = new JsonObject(); | ||||
| jsonObject.addProperty("media_id", mediaId); | jsonObject.addProperty("media_id", mediaId); | ||||
| return post(url, jsonObject.toString()); | |||||
| return post(WxCpService.BATCH_REPLACE_PARTY, jsonObject.toString()); | |||||
| } | } | ||||
| @Override | @Override | ||||
| public String replaceUser(String mediaId) throws WxErrorException { | public String replaceUser(String mediaId) throws WxErrorException { | ||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser"; | |||||
| JsonObject jsonObject = new JsonObject(); | JsonObject jsonObject = new JsonObject(); | ||||
| jsonObject.addProperty("media_id", mediaId); | jsonObject.addProperty("media_id", mediaId); | ||||
| return post(url, jsonObject.toString()); | |||||
| return post(WxCpService.BATCH_REPLACE_USER, jsonObject.toString()); | |||||
| } | } | ||||
| @Override | @Override | ||||
| public String getTaskResult(String joinId) throws WxErrorException { | public String getTaskResult(String joinId) throws WxErrorException { | ||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/getresult?jobid=" + joinId; | |||||
| String url = WxCpService.BATCH_GET_RESULT + joinId; | |||||
| return get(url, null); | return get(url, null); | ||||
| } | } | ||||
| @@ -350,6 +388,16 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||||
| return chatService; | return chatService; | ||||
| } | } | ||||
| @Override | |||||
| public WxCpOAService getOAService() { | |||||
| return oaService; | |||||
| } | |||||
| @Override | |||||
| public WxCpTaskCardService getTaskCardService() { | |||||
| return taskCardService; | |||||
| } | |||||
| @Override | @Override | ||||
| public RequestHttp<?, ?> getRequestHttp() { | public RequestHttp<?, ?> getRequestHttp() { | ||||
| return this; | return this; | ||||
| @@ -1,36 +1,35 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | package me.chanjar.weixin.cp.api.impl; | ||||
| import java.util.HashMap; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import com.google.gson.JsonParser; | import com.google.gson.JsonParser; | ||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | import me.chanjar.weixin.common.util.json.WxGsonBuilder; | ||||
| import me.chanjar.weixin.cp.api.WxCpChatService; | import me.chanjar.weixin.cp.api.WxCpChatService; | ||||
| import me.chanjar.weixin.cp.api.WxCpService; | import me.chanjar.weixin.cp.api.WxCpService; | ||||
| import me.chanjar.weixin.cp.bean.WxCpAppChatMessage; | |||||
| import me.chanjar.weixin.cp.bean.WxCpChat; | import me.chanjar.weixin.cp.bean.WxCpChat; | ||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | ||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import java.util.HashMap; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| /** | /** | ||||
| * 群聊服务实现 | |||||
| * 群聊服务实现. | |||||
| * | * | ||||
| * @author gaigeshen | * @author gaigeshen | ||||
| */ | */ | ||||
| public class WxCpChatServiceImpl implements WxCpChatService { | |||||
| public class WxCpChatServiceImpl implements WxCpChatService { | |||||
| private static final JsonParser JSON_PARSER = new JsonParser(); | |||||
| private final WxCpService cpService; | |||||
| private final WxCpService internalService; | |||||
| /** | /** | ||||
| * 创建群聊服务实现的实例 | |||||
| * | |||||
| * @param internalService 企业微信的服务 | |||||
| * 创建群聊服务实现的实例. | |||||
| * | |||||
| * @param cpService 企业微信的服务 | |||||
| */ | */ | ||||
| public WxCpChatServiceImpl(WxCpService internalService) { | |||||
| this.internalService = internalService; | |||||
| WxCpChatServiceImpl(WxCpService cpService) { | |||||
| this.cpService = cpService; | |||||
| } | } | ||||
| @Override | @Override | ||||
| @@ -48,12 +47,18 @@ public class WxCpChatServiceImpl implements WxCpChatService { | |||||
| if (StringUtils.isNotBlank(chatId)) { | if (StringUtils.isNotBlank(chatId)) { | ||||
| data.put("chatid", chatId); | data.put("chatid", chatId); | ||||
| } | } | ||||
| String result = internalService.post("https://qyapi.weixin.qq.com/cgi-bin/appchat/create", WxGsonBuilder.create().toJson(data)); | |||||
| String result = this.cpService.post(APPCHAT_CREATE, WxGsonBuilder.create().toJson(data)); | |||||
| return new JsonParser().parse(result).getAsJsonObject().get("chatid").getAsString(); | return new JsonParser().parse(result).getAsJsonObject().get("chatid").getAsString(); | ||||
| } | } | ||||
| @Override | @Override | ||||
| public void chatUpdate(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException { | |||||
| public String create(String name, String owner, List<String> users, String chatId) throws WxErrorException { | |||||
| return chatCreate(name, owner, users, chatId); | |||||
| } | |||||
| @Override | |||||
| public void chatUpdate(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) | |||||
| throws WxErrorException { | |||||
| Map<String, Object> data = new HashMap<>(5); | Map<String, Object> data = new HashMap<>(5); | ||||
| if (StringUtils.isNotBlank(chatId)) { | if (StringUtils.isNotBlank(chatId)) { | ||||
| data.put("chatid", chatId); | data.put("chatid", chatId); | ||||
| @@ -70,14 +75,30 @@ public class WxCpChatServiceImpl implements WxCpChatService { | |||||
| if (usersToDelete != null && !usersToDelete.isEmpty()) { | if (usersToDelete != null && !usersToDelete.isEmpty()) { | ||||
| data.put("del_user_list", usersToDelete); | data.put("del_user_list", usersToDelete); | ||||
| } | } | ||||
| internalService.post("https://qyapi.weixin.qq.com/cgi-bin/appchat/update", WxGsonBuilder.create().toJson(data)); | |||||
| this.cpService.post(APPCHAT_UPDATE, WxGsonBuilder.create().toJson(data)); | |||||
| } | |||||
| @Override | |||||
| public void update(String chatId, String name, String owner, List<String> usersToAdd, List<String> usersToDelete) throws WxErrorException { | |||||
| chatUpdate(chatId, name, owner, usersToAdd, usersToDelete); | |||||
| } | } | ||||
| @Override | @Override | ||||
| public WxCpChat chatGet(String chatId) throws WxErrorException { | public WxCpChat chatGet(String chatId) throws WxErrorException { | ||||
| String result = internalService.get("https://qyapi.weixin.qq.com/cgi-bin/appchat/get?chatid=" + chatId, null); | |||||
| return WxCpGsonBuilder.create().fromJson( | |||||
| new JsonParser().parse(result).getAsJsonObject().getAsJsonObject("chat_info").toString(), WxCpChat.class); | |||||
| String result = this.cpService.get(APPCHAT_GET_CHATID + chatId, null); | |||||
| return WxCpGsonBuilder.create() | |||||
| .fromJson(JSON_PARSER.parse(result).getAsJsonObject().getAsJsonObject("chat_info").toString(), WxCpChat.class); | |||||
| } | |||||
| @Override | |||||
| public WxCpChat get(String chatId) throws WxErrorException { | |||||
| return chatGet(chatId); | |||||
| } | |||||
| @Override | |||||
| public void sendMsg(WxCpAppChatMessage message) throws WxErrorException { | |||||
| this.cpService.post("https://qyapi.weixin.qq.com/cgi-bin/appchat/send", message.toJson()); | |||||
| } | } | ||||
| } | } | ||||
| @@ -28,11 +28,11 @@ public class WxCpDepartmentServiceImpl implements WxCpDepartmentService { | |||||
| } | } | ||||
| @Override | @Override | ||||
| public Integer create(WxCpDepart depart) throws WxErrorException { | |||||
| public Long create(WxCpDepart depart) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create"; | String url = "https://qyapi.weixin.qq.com/cgi-bin/department/create"; | ||||
| String responseContent = this.mainService.post(url, depart.toJson()); | String responseContent = this.mainService.post(url, depart.toJson()); | ||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | ||||
| return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id")); | |||||
| return GsonHelper.getAsLong(tmpJsonElement.getAsJsonObject().get("id")); | |||||
| } | } | ||||
| @Override | @Override | ||||
| @@ -0,0 +1,165 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import com.google.gson.JsonArray; | |||||
| import com.google.gson.JsonElement; | |||||
| import com.google.gson.JsonObject; | |||||
| import com.google.gson.JsonParser; | |||||
| import com.google.gson.reflect.TypeToken; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.cp.api.WxCpOAService; | |||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import me.chanjar.weixin.cp.bean.WxCpApprovalDataResult; | |||||
| import me.chanjar.weixin.cp.bean.WxCpCheckinData; | |||||
| import me.chanjar.weixin.cp.bean.WxCpCheckinOption; | |||||
| import me.chanjar.weixin.cp.bean.WxCpDialRecord; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| import java.util.Date; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @author Element | |||||
| * @Package me.chanjar.weixin.cp.api.impl | |||||
| * @date 2019-04-06 11:20 | |||||
| * @Description: TODO | |||||
| */ | |||||
| public class WxCpOAServiceImpl implements WxCpOAService { | |||||
| private WxCpService mainService; | |||||
| public WxCpOAServiceImpl(WxCpService mainService) { | |||||
| this.mainService = mainService; | |||||
| } | |||||
| @Override | |||||
| public List<WxCpCheckinData> getCheckinData(Integer openCheckinDataType, Date starttime, Date endtime, List<String> userIdList) throws WxErrorException { | |||||
| if (starttime == null || endtime == null) { | |||||
| throw new RuntimeException("starttime and endtime can't be null"); | |||||
| } | |||||
| if (userIdList == null || userIdList.size() > 100) { | |||||
| throw new RuntimeException("用户列表不能为空,不超过100个,若用户超过100个,请分批获取"); | |||||
| } | |||||
| long endtimestamp = endtime.getTime() / 1000L; | |||||
| long starttimestamp = starttime.getTime() / 1000L; | |||||
| if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= 30 * 24 * 60 * 60) { | |||||
| throw new RuntimeException("获取记录时间跨度不超过一个月"); | |||||
| } | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckindata"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| jsonObject.addProperty("opencheckindatatype", openCheckinDataType); | |||||
| jsonObject.addProperty("starttime", starttimestamp); | |||||
| jsonObject.addProperty("endtime", endtimestamp); | |||||
| for (String userid : userIdList) { | |||||
| jsonArray.add(userid); | |||||
| } | |||||
| jsonObject.add("useridlist", jsonArray); | |||||
| String responseContent = this.mainService.post(url, jsonObject.toString()); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.create() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("checkindata"), | |||||
| new TypeToken<List<WxCpCheckinData>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpCheckinOption> getCheckinOption(Date datetime, List<String> userIdList) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/checkin/getcheckinoption"; | |||||
| if (datetime == null) { | |||||
| throw new RuntimeException("datetime can't be null"); | |||||
| } | |||||
| if (userIdList == null || userIdList.size() > 100) { | |||||
| throw new RuntimeException("用户列表不能为空,不超过100个,若用户超过100个,请分批获取"); | |||||
| } | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| for (String userid : userIdList) { | |||||
| jsonArray.add(userid); | |||||
| } | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("datetime", datetime.getTime() / 1000L); | |||||
| jsonObject.add("useridlist", jsonArray); | |||||
| String responseContent = this.mainService.post(url, jsonObject.toString()); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.create() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("info"), | |||||
| new TypeToken<List<WxCpCheckinOption>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| @Override | |||||
| public WxCpApprovalDataResult getApprovalData(Date starttime, Date endtime, Long nextSpnum) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/corp/getapprovaldata"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| jsonObject.addProperty("starttime", starttime.getTime() / 1000L); | |||||
| jsonObject.addProperty("endtime", endtime.getTime() / 1000L); | |||||
| if (nextSpnum != null) { | |||||
| jsonObject.addProperty("next_spnum", nextSpnum); | |||||
| } | |||||
| String responseContent = this.mainService.post(url, jsonObject.toString()); | |||||
| return WxCpGsonBuilder.create().fromJson(responseContent, WxCpApprovalDataResult.class); | |||||
| } | |||||
| @Override | |||||
| public List<WxCpDialRecord> getDialRecord(Date starttime, Date endtime, Integer offset, Integer limit) throws WxErrorException { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/dial/get_dial_record"; | |||||
| JsonObject jsonObject = new JsonObject(); | |||||
| if (offset == null) { | |||||
| offset = 0; | |||||
| } | |||||
| if (limit == null || limit <= 0) { | |||||
| limit = 100; | |||||
| } | |||||
| jsonObject.addProperty("offset", offset); | |||||
| jsonObject.addProperty("limit", limit); | |||||
| if (starttime != null && endtime != null) { | |||||
| long endtimestamp = endtime.getTime() / 1000L; | |||||
| long starttimestamp = starttime.getTime() / 1000L; | |||||
| if (endtimestamp - starttimestamp < 0 || endtimestamp - starttimestamp >= 30 * 24 * 60 * 60) { | |||||
| throw new RuntimeException("受限于网络传输,起止时间的最大跨度为30天,如超过30天,则以结束时间为基准向前取30天进行查询"); | |||||
| } | |||||
| jsonObject.addProperty("start_time", starttimestamp); | |||||
| jsonObject.addProperty("end_time", endtimestamp); | |||||
| } | |||||
| String responseContent = this.mainService.post(url, jsonObject.toString()); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return WxCpGsonBuilder.create() | |||||
| .fromJson( | |||||
| tmpJsonElement.getAsJsonObject().get("record"), | |||||
| new TypeToken<List<WxCpDialRecord>>() { | |||||
| }.getType() | |||||
| ); | |||||
| } | |||||
| } | |||||
| @@ -8,6 +8,7 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.HttpType; | 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.ApacheHttpClientBuilder; | ||||
| import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | ||||
| import me.chanjar.weixin.cp.api.WxCpOAService; | |||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | import me.chanjar.weixin.cp.config.WxCpConfigStorage; | ||||
| import org.apache.http.HttpHost; | import org.apache.http.HttpHost; | ||||
| import org.apache.http.client.config.RequestConfig; | import org.apache.http.client.config.RequestConfig; | ||||
| @@ -18,7 +19,7 @@ import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| public class WxCpServiceApacheHttpClientImpl extends WxCpServiceAbstractImpl<CloseableHttpClient, HttpHost> { | |||||
| public class WxCpServiceApacheHttpClientImpl extends BaseWxCpServiceImpl<CloseableHttpClient, HttpHost> { | |||||
| protected CloseableHttpClient httpClient; | protected CloseableHttpClient httpClient; | ||||
| protected HttpHost httpProxy; | protected HttpHost httpProxy; | ||||
| @@ -39,37 +40,37 @@ public class WxCpServiceApacheHttpClientImpl extends WxCpServiceAbstractImpl<Clo | |||||
| @Override | @Override | ||||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | public String getAccessToken(boolean forceRefresh) throws WxErrorException { | ||||
| if (this.configStorage.isAccessTokenExpired() || forceRefresh) { | |||||
| synchronized (this.globalAccessTokenRefreshLock) { | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
| + "&corpid=" + this.configStorage.getCorpId() | |||||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||||
| try { | |||||
| HttpGet httpGet = new HttpGet(url); | |||||
| if (this.httpProxy != null) { | |||||
| RequestConfig config = RequestConfig.custom() | |||||
| .setProxy(this.httpProxy).build(); | |||||
| httpGet.setConfig(config); | |||||
| } | |||||
| String resultContent = null; | |||||
| try (CloseableHttpClient httpclient = getRequestHttpClient(); | |||||
| CloseableHttpResponse response = httpclient.execute(httpGet)) { | |||||
| resultContent = new BasicResponseHandler().handleResponse(response); | |||||
| } finally { | |||||
| httpGet.releaseConnection(); | |||||
| } | |||||
| WxError error = WxError.fromJson(resultContent, WxType.CP); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.configStorage.updateAccessToken( | |||||
| accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| if (!this.configStorage.isAccessTokenExpired() && !forceRefresh) { | |||||
| return this.configStorage.getAccessToken(); | |||||
| } | |||||
| synchronized (this.globalAccessTokenRefreshLock) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
| + "&corpid=" + this.configStorage.getCorpId() | |||||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||||
| try { | |||||
| HttpGet httpGet = new HttpGet(url); | |||||
| if (this.httpProxy != null) { | |||||
| RequestConfig config = RequestConfig.custom() | |||||
| .setProxy(this.httpProxy).build(); | |||||
| httpGet.setConfig(config); | |||||
| } | |||||
| String resultContent; | |||||
| try (CloseableHttpClient httpclient = getRequestHttpClient(); | |||||
| CloseableHttpResponse response = httpclient.execute(httpGet)) { | |||||
| resultContent = new BasicResponseHandler().handleResponse(response); | |||||
| } finally { | |||||
| httpGet.releaseConnection(); | |||||
| } | } | ||||
| WxError error = WxError.fromJson(resultContent, WxType.CP); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
| } catch (IOException e) { | |||||
| throw new RuntimeException(e); | |||||
| } | } | ||||
| } | } | ||||
| return this.configStorage.getAccessToken(); | return this.configStorage.getAccessToken(); | ||||
| @@ -8,7 +8,7 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.HttpType; | import me.chanjar.weixin.common.util.http.HttpType; | ||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | import me.chanjar.weixin.cp.config.WxCpConfigStorage; | ||||
| public class WxCpServiceJoddHttpImpl extends WxCpServiceAbstractImpl<HttpConnectionProvider, ProxyInfo> { | |||||
| public class WxCpServiceJoddHttpImpl extends BaseWxCpServiceImpl<HttpConnectionProvider, ProxyInfo> { | |||||
| protected HttpConnectionProvider httpClient; | protected HttpConnectionProvider httpClient; | ||||
| protected ProxyInfo httpProxy; | protected ProxyInfo httpProxy; | ||||
| @@ -30,30 +30,29 @@ public class WxCpServiceJoddHttpImpl extends WxCpServiceAbstractImpl<HttpConnect | |||||
| @Override | @Override | ||||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | public String getAccessToken(boolean forceRefresh) throws WxErrorException { | ||||
| if (this.configStorage.isAccessTokenExpired() || forceRefresh) { | |||||
| synchronized (this.globalAccessTokenRefreshLock) { | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
| + "&corpid=" + this.configStorage.getCorpId() | |||||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||||
| if (!this.configStorage.isAccessTokenExpired() && !forceRefresh) { | |||||
| return this.configStorage.getAccessToken(); | |||||
| } | |||||
| synchronized (this.globalAccessTokenRefreshLock) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
| + "&corpid=" + this.configStorage.getCorpId() | |||||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||||
| HttpRequest request = HttpRequest.get(url); | |||||
| if (this.httpProxy != null) { | |||||
| httpClient.useProxy(this.httpProxy); | |||||
| } | |||||
| request.withConnectionProvider(httpClient); | |||||
| HttpResponse response = request.send(); | |||||
| HttpRequest request = HttpRequest.get(url); | |||||
| if (this.httpProxy != null) { | |||||
| httpClient.useProxy(this.httpProxy); | |||||
| } | |||||
| request.withConnectionProvider(httpClient); | |||||
| HttpResponse response = request.send(); | |||||
| String resultContent = response.bodyText(); | |||||
| WxError error = WxError.fromJson(resultContent, WxType.CP); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.configStorage.updateAccessToken( | |||||
| accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
| } | |||||
| String resultContent = response.bodyText(); | |||||
| WxError error = WxError.fromJson(resultContent, WxType.CP); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | } | ||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.configStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
| } | } | ||||
| return this.configStorage.getAccessToken(); | return this.configStorage.getAccessToken(); | ||||
| } | } | ||||
| @@ -11,7 +11,7 @@ import okhttp3.*; | |||||
| import java.io.IOException; | import java.io.IOException; | ||||
| public class WxCpServiceOkHttpImpl extends WxCpServiceAbstractImpl<OkHttpClient, OkHttpProxyInfo> { | |||||
| public class WxCpServiceOkHttpImpl extends BaseWxCpServiceImpl<OkHttpClient, OkHttpProxyInfo> { | |||||
| protected OkHttpClient httpClient; | protected OkHttpClient httpClient; | ||||
| protected OkHttpProxyInfo httpProxy; | protected OkHttpProxyInfo httpProxy; | ||||
| @@ -33,34 +33,33 @@ public class WxCpServiceOkHttpImpl extends WxCpServiceAbstractImpl<OkHttpClient, | |||||
| @Override | @Override | ||||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | public String getAccessToken(boolean forceRefresh) throws WxErrorException { | ||||
| this.log.debug("WxCpServiceOkHttpImpl is running"); | |||||
| if (this.configStorage.isAccessTokenExpired() || forceRefresh) { | |||||
| synchronized (this.globalAccessTokenRefreshLock) { | |||||
| if (this.configStorage.isAccessTokenExpired()) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
| + "&corpid=" + this.configStorage.getCorpId() | |||||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||||
| //得到httpClient | |||||
| OkHttpClient client = getRequestHttpClient(); | |||||
| //请求的request | |||||
| Request request = new Request.Builder().url(url).get().build(); | |||||
| String resultContent = null; | |||||
| try { | |||||
| Response response = client.newCall(request).execute(); | |||||
| resultContent = response.body().string(); | |||||
| } catch (IOException e) { | |||||
| this.log.error(e.getMessage(), e); | |||||
| } | |||||
| if (!this.configStorage.isAccessTokenExpired() && !forceRefresh) { | |||||
| return this.configStorage.getAccessToken(); | |||||
| } | |||||
| synchronized (this.globalAccessTokenRefreshLock) { | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken?" | |||||
| + "&corpid=" + this.configStorage.getCorpId() | |||||
| + "&corpsecret=" + this.configStorage.getCorpSecret(); | |||||
| //得到httpClient | |||||
| OkHttpClient client = getRequestHttpClient(); | |||||
| //请求的request | |||||
| Request request = new Request.Builder().url(url).get().build(); | |||||
| String resultContent = null; | |||||
| try { | |||||
| Response response = client.newCall(request).execute(); | |||||
| resultContent = response.body().string(); | |||||
| } catch (IOException e) { | |||||
| this.log.error(e.getMessage(), e); | |||||
| } | |||||
| WxError error = WxError.fromJson(resultContent, WxType.CP); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.configStorage.updateAccessToken(accessToken.getAccessToken(), | |||||
| accessToken.getExpiresIn()); | |||||
| WxError error = WxError.fromJson(resultContent, WxType.CP); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | } | ||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.configStorage.updateAccessToken(accessToken.getAccessToken(), | |||||
| accessToken.getExpiresIn()); | |||||
| } | } | ||||
| return this.configStorage.getAccessToken(); | return this.configStorage.getAccessToken(); | ||||
| } | } | ||||
| @@ -0,0 +1,39 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import lombok.RequiredArgsConstructor; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import me.chanjar.weixin.cp.api.WxCpTaskCardService; | |||||
| import java.util.HashMap; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| /** | |||||
| * <pre> | |||||
| * 任务卡片管理接口. | |||||
| * Created by Jeff on 2019-05-16. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/domainname">Jeff</a> | |||||
| * @date 2019-05-16 | |||||
| */ | |||||
| @RequiredArgsConstructor | |||||
| public class WxCpTaskCardServiceImpl implements WxCpTaskCardService { | |||||
| private final WxCpService mainService; | |||||
| @Override | |||||
| public void update(List<String> userIds, String taskId, String clickedKey) throws WxErrorException { | |||||
| Integer agentId = this.mainService.getWxCpConfigStorage().getAgentId(); | |||||
| Map<String, Object> data = new HashMap<>(4); | |||||
| data.put("userids", userIds); | |||||
| data.put("agentid", agentId); | |||||
| data.put("task_id", taskId); | |||||
| data.put("clicked_key", clickedKey); | |||||
| String url = "https://qyapi.weixin.qq.com/cgi-bin/message/update_taskcard"; | |||||
| this.mainService.post(url, WxGsonBuilder.create().toJson(data)); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,163 @@ | |||||
| 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.WxCpConsts.AppChatMsgType; | |||||
| import me.chanjar.weixin.cp.bean.article.MpnewsArticle; | |||||
| import me.chanjar.weixin.cp.bean.article.NewArticle; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 应用推送消息 | |||||
| * Created by Binary Wang on 2019/1/26. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| @Data | |||||
| @Builder | |||||
| @NoArgsConstructor | |||||
| @AllArgsConstructor | |||||
| public class WxCpAppChatMessage implements Serializable { | |||||
| private static final long serialVersionUID = -5469013416372240229L; | |||||
| private String msgType; | |||||
| private String content; | |||||
| private String chatId; | |||||
| private String mediaId; | |||||
| private String title; | |||||
| private String description; | |||||
| private Boolean safe; | |||||
| private String url; | |||||
| private String btnTxt; | |||||
| private List<NewArticle> articles; | |||||
| private List<MpnewsArticle> mpnewsArticles; | |||||
| /** | |||||
| * 构建文本消息. | |||||
| */ | |||||
| public static WxCpAppChatMessage buildTextMsg(String chatId, String content, boolean safe) { | |||||
| final WxCpAppChatMessage message = new WxCpAppChatMessage(); | |||||
| message.setMsgType(AppChatMsgType.TEXT); | |||||
| message.setContent(content); | |||||
| message.setChatId(chatId); | |||||
| message.setSafe(safe); | |||||
| return message; | |||||
| } | |||||
| /** | |||||
| * 生成json字符串. | |||||
| */ | |||||
| public String toJson() { | |||||
| JsonObject messageJson = new JsonObject(); | |||||
| messageJson.addProperty("msgtype", this.getMsgType()); | |||||
| messageJson.addProperty("chatid", this.getChatId()); | |||||
| if (this.getSafe() != null && this.getSafe()) { | |||||
| messageJson.addProperty("safe", 1); | |||||
| } | |||||
| this.handleMsgType(messageJson); | |||||
| return messageJson.toString(); | |||||
| } | |||||
| private void handleMsgType(JsonObject messageJson) { | |||||
| switch (this.getMsgType()) { | |||||
| case AppChatMsgType.TEXT: { | |||||
| JsonObject text = new JsonObject(); | |||||
| text.addProperty("content", this.getContent()); | |||||
| messageJson.add("text", text); | |||||
| break; | |||||
| } | |||||
| case AppChatMsgType.MARKDOWN: { | |||||
| JsonObject text = new JsonObject(); | |||||
| text.addProperty("content", this.getContent()); | |||||
| messageJson.add("markdown", text); | |||||
| break; | |||||
| } | |||||
| case AppChatMsgType.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 AppChatMsgType.IMAGE: { | |||||
| JsonObject image = new JsonObject(); | |||||
| image.addProperty("media_id", this.getMediaId()); | |||||
| messageJson.add("image", image); | |||||
| break; | |||||
| } | |||||
| case AppChatMsgType.FILE: { | |||||
| JsonObject image = new JsonObject(); | |||||
| image.addProperty("media_id", this.getMediaId()); | |||||
| messageJson.add("file", image); | |||||
| break; | |||||
| } | |||||
| case AppChatMsgType.VOICE: { | |||||
| JsonObject voice = new JsonObject(); | |||||
| voice.addProperty("media_id", this.getMediaId()); | |||||
| messageJson.add("voice", voice); | |||||
| break; | |||||
| } | |||||
| case AppChatMsgType.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 AppChatMsgType.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()); | |||||
| articleJsonArray.add(articleJson); | |||||
| } | |||||
| newsJsonObject.add("articles", articleJsonArray); | |||||
| messageJson.add("news", newsJsonObject); | |||||
| break; | |||||
| } | |||||
| case AppChatMsgType.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()); | |||||
| articleJsonArray.add(articleJson); | |||||
| } | |||||
| newsJsonObject.add("articles", articleJsonArray); | |||||
| } | |||||
| messageJson.add("mpnews", newsJsonObject); | |||||
| break; | |||||
| } | |||||
| default: { | |||||
| //do nothing | |||||
| } | |||||
| } | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,69 @@ | |||||
| package me.chanjar.weixin.cp.bean; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| import java.util.Map; | |||||
| /** | |||||
| * @author Element | |||||
| * @Package me.chanjar.weixin.cp.bean | |||||
| * @date 2019-04-06 14:36 | |||||
| * @Description: 企业微信 OA 审批数据 | |||||
| */ | |||||
| @Data | |||||
| public class WxCpApprovalDataResult implements Serializable { | |||||
| private static final long serialVersionUID = -1046940445840716590L; | |||||
| @SerializedName("errcode") | |||||
| private Integer errCode; | |||||
| @SerializedName("errmsg") | |||||
| private String errMsg; | |||||
| private Integer count; | |||||
| private Integer total; | |||||
| @SerializedName("next_spnum") | |||||
| private Long nextSpnum; | |||||
| private WxCpApprovalData[] data; | |||||
| @Data | |||||
| public static class WxCpApprovalData implements Serializable{ | |||||
| private static final long serialVersionUID = -3051785319608491640L; | |||||
| private String spname; | |||||
| @SerializedName("apply_name") | |||||
| private String applyName; | |||||
| @SerializedName("apply_org") | |||||
| private String applyOrg; | |||||
| @SerializedName("approval_name") | |||||
| private String[] approvalName; | |||||
| @SerializedName("notify_name") | |||||
| private String[] notifyName; | |||||
| @SerializedName("sp_status") | |||||
| private Integer spStatus; | |||||
| @SerializedName("sp_num") | |||||
| private Long spNum; | |||||
| @SerializedName("apply_time") | |||||
| private Long applyTime; | |||||
| @SerializedName("apply_user_id") | |||||
| private String applyUserId; | |||||
| @SerializedName("comm") | |||||
| private Map<String,String> comm; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,51 @@ | |||||
| package me.chanjar.weixin.cp.bean; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @author Element | |||||
| * @Package me.chanjar.weixin.cp.bean | |||||
| * @date 2019-04-06 11:01 | |||||
| * @Description: 企业微信打卡数据 | |||||
| */ | |||||
| @Data | |||||
| public class WxCpCheckinData implements Serializable { | |||||
| private static final long serialVersionUID = 1915820330847799605L; | |||||
| @SerializedName("userid") | |||||
| private String userId; | |||||
| @SerializedName("groupname") | |||||
| private String groupName; | |||||
| @SerializedName("checkin_type") | |||||
| private String checkinType; | |||||
| @SerializedName("exception_type") | |||||
| private String exceptionType; | |||||
| @SerializedName("checkin_time") | |||||
| private Long checkinTime; | |||||
| @SerializedName("location_title") | |||||
| private String locationTitle; | |||||
| @SerializedName("location_detail") | |||||
| private String locationDetail; | |||||
| @SerializedName("wifiname") | |||||
| private String wifiName; | |||||
| @SerializedName("wifimac") | |||||
| private String wifiMAC; | |||||
| private String notes; | |||||
| @SerializedName("mediaids") | |||||
| private List<String> mediaIds; | |||||
| } | |||||
| @@ -0,0 +1,151 @@ | |||||
| package me.chanjar.weixin.cp.bean; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @author Element | |||||
| * @Package me.chanjar.weixin.cp.bean | |||||
| * @date 2019-04-06 13:22 | |||||
| * @Description: 企业微信打卡规则 | |||||
| */ | |||||
| @Data | |||||
| public class WxCpCheckinOption implements Serializable { | |||||
| private static final long serialVersionUID = -1964233697990417482L; | |||||
| @SerializedName("userid") | |||||
| private String userId; | |||||
| private Group group; | |||||
| @Data | |||||
| public static class CheckinDate implements Serializable { | |||||
| private static final long serialVersionUID = -5601722383347110974L; | |||||
| private List<Integer> workdays; | |||||
| @SerializedName("checkintime") | |||||
| private CheckinTime[] checkinTime; | |||||
| @SerializedName("flex_time") | |||||
| private Long flexTime; | |||||
| @SerializedName("noneed_offwork") | |||||
| private Boolean noneedOffwork; | |||||
| @SerializedName("limit_aheadtime") | |||||
| private Long limitAheadtime; | |||||
| } | |||||
| @Data | |||||
| public static class CheckinTime implements Serializable { | |||||
| private static final long serialVersionUID = -8579954143265336276L; | |||||
| @SerializedName("work_sec") | |||||
| private Long workSec; | |||||
| @SerializedName("off_work_sec") | |||||
| private Long offWorkSec; | |||||
| @SerializedName("remind_work_sec") | |||||
| private Long remindWorkSec; | |||||
| @SerializedName("remind_off_work_sec") | |||||
| private Long remindOffWorkSec; | |||||
| } | |||||
| @Data | |||||
| public static class Group implements Serializable { | |||||
| private static final long serialVersionUID = -5888406969613403044L; | |||||
| @SerializedName("groupid") | |||||
| private Long id; | |||||
| @SerializedName("groupname") | |||||
| private String name; | |||||
| @SerializedName("grouptype") | |||||
| private Integer type; | |||||
| @SerializedName("checkindate") | |||||
| private List<CheckinDate> checkinDate; | |||||
| @SerializedName("spe_workdays") | |||||
| private List<SpeDay> speWorkdays; | |||||
| @SerializedName("spe_offdays") | |||||
| private List<SpeDay> speOffdays; | |||||
| @SerializedName("sync_holidays") | |||||
| private Boolean syncHolidays; | |||||
| @SerializedName("need_photo") | |||||
| private Boolean needPhoto; | |||||
| @SerializedName("note_can_use_local_pic") | |||||
| private Boolean note_can_use_local_pic; | |||||
| @SerializedName("allow_checkin_offworkday") | |||||
| private Boolean allow_checkin_offworkday; | |||||
| @SerializedName("allow_apply_offworkday") | |||||
| private Boolean allow_apply_offworkday; | |||||
| @SerializedName("wifimac_infos") | |||||
| private List<WifiMACInfo> wifiMACInfos; | |||||
| @SerializedName("loc_infos") | |||||
| private List<LocInfo> locInfos; | |||||
| } | |||||
| @Data | |||||
| public static class WifiMACInfo implements Serializable{ | |||||
| private static final long serialVersionUID = -4657809185716627368L; | |||||
| @SerializedName("wifiname") | |||||
| private String name; | |||||
| @SerializedName("wifimac") | |||||
| private String mac; | |||||
| } | |||||
| @Data | |||||
| public static class LocInfo implements Serializable{ | |||||
| private static final long serialVersionUID = -618965280668099608L; | |||||
| private Long lat; | |||||
| private Long lng; | |||||
| @SerializedName("loc_title") | |||||
| private String title; | |||||
| @SerializedName("loc_detail") | |||||
| private String detail; | |||||
| private Long distance; | |||||
| } | |||||
| @Data | |||||
| public static class SpeDay implements Serializable{ | |||||
| private static final long serialVersionUID = -3538818921359212748L; | |||||
| private Long timestamp; | |||||
| private String notes; | |||||
| @SerializedName("checkintime") | |||||
| private List<CheckinTime> checkinTime; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,73 @@ | |||||
| package me.chanjar.weixin.cp.bean; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @author Element | |||||
| * @Package me.chanjar.weixin.cp.bean | |||||
| * @date 2019-04-06 15:38 | |||||
| * @Description: 公费电话拨打记录 | |||||
| */ | |||||
| @Data | |||||
| public class WxCpDialRecord implements Serializable { | |||||
| private static final long serialVersionUID = 4178886812949929116L; | |||||
| @SerializedName("call_time") | |||||
| private Long callTime; | |||||
| /** | |||||
| * 总通话时长,单位为分钟 | |||||
| */ | |||||
| @SerializedName("total_duration") | |||||
| private Integer totalDuration; | |||||
| /** | |||||
| * 通话类型,1-单人通话 2-多人通话 | |||||
| */ | |||||
| @SerializedName("call_type") | |||||
| private Integer callType; | |||||
| private Caller caller; | |||||
| private List<Callee> callee; | |||||
| /** | |||||
| * 主叫信息 | |||||
| */ | |||||
| @Data | |||||
| public static class Caller implements Serializable{ | |||||
| private static final long serialVersionUID = 4792200404338145607L; | |||||
| @SerializedName("userid") | |||||
| private String userId; | |||||
| private Integer duration; | |||||
| } | |||||
| /** | |||||
| * 被叫信息 | |||||
| */ | |||||
| @Data | |||||
| public static class Callee implements Serializable{ | |||||
| private static final long serialVersionUID = 2390963671336179550L; | |||||
| /** | |||||
| * 被叫用户的userid,当被叫用户为企业内用户时返回 | |||||
| */ | |||||
| @SerializedName("userid") | |||||
| private String userId; | |||||
| /** | |||||
| * 被叫用户的号码,当被叫用户为外部用户时返回 | |||||
| */ | |||||
| private String phone; | |||||
| private Integer duration; | |||||
| } | |||||
| } | |||||
| @@ -37,7 +37,7 @@ public class WxCpInviteResult implements Serializable { | |||||
| private String errMsg; | private String errMsg; | ||||
| @SerializedName("invaliduser") | @SerializedName("invaliduser") | ||||
| private String invalidUsers; | |||||
| private String[] invalidUsers; | |||||
| @SerializedName("invalidparty") | @SerializedName("invalidparty") | ||||
| private String[] invalidParties; | private String[] invalidParties; | ||||
| @@ -45,16 +45,4 @@ public class WxCpInviteResult implements Serializable { | |||||
| @SerializedName("invalidtag") | @SerializedName("invalidtag") | ||||
| private String[] invalidTags; | private String[] invalidTags; | ||||
| public List<String> getInvalidUserList() { | |||||
| return this.content2List(this.invalidUsers); | |||||
| } | |||||
| private List<String> content2List(String content) { | |||||
| if (StringUtils.isBlank(content)) { | |||||
| return Collections.emptyList(); | |||||
| } | |||||
| return Splitter.on("|").splitToList(content); | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,33 @@ | |||||
| package me.chanjar.weixin.cp.bean; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.Data; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| import java.io.Serializable; | |||||
| /** | |||||
| * <pre> | |||||
| * 小程序登录凭证校验 | |||||
| * 文档地址:https://work.weixin.qq.com/api/doc#90000/90136/90289/wx.qy.login | |||||
| * </pre> | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| @Data | |||||
| public class WxCpMaJsCode2SessionResult implements Serializable { | |||||
| private static final long serialVersionUID = 6229609023682814765L; | |||||
| @SerializedName("session_key") | |||||
| private String sessionKey; | |||||
| @SerializedName("userid") | |||||
| private String userId; | |||||
| @SerializedName("corpid") | |||||
| private String corpId; | |||||
| public static WxCpMaJsCode2SessionResult fromJson(String json) { | |||||
| return WxCpGsonBuilder.create().fromJson(json, WxCpMaJsCode2SessionResult.class); | |||||
| } | |||||
| } | |||||
| @@ -1,22 +1,18 @@ | |||||
| package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
| import java.io.Serializable; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| import com.google.gson.JsonArray; | |||||
| import com.google.gson.JsonObject; | |||||
| import lombok.Data; | import lombok.Data; | ||||
| import me.chanjar.weixin.common.api.WxConsts; | |||||
| import me.chanjar.weixin.common.api.WxConsts.KefuMsgType; | |||||
| import me.chanjar.weixin.cp.bean.article.MpnewsArticle; | import me.chanjar.weixin.cp.bean.article.MpnewsArticle; | ||||
| import me.chanjar.weixin.cp.bean.article.NewArticle; | import me.chanjar.weixin.cp.bean.article.NewArticle; | ||||
| import me.chanjar.weixin.cp.bean.messagebuilder.FileBuilder; | |||||
| import me.chanjar.weixin.cp.bean.messagebuilder.ImageBuilder; | |||||
| import me.chanjar.weixin.cp.bean.messagebuilder.MpnewsBuilder; | |||||
| import me.chanjar.weixin.cp.bean.messagebuilder.NewsBuilder; | |||||
| import me.chanjar.weixin.cp.bean.messagebuilder.TextBuilder; | |||||
| import me.chanjar.weixin.cp.bean.messagebuilder.TextCardBuilder; | |||||
| import me.chanjar.weixin.cp.bean.messagebuilder.VideoBuilder; | |||||
| import me.chanjar.weixin.cp.bean.messagebuilder.VoiceBuilder; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| import me.chanjar.weixin.cp.bean.messagebuilder.*; | |||||
| import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import java.io.Serializable; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| /** | /** | ||||
| * 消息. | * 消息. | ||||
| @@ -45,6 +41,12 @@ public class WxCpMessage implements Serializable { | |||||
| private List<NewArticle> articles = new ArrayList<>(); | private List<NewArticle> articles = new ArrayList<>(); | ||||
| private List<MpnewsArticle> mpnewsArticles = new ArrayList<>(); | private List<MpnewsArticle> mpnewsArticles = new ArrayList<>(); | ||||
| /** | |||||
| * 任务卡片特有的属性 | |||||
| */ | |||||
| private String taskId; | |||||
| private List<TaskCardButton> taskButtons = new ArrayList<>(); | |||||
| /** | /** | ||||
| * 获得文本消息builder. | * 获得文本消息builder. | ||||
| */ | */ | ||||
| @@ -94,6 +96,13 @@ public class WxCpMessage implements Serializable { | |||||
| return new MpnewsBuilder(); | return new MpnewsBuilder(); | ||||
| } | } | ||||
| /** | |||||
| * 获得markdown消息builder. | |||||
| */ | |||||
| public static MarkdownMsgBuilder MARKDOWN() { | |||||
| return new MarkdownMsgBuilder(); | |||||
| } | |||||
| /** | /** | ||||
| * 获得文件消息builder. | * 获得文件消息builder. | ||||
| */ | */ | ||||
| @@ -101,17 +110,26 @@ public class WxCpMessage implements Serializable { | |||||
| return new FileBuilder(); | return new FileBuilder(); | ||||
| } | } | ||||
| /** | |||||
| * 获得任务卡片消息builder. | |||||
| */ | |||||
| public static TaskCardBuilder TASKCARD() { | |||||
| return new TaskCardBuilder(); | |||||
| } | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 请使用 | * 请使用 | ||||
| * {@link WxConsts.KefuMsgType#TEXT} | |||||
| * {@link WxConsts.KefuMsgType#IMAGE} | |||||
| * {@link WxConsts.KefuMsgType#VOICE} | |||||
| * {@link WxConsts.KefuMsgType#MUSIC} | |||||
| * {@link WxConsts.KefuMsgType#VIDEO} | |||||
| * {@link WxConsts.KefuMsgType#NEWS} | |||||
| * {@link WxConsts.KefuMsgType#MPNEWS} | |||||
| * {@link KefuMsgType#TEXT} | |||||
| * {@link KefuMsgType#IMAGE} | |||||
| * {@link KefuMsgType#VOICE} | |||||
| * {@link KefuMsgType#MUSIC} | |||||
| * {@link KefuMsgType#VIDEO} | |||||
| * {@link KefuMsgType#NEWS} | |||||
| * {@link KefuMsgType#MPNEWS} | |||||
| * {@link KefuMsgType#MARKDOWN} | |||||
| * {@link KefuMsgType#TASKCARD} | |||||
| * </pre> | * </pre> | ||||
| * | * | ||||
| * @param msgType 消息类型 | * @param msgType 消息类型 | ||||
| @@ -121,7 +139,162 @@ public class WxCpMessage implements Serializable { | |||||
| } | } | ||||
| public String toJson() { | public String toJson() { | ||||
| return WxCpGsonBuilder.create().toJson(this); | |||||
| JsonObject messageJson = new JsonObject(); | |||||
| if (this.getAgentId() != null) { | |||||
| messageJson.addProperty("agentid", this.getAgentId()); | |||||
| } | |||||
| if (StringUtils.isNotBlank(this.getToUser())) { | |||||
| messageJson.addProperty("touser", this.getToUser()); | |||||
| } | |||||
| messageJson.addProperty("msgtype", this.getMsgType()); | |||||
| if (StringUtils.isNotBlank(this.getToParty())) { | |||||
| messageJson.addProperty("toparty", this.getToParty()); | |||||
| } | |||||
| if (StringUtils.isNotBlank(this.getToTag())) { | |||||
| messageJson.addProperty("totag", this.getToTag()); | |||||
| } | |||||
| this.handleMsgType(messageJson); | |||||
| if (StringUtils.isNotBlank(this.getSafe())) { | |||||
| messageJson.addProperty("safe", this.getSafe()); | |||||
| } | |||||
| return messageJson.toString(); | |||||
| } | |||||
| private void handleMsgType(JsonObject messageJson) { | |||||
| switch (this.getMsgType()) { | |||||
| case KefuMsgType.TEXT: { | |||||
| JsonObject text = new JsonObject(); | |||||
| text.addProperty("content", this.getContent()); | |||||
| messageJson.add("text", text); | |||||
| break; | |||||
| } | |||||
| case KefuMsgType.MARKDOWN: { | |||||
| JsonObject text = new JsonObject(); | |||||
| text.addProperty("content", this.getContent()); | |||||
| messageJson.add("markdown", text); | |||||
| break; | |||||
| } | |||||
| case KefuMsgType.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 KefuMsgType.IMAGE: { | |||||
| JsonObject image = new JsonObject(); | |||||
| image.addProperty("media_id", this.getMediaId()); | |||||
| messageJson.add("image", image); | |||||
| break; | |||||
| } | |||||
| case KefuMsgType.FILE: { | |||||
| JsonObject image = new JsonObject(); | |||||
| image.addProperty("media_id", this.getMediaId()); | |||||
| messageJson.add("file", image); | |||||
| break; | |||||
| } | |||||
| case KefuMsgType.VOICE: { | |||||
| JsonObject voice = new JsonObject(); | |||||
| voice.addProperty("media_id", this.getMediaId()); | |||||
| messageJson.add("voice", voice); | |||||
| break; | |||||
| } | |||||
| case KefuMsgType.VIDEO: { | |||||
| JsonObject video = new JsonObject(); | |||||
| video.addProperty("media_id", this.getMediaId()); | |||||
| video.addProperty("thumb_media_id", this.getThumbMediaId()); | |||||
| video.addProperty("title", this.getTitle()); | |||||
| video.addProperty("description", this.getDescription()); | |||||
| messageJson.add("video", video); | |||||
| break; | |||||
| } | |||||
| case KefuMsgType.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()); | |||||
| articleJsonArray.add(articleJson); | |||||
| } | |||||
| newsJsonObject.add("articles", articleJsonArray); | |||||
| messageJson.add("news", newsJsonObject); | |||||
| break; | |||||
| } | |||||
| case KefuMsgType.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()); | |||||
| articleJson.addProperty("show_cover_pic", article.getShowCoverPic()); | |||||
| articleJsonArray.add(articleJson); | |||||
| } | |||||
| newsJsonObject.add("articles", articleJsonArray); | |||||
| } | |||||
| messageJson.add("mpnews", newsJsonObject); | |||||
| break; | |||||
| } | |||||
| case KefuMsgType.TASKCARD: { | |||||
| JsonObject text = new JsonObject(); | |||||
| text.addProperty("title", this.getTitle()); | |||||
| text.addProperty("description", this.getDescription()); | |||||
| if (StringUtils.isNotBlank(this.getUrl())) { | |||||
| text.addProperty("url", this.getUrl()); | |||||
| } | |||||
| text.addProperty("task_id", this.getTaskId()); | |||||
| JsonArray buttonJsonArray = new JsonArray(); | |||||
| for (TaskCardButton button : this.getTaskButtons()) { | |||||
| JsonObject buttonJson = new JsonObject(); | |||||
| buttonJson.addProperty("key", button.getKey()); | |||||
| buttonJson.addProperty("name", button.getName()); | |||||
| if (StringUtils.isNotBlank(button.getReplaceName())) { | |||||
| buttonJson.addProperty("replace_name", button.getReplaceName()); | |||||
| } | |||||
| if (StringUtils.isNotBlank(button.getColor())) { | |||||
| buttonJson.addProperty("color", button.getColor()); | |||||
| } | |||||
| if (button.getBold() != null) { | |||||
| buttonJson.addProperty("is_bold", button.getBold()); | |||||
| } | |||||
| buttonJsonArray.add(buttonJson); | |||||
| } | |||||
| text.add("btn", buttonJsonArray); | |||||
| messageJson.add("taskcard", text); | |||||
| break; | |||||
| } | |||||
| default: { | |||||
| // do nothing | |||||
| } | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,42 @@ | |||||
| package me.chanjar.weixin.cp.bean; | |||||
| import com.google.gson.annotations.SerializedName; | |||||
| import lombok.AllArgsConstructor; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||||
| import java.io.Serializable; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 更新任务卡片消息状态的返回类 | |||||
| * 参考文档:https://work.weixin.qq.com/api/doc#90000/90135/91579 | |||||
| * Created by Jeff on 2019-05-16. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/domainname">Jeff</a> | |||||
| * @date 2019-05-16 | |||||
| */ | |||||
| @Data | |||||
| @AllArgsConstructor | |||||
| @NoArgsConstructor | |||||
| public class WxCpTaskCardUpdateResult implements Serializable { | |||||
| @SerializedName("errcode") | |||||
| private Integer errcode; | |||||
| @SerializedName("errmsg") | |||||
| private String errmsg; | |||||
| /** | |||||
| * 用户列表 | |||||
| */ | |||||
| @SerializedName("invaliduser") | |||||
| private List<String> invalidUsers; | |||||
| public static WxCpTaskCardUpdateResult fromJson(String json) { | |||||
| return WxCpGsonBuilder.create().fromJson(json, WxCpTaskCardUpdateResult.class); | |||||
| } | |||||
| } | |||||
| @@ -20,7 +20,8 @@ public class WxCpUser implements Serializable { | |||||
| private static final long serialVersionUID = -5696099236344075582L; | private static final long serialVersionUID = -5696099236344075582L; | ||||
| private String userId; | private String userId; | ||||
| private String name; | private String name; | ||||
| private Integer[] departIds; | |||||
| private Long[] departIds; | |||||
| private Integer[] orders; | |||||
| private String position; | private String position; | ||||
| private String mobile; | private String mobile; | ||||
| private Gender gender; | private Gender gender; | ||||
| @@ -1,24 +1,26 @@ | |||||
| package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.io.Serializable; | |||||
| import java.nio.charset.StandardCharsets; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| import org.apache.commons.io.IOUtils; | |||||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | import com.thoughtworks.xstream.annotations.XStreamAlias; | ||||
| import com.thoughtworks.xstream.annotations.XStreamConverter; | import com.thoughtworks.xstream.annotations.XStreamConverter; | ||||
| import com.thoughtworks.xstream.annotations.XStreamImplicit; | |||||
| import lombok.Data; | import lombok.Data; | ||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
| import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
| import me.chanjar.weixin.common.util.XmlUtils; | |||||
| import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | import me.chanjar.weixin.common.util.xml.XStreamCDataConverter; | ||||
| import me.chanjar.weixin.cp.config.WxCpConfigStorage; | import me.chanjar.weixin.cp.config.WxCpConfigStorage; | ||||
| import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | ||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | ||||
| import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | ||||
| import org.apache.commons.io.IOUtils; | |||||
| import java.io.IOException; | |||||
| import java.io.InputStream; | |||||
| import java.io.Serializable; | |||||
| import java.nio.charset.StandardCharsets; | |||||
| import java.util.ArrayList; | |||||
| import java.util.List; | |||||
| import java.util.Map; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| @@ -36,6 +38,11 @@ import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | |||||
| public class WxCpXmlMessage implements Serializable { | public class WxCpXmlMessage implements Serializable { | ||||
| private static final long serialVersionUID = -1042994982179476410L; | private static final long serialVersionUID = -1042994982179476410L; | ||||
| /** | |||||
| * 使用dom4j解析的存放所有xml属性和值的map. | |||||
| */ | |||||
| private Map<String, Object> allFieldsMap; | |||||
| /////////////////////// | /////////////////////// | ||||
| // 以下都是微信推送过来的消息的xml的element所对应的属性 | // 以下都是微信推送过来的消息的xml的element所对应的属性 | ||||
| /////////////////////// | /////////////////////// | ||||
| @@ -149,6 +156,10 @@ public class WxCpXmlMessage implements Serializable { | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | @XStreamConverter(value = XStreamCDataConverter.class) | ||||
| private String recognition; | private String recognition; | ||||
| @XStreamAlias("TaskId") | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||||
| private String taskId; | |||||
| /** | /** | ||||
| * 通讯录变更事件. | * 通讯录变更事件. | ||||
| * 请参考常量 me.chanjar.weixin.cp.WxCpConsts.ContactChangeType | * 请参考常量 me.chanjar.weixin.cp.WxCpConsts.ContactChangeType | ||||
| @@ -240,6 +251,13 @@ public class WxCpXmlMessage implements Serializable { | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | @XStreamConverter(value = XStreamCDataConverter.class) | ||||
| private String telephone; | private String telephone; | ||||
| /** | |||||
| * 地址. | |||||
| */ | |||||
| @XStreamAlias("Address") | |||||
| @XStreamConverter(value = XStreamCDataConverter.class) | |||||
| private String address; | |||||
| /** | /** | ||||
| * 扩展属性. | * 扩展属性. | ||||
| */ | */ | ||||
| @@ -320,17 +338,20 @@ public class WxCpXmlMessage implements Serializable { | |||||
| */ | */ | ||||
| @XStreamAlias("TotalCount") | @XStreamAlias("TotalCount") | ||||
| private Integer totalCount; | private Integer totalCount; | ||||
| /** | /** | ||||
| * 过滤. | * 过滤. | ||||
| * (过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount | * (过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount | ||||
| */ | */ | ||||
| @XStreamAlias("FilterCount") | @XStreamAlias("FilterCount") | ||||
| private Integer filterCount; | private Integer filterCount; | ||||
| /** | /** | ||||
| * 发送成功的粉丝数. | * 发送成功的粉丝数. | ||||
| */ | */ | ||||
| @XStreamAlias("SentCount") | @XStreamAlias("SentCount") | ||||
| private Integer sentCount; | private Integer sentCount; | ||||
| /** | /** | ||||
| * 发送失败的粉丝数. | * 发送失败的粉丝数. | ||||
| */ | */ | ||||
| @@ -349,7 +370,9 @@ public class WxCpXmlMessage implements Serializable { | |||||
| protected static WxCpXmlMessage fromXml(String xml) { | protected static WxCpXmlMessage fromXml(String xml) { | ||||
| //修改微信变态的消息内容格式,方便解析 | //修改微信变态的消息内容格式,方便解析 | ||||
| xml = xml.replace("</PicList><PicList>", ""); | xml = xml.replace("</PicList><PicList>", ""); | ||||
| return XStreamTransformer.fromXml(WxCpXmlMessage.class, xml); | |||||
| final WxCpXmlMessage xmlMessage = XStreamTransformer.fromXml(WxCpXmlMessage.class, xml); | |||||
| xmlMessage.setAllFieldsMap(XmlUtils.xml2Map(xml)); | |||||
| return xmlMessage; | |||||
| } | } | ||||
| protected static WxCpXmlMessage fromXml(InputStream is) { | protected static WxCpXmlMessage fromXml(InputStream is) { | ||||
| @@ -402,9 +425,11 @@ public class WxCpXmlMessage implements Serializable { | |||||
| @Data | @Data | ||||
| public static class ExtAttr { | public static class ExtAttr { | ||||
| @XStreamAlias("Item") | |||||
| @XStreamImplicit(itemFieldName = "Item") | |||||
| protected final List<Item> items = new ArrayList<>(); | protected final List<Item> items = new ArrayList<>(); | ||||
| @XStreamAlias("Item") | |||||
| @Data | @Data | ||||
| public static class Item { | public static class Item { | ||||
| @XStreamAlias("Name") | @XStreamAlias("Name") | ||||
| @@ -1,9 +1,12 @@ | |||||
| package me.chanjar.weixin.cp.bean.article; | package me.chanjar.weixin.cp.bean.article; | ||||
| import lombok.Data; | |||||
| import java.io.Serializable; | import java.io.Serializable; | ||||
| import lombok.AllArgsConstructor; | |||||
| import lombok.Builder; | |||||
| import lombok.Data; | |||||
| import lombok.NoArgsConstructor; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * Created by BinaryWang on 2017/3/27. | * Created by BinaryWang on 2017/3/27. | ||||
| @@ -12,6 +15,9 @@ import java.io.Serializable; | |||||
| * @author Binary Wang | * @author Binary Wang | ||||
| */ | */ | ||||
| @Data | @Data | ||||
| @Builder | |||||
| @AllArgsConstructor | |||||
| @NoArgsConstructor | |||||
| public class NewArticle implements Serializable { | public class NewArticle implements Serializable { | ||||
| private static final long serialVersionUID = 4087852055781140659L; | private static final long serialVersionUID = 4087852055781140659L; | ||||
| @@ -0,0 +1,32 @@ | |||||
| package me.chanjar.weixin.cp.bean.messagebuilder; | |||||
| import me.chanjar.weixin.common.api.WxConsts; | |||||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | |||||
| /** | |||||
| * <pre> | |||||
| * markdown类型的消息builder | |||||
| * Created by Binary Wang on 2019/1/20. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public class MarkdownMsgBuilder extends BaseBuilder<MarkdownMsgBuilder> { | |||||
| private String content; | |||||
| public MarkdownMsgBuilder() { | |||||
| this.msgType = WxConsts.KefuMsgType.MARKDOWN; | |||||
| } | |||||
| public MarkdownMsgBuilder content(String content) { | |||||
| this.content = content; | |||||
| return this; | |||||
| } | |||||
| @Override | |||||
| public WxCpMessage build() { | |||||
| WxCpMessage m = super.build(); | |||||
| m.setContent(this.content); | |||||
| return m; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,68 @@ | |||||
| 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.taskcard.TaskCardButton; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 任务卡片消息Builder | |||||
| * 用法: WxCustomMessage m = WxCustomMessage.TASKCARD().title(...)....toUser(...).build(); | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/domainname">Jeff</a> | |||||
| * @date 2019-05-16 | |||||
| */ | |||||
| public class TaskCardBuilder extends BaseBuilder<TaskCardBuilder> { | |||||
| private String title; | |||||
| private String description; | |||||
| private String url; | |||||
| private String taskId; | |||||
| /** | |||||
| * 按钮个数为1~2个 | |||||
| */ | |||||
| private List<TaskCardButton> buttons; | |||||
| public TaskCardBuilder() { | |||||
| this.msgType = WxConsts.KefuMsgType.TASKCARD; | |||||
| } | |||||
| public TaskCardBuilder title(String title) { | |||||
| this.title = title; | |||||
| return this; | |||||
| } | |||||
| public TaskCardBuilder description(String description) { | |||||
| this.description = description; | |||||
| return this; | |||||
| } | |||||
| public TaskCardBuilder url(String url) { | |||||
| this.url = url; | |||||
| return this; | |||||
| } | |||||
| public TaskCardBuilder taskId(String taskId) { | |||||
| this.taskId = taskId; | |||||
| return this; | |||||
| } | |||||
| public TaskCardBuilder buttons(List<TaskCardButton> buttons) { | |||||
| this.buttons = buttons; | |||||
| return this; | |||||
| } | |||||
| @Override | |||||
| public WxCpMessage build() { | |||||
| WxCpMessage m = super.build(); | |||||
| m.setSafe(null); | |||||
| m.setTitle(this.title); | |||||
| m.setDescription(this.description); | |||||
| m.setUrl(this.url); | |||||
| m.setTaskId(this.taskId); | |||||
| m.setTaskButtons(this.buttons); | |||||
| return m; | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,23 @@ | |||||
| package me.chanjar.weixin.cp.bean.taskcard; | |||||
| import lombok.Builder; | |||||
| import lombok.Data; | |||||
| /** | |||||
| * <pre> | |||||
| * 任务卡片按钮 | |||||
| * Created by Jeff on 2019-05-16. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/domainname">Jeff</a> | |||||
| * @date 2019-05-16 | |||||
| */ | |||||
| @Data | |||||
| @Builder | |||||
| public class TaskCardButton { | |||||
| private String key; | |||||
| private String name; | |||||
| private String replaceName; | |||||
| private String color; | |||||
| private Boolean bold; | |||||
| } | |||||
| @@ -36,11 +36,23 @@ public interface WxCpConfigStorage { | |||||
| /** | /** | ||||
| * 应该是线程安全的 | * 应该是线程安全的 | ||||
| * | |||||
| * @param jsapiTicket | |||||
| */ | */ | ||||
| void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | void updateJsapiTicket(String jsapiTicket, int expiresInSeconds); | ||||
| String getAgentJsapiTicket(); | |||||
| boolean isAgentJsapiTicketExpired(); | |||||
| /** | |||||
| * 强制将jsapi ticket过期掉 | |||||
| */ | |||||
| void expireAgentJsapiTicket(); | |||||
| /** | |||||
| * 应该是线程安全的 | |||||
| */ | |||||
| void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds); | |||||
| String getCorpId(); | String getCorpId(); | ||||
| String getCorpSecret(); | String getCorpSecret(); | ||||
| @@ -1,11 +1,11 @@ | |||||
| package me.chanjar.weixin.cp.config; | package me.chanjar.weixin.cp.config; | ||||
| import java.io.File; | |||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | import me.chanjar.weixin.common.bean.WxAccessToken; | ||||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | ||||
| import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | ||||
| import java.io.File; | |||||
| /** | /** | ||||
| * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | * 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | ||||
| * | * | ||||
| @@ -32,6 +32,9 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||||
| protected volatile String jsapiTicket; | protected volatile String jsapiTicket; | ||||
| protected volatile long jsapiTicketExpiresTime; | protected volatile long jsapiTicketExpiresTime; | ||||
| protected volatile String agentJsapiTicket; | |||||
| protected volatile long agentJsapiTicketExpiresTime; | |||||
| protected volatile File tmpDirFile; | protected volatile File tmpDirFile; | ||||
| private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; | private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; | ||||
| @@ -95,6 +98,28 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||||
| this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | this.jsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | ||||
| } | } | ||||
| @Override | |||||
| public String getAgentJsapiTicket() { | |||||
| return this.agentJsapiTicket; | |||||
| } | |||||
| @Override | |||||
| public boolean isAgentJsapiTicketExpired() { | |||||
| return System.currentTimeMillis() > this.agentJsapiTicketExpiresTime; | |||||
| } | |||||
| @Override | |||||
| public void expireAgentJsapiTicket() { | |||||
| this.agentJsapiTicketExpiresTime = 0; | |||||
| } | |||||
| @Override | |||||
| public void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||||
| this.agentJsapiTicket = jsapiTicket; | |||||
| // 预留200秒的时间 | |||||
| this.agentJsapiTicketExpiresTime = System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L; | |||||
| } | |||||
| @Override | @Override | ||||
| public void expireJsapiTicket() { | public void expireJsapiTicket() { | ||||
| this.jsapiTicketExpiresTime = 0; | this.jsapiTicketExpiresTime = 0; | ||||
| @@ -9,26 +9,21 @@ import redis.clients.jedis.JedisPoolConfig; | |||||
| import java.io.File; | import java.io.File; | ||||
| /** | /** | ||||
| * Jedis client implementor for wechat config storage. | |||||
| * <pre> | * <pre> | ||||
| * 使用说明:本实现仅供参考,并不完整, | |||||
| * 使用说明:本实现仅供参考,并不完整. | |||||
| * 比如为减少项目依赖,未加入redis分布式锁的实现,如有需要请自行实现。 | * 比如为减少项目依赖,未加入redis分布式锁的实现,如有需要请自行实现。 | ||||
| * </pre> | * </pre> | ||||
| * | * | ||||
| * @author gaigeshen | * @author gaigeshen | ||||
| */ | */ | ||||
| public class WxCpJedisConfigStorage implements WxCpConfigStorage { | public class WxCpJedisConfigStorage implements WxCpConfigStorage { | ||||
| /** | |||||
| * Redis keys here | |||||
| */ | |||||
| private static final String ACCESS_TOKEN_KEY = "WX_CP_ACCESS_TOKEN"; | private static final String ACCESS_TOKEN_KEY = "WX_CP_ACCESS_TOKEN"; | ||||
| private static final String ACCESS_TOKEN_EXPIRES_TIME_KEY = "WX_CP_ACCESS_TOKEN_EXPIRES_TIME"; | private static final String ACCESS_TOKEN_EXPIRES_TIME_KEY = "WX_CP_ACCESS_TOKEN_EXPIRES_TIME"; | ||||
| private static final String JS_API_TICKET_KEY = "WX_CP_JS_API_TICKET"; | private static final String JS_API_TICKET_KEY = "WX_CP_JS_API_TICKET"; | ||||
| private static final String JS_API_TICKET_EXPIRES_TIME_KEY = "WX_CP_JS_API_TICKET_EXPIRES_TIME"; | private static final String JS_API_TICKET_EXPIRES_TIME_KEY = "WX_CP_JS_API_TICKET_EXPIRES_TIME"; | ||||
| /** | |||||
| * Redis clients pool | |||||
| */ | |||||
| private static final String AGENT_JSAPI_TICKET_KEY = "WX_CP_AGENT_%s_JSAPI_TICKET"; | |||||
| private static final String AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY = "WX_CP_AGENT_%s_JSAPI_TICKET_EXPIRES_TIME"; | |||||
| private final JedisPool jedisPool; | private final JedisPool jedisPool; | ||||
| private volatile String corpId; | private volatile String corpId; | ||||
| private volatile String corpSecret; | private volatile String corpSecret; | ||||
| @@ -46,7 +41,7 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||||
| public WxCpJedisConfigStorage(JedisPool jedisPool) { | public WxCpJedisConfigStorage(JedisPool jedisPool) { | ||||
| this.jedisPool = jedisPool; | this.jedisPool = jedisPool; | ||||
| } | } | ||||
| public WxCpJedisConfigStorage(String host, int port) { | public WxCpJedisConfigStorage(String host, int port) { | ||||
| jedisPool = new JedisPool(host, port); | jedisPool = new JedisPool(host, port); | ||||
| } | } | ||||
| @@ -83,8 +78,7 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||||
| String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY); | String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY); | ||||
| if (expiresTimeStr != null) { | if (expiresTimeStr != null) { | ||||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||||
| return System.currentTimeMillis() > expiresTime; | |||||
| return System.currentTimeMillis() > Long.parseLong(expiresTimeStr); | |||||
| } | } | ||||
| return true; | return true; | ||||
| @@ -123,17 +117,15 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||||
| @Override | @Override | ||||
| public boolean isJsapiTicketExpired() { | public boolean isJsapiTicketExpired() { | ||||
| try (Jedis jedis = this.jedisPool.getResource()) { | try (Jedis jedis = this.jedisPool.getResource()) { | ||||
| String expiresTimeStr = jedis.get(JS_API_TICKET_EXPIRES_TIME_KEY); | String expiresTimeStr = jedis.get(JS_API_TICKET_EXPIRES_TIME_KEY); | ||||
| if (expiresTimeStr != null) { | if (expiresTimeStr != null) { | ||||
| Long expiresTime = Long.parseLong(expiresTimeStr); | |||||
| long expiresTime = Long.parseLong(expiresTimeStr); | |||||
| return System.currentTimeMillis() > expiresTime; | return System.currentTimeMillis() > expiresTime; | ||||
| } | } | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| @@ -146,16 +138,51 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||||
| @Override | @Override | ||||
| public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | ||||
| try (Jedis jedis = this.jedisPool.getResource()) { | try (Jedis jedis = this.jedisPool.getResource()) { | ||||
| jedis.set(JS_API_TICKET_KEY, jsapiTicket); | jedis.set(JS_API_TICKET_KEY, jsapiTicket); | ||||
| jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY, | jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY, | ||||
| (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + "")); | (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + "")); | ||||
| } | } | ||||
| } | } | ||||
| @Override | |||||
| public String getAgentJsapiTicket() { | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| return jedis.get(String.format(AGENT_JSAPI_TICKET_KEY, agentId)); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public boolean isAgentJsapiTicketExpired() { | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| String expiresTimeStr = jedis.get(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId)); | |||||
| if (expiresTimeStr != null) { | |||||
| return System.currentTimeMillis() > Long.parseLong(expiresTimeStr); | |||||
| } | |||||
| return true; | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void expireAgentJsapiTicket() { | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| jedis.set(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId), "0"); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||||
| try (Jedis jedis = this.jedisPool.getResource()) { | |||||
| jedis.set(String.format(AGENT_JSAPI_TICKET_KEY, agentId), jsapiTicket); | |||||
| jedis.set(String.format(AGENT_JSAPI_TICKET_EXPIRES_TIME_KEY, agentId), | |||||
| (System.currentTimeMillis() + (expiresInSeconds - 200) * 1000L + "")); | |||||
| } | |||||
| } | |||||
| @Override | @Override | ||||
| public String getCorpId() { | public String getCorpId() { | ||||
| return this.corpId; | return this.corpId; | ||||
| @@ -73,7 +73,7 @@ public class WxCpMessageRouter { | |||||
| this.wxCpService = wxCpService; | this.wxCpService = wxCpService; | ||||
| this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | ||||
| this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); | this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); | ||||
| this.sessionManager = new StandardSessionManager(); | |||||
| this.sessionManager = wxCpService.getSessionManager(); | |||||
| this.exceptionHandler = new LogExceptionHandler(); | this.exceptionHandler = new LogExceptionHandler(); | ||||
| } | } | ||||
| @@ -7,7 +7,6 @@ import me.chanjar.weixin.common.error.WxError; | |||||
| import me.chanjar.weixin.common.util.json.WxErrorAdapter; | import me.chanjar.weixin.common.util.json.WxErrorAdapter; | ||||
| import me.chanjar.weixin.cp.bean.WxCpChat; | import me.chanjar.weixin.cp.bean.WxCpChat; | ||||
| import me.chanjar.weixin.cp.bean.WxCpDepart; | import me.chanjar.weixin.cp.bean.WxCpDepart; | ||||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | |||||
| import me.chanjar.weixin.cp.bean.WxCpTag; | import me.chanjar.weixin.cp.bean.WxCpTag; | ||||
| import me.chanjar.weixin.cp.bean.WxCpUser; | import me.chanjar.weixin.cp.bean.WxCpUser; | ||||
| @@ -20,7 +19,6 @@ public class WxCpGsonBuilder { | |||||
| static { | static { | ||||
| INSTANCE.disableHtmlEscaping(); | INSTANCE.disableHtmlEscaping(); | ||||
| INSTANCE.registerTypeAdapter(WxCpMessage.class, new WxCpMessageGsonAdapter()); | |||||
| INSTANCE.registerTypeAdapter(WxCpChat.class, new WxCpChatGsonAdapter()); | INSTANCE.registerTypeAdapter(WxCpChat.class, new WxCpChatGsonAdapter()); | ||||
| INSTANCE.registerTypeAdapter(WxCpDepart.class, new WxCpDepartGsonAdapter()); | INSTANCE.registerTypeAdapter(WxCpDepart.class, new WxCpDepartGsonAdapter()); | ||||
| INSTANCE.registerTypeAdapter(WxCpUser.class, new WxCpUserGsonAdapter()); | INSTANCE.registerTypeAdapter(WxCpUser.class, new WxCpUserGsonAdapter()); | ||||
| @@ -1,127 +0,0 @@ | |||||
| /* | |||||
| * KINGSTAR MEDIA SOLUTIONS Co.,LTD. Copyright c 2005-2013. All rights reserved. | |||||
| * | |||||
| * This source code is the property of KINGSTAR MEDIA SOLUTIONS LTD. It is intended | |||||
| * only for the use of KINGSTAR MEDIA application development. Reengineering, reproduction | |||||
| * arose from modification of the original source, or other redistribution of this source | |||||
| * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | |||||
| */ | |||||
| package me.chanjar.weixin.cp.util.json; | |||||
| import com.google.gson.*; | |||||
| import me.chanjar.weixin.common.api.WxConsts; | |||||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | |||||
| import me.chanjar.weixin.cp.bean.article.MpnewsArticle; | |||||
| import me.chanjar.weixin.cp.bean.article.NewArticle; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import java.lang.reflect.Type; | |||||
| /** | |||||
| * @author Daniel Qian | |||||
| */ | |||||
| public class WxCpMessageGsonAdapter implements JsonSerializer<WxCpMessage> { | |||||
| @Override | |||||
| public JsonElement serialize(WxCpMessage message, Type typeOfSrc, JsonSerializationContext context) { | |||||
| JsonObject messageJson = new JsonObject(); | |||||
| messageJson.addProperty("agentid", message.getAgentId()); | |||||
| if (StringUtils.isNotBlank(message.getToUser())) { | |||||
| messageJson.addProperty("touser", message.getToUser()); | |||||
| } | |||||
| messageJson.addProperty("msgtype", message.getMsgType()); | |||||
| if (StringUtils.isNotBlank(message.getToParty())) { | |||||
| messageJson.addProperty("toparty", message.getToParty()); | |||||
| } | |||||
| if (StringUtils.isNotBlank(message.getToTag())) { | |||||
| messageJson.addProperty("totag", message.getToTag()); | |||||
| } | |||||
| if (WxConsts.KefuMsgType.TEXT.equals(message.getMsgType())) { | |||||
| JsonObject text = new JsonObject(); | |||||
| text.addProperty("content", message.getContent()); | |||||
| messageJson.add("text", text); | |||||
| } | |||||
| if (WxConsts.KefuMsgType.TEXTCARD.equals(message.getMsgType())) { | |||||
| JsonObject text = new JsonObject(); | |||||
| text.addProperty("title", message.getTitle()); | |||||
| text.addProperty("description", message.getDescription()); | |||||
| text.addProperty("url", message.getUrl()); | |||||
| text.addProperty("btntxt", message.getBtnTxt()); | |||||
| messageJson.add("textcard", text); | |||||
| } | |||||
| if (WxConsts.KefuMsgType.IMAGE.equals(message.getMsgType())) { | |||||
| JsonObject image = new JsonObject(); | |||||
| image.addProperty("media_id", message.getMediaId()); | |||||
| messageJson.add("image", image); | |||||
| } | |||||
| if (WxConsts.KefuMsgType.FILE.equals(message.getMsgType())) { | |||||
| JsonObject image = new JsonObject(); | |||||
| image.addProperty("media_id", message.getMediaId()); | |||||
| messageJson.add("file", image); | |||||
| } | |||||
| if (WxConsts.KefuMsgType.VOICE.equals(message.getMsgType())) { | |||||
| JsonObject voice = new JsonObject(); | |||||
| voice.addProperty("media_id", message.getMediaId()); | |||||
| messageJson.add("voice", voice); | |||||
| } | |||||
| if (StringUtils.isNotBlank(message.getSafe())) { | |||||
| messageJson.addProperty("safe", message.getSafe()); | |||||
| } | |||||
| if (WxConsts.KefuMsgType.VIDEO.equals(message.getMsgType())) { | |||||
| JsonObject video = new JsonObject(); | |||||
| video.addProperty("media_id", message.getMediaId()); | |||||
| video.addProperty("thumb_media_id", message.getThumbMediaId()); | |||||
| video.addProperty("title", message.getTitle()); | |||||
| video.addProperty("description", message.getDescription()); | |||||
| messageJson.add("video", video); | |||||
| } | |||||
| if (WxConsts.KefuMsgType.NEWS.equals(message.getMsgType())) { | |||||
| JsonObject newsJsonObject = new JsonObject(); | |||||
| JsonArray articleJsonArray = new JsonArray(); | |||||
| for (NewArticle article : message.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()); | |||||
| articleJsonArray.add(articleJson); | |||||
| } | |||||
| newsJsonObject.add("articles", articleJsonArray); | |||||
| messageJson.add("news", newsJsonObject); | |||||
| } | |||||
| if (WxConsts.KefuMsgType.MPNEWS.equals(message.getMsgType())) { | |||||
| JsonObject newsJsonObject = new JsonObject(); | |||||
| if (message.getMediaId() != null) { | |||||
| newsJsonObject.addProperty("media_id", message.getMediaId()); | |||||
| } else { | |||||
| JsonArray articleJsonArray = new JsonArray(); | |||||
| for (MpnewsArticle article : message.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()); | |||||
| articleJson.addProperty("show_cover_pic", article.getShowCoverPic()); | |||||
| articleJsonArray.add(articleJson); | |||||
| } | |||||
| newsJsonObject.add("articles", articleJsonArray); | |||||
| } | |||||
| messageJson.add("mpnews", newsJsonObject); | |||||
| } | |||||
| return messageJson; | |||||
| } | |||||
| } | |||||
| @@ -6,6 +6,7 @@ | |||||
| * arose from modification of the original source, or other redistribution of this source | * arose from modification of the original source, or other redistribution of this source | ||||
| * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | * is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | ||||
| */ | */ | ||||
| package me.chanjar.weixin.cp.util.json; | package me.chanjar.weixin.cp.util.json; | ||||
| import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||
| @@ -24,11 +25,14 @@ import me.chanjar.weixin.cp.bean.Gender; | |||||
| import me.chanjar.weixin.cp.bean.WxCpUser; | import me.chanjar.weixin.cp.bean.WxCpUser; | ||||
| /** | /** | ||||
| * cp user gson adapter. | |||||
| * | |||||
| * @author Daniel Qian | * @author Daniel Qian | ||||
| */ | */ | ||||
| public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSerializer<WxCpUser> { | public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSerializer<WxCpUser> { | ||||
| private static final String EXTERNAL_PROFILE = "external_profile"; | private static final String EXTERNAL_PROFILE = "external_profile"; | ||||
| private static final String EXTERNAL_ATTR = "external_attr"; | private static final String EXTERNAL_ATTR = "external_attr"; | ||||
| private static final String EXTATTR = "extattr"; | |||||
| @Override | @Override | ||||
| public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | public WxCpUser deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { | ||||
| @@ -37,14 +41,24 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||||
| if (o.get("department") != null) { | if (o.get("department") != null) { | ||||
| JsonArray departJsonArray = o.get("department").getAsJsonArray(); | JsonArray departJsonArray = o.get("department").getAsJsonArray(); | ||||
| Integer[] departIds = new Integer[departJsonArray.size()]; | |||||
| Long[] departIds = new Long[departJsonArray.size()]; | |||||
| int i = 0; | int i = 0; | ||||
| for (JsonElement jsonElement : departJsonArray) { | for (JsonElement jsonElement : departJsonArray) { | ||||
| departIds[i++] = jsonElement.getAsInt(); | |||||
| departIds[i++] = jsonElement.getAsLong(); | |||||
| } | } | ||||
| user.setDepartIds(departIds); | user.setDepartIds(departIds); | ||||
| } | } | ||||
| if (o.get("order") != null) { | |||||
| JsonArray departJsonArray = o.get("order").getAsJsonArray(); | |||||
| Integer[] orders = new Integer[departJsonArray.size()]; | |||||
| int i = 0; | |||||
| for (JsonElement jsonElement : departJsonArray) { | |||||
| orders[i++] = jsonElement.getAsInt(); | |||||
| } | |||||
| user.setOrders(orders); | |||||
| } | |||||
| user.setUserId(GsonHelper.getString(o, "userid")); | user.setUserId(GsonHelper.getString(o, "userid")); | ||||
| user.setName(GsonHelper.getString(o, "name")); | user.setName(GsonHelper.getString(o, "name")); | ||||
| user.setPosition(GsonHelper.getString(o, "position")); | user.setPosition(GsonHelper.getString(o, "position")); | ||||
| @@ -62,64 +76,73 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||||
| user.setQrCode(GsonHelper.getString(o, "qr_code")); | user.setQrCode(GsonHelper.getString(o, "qr_code")); | ||||
| user.setToInvite(GsonHelper.getBoolean(o, "to_invite")); | user.setToInvite(GsonHelper.getBoolean(o, "to_invite")); | ||||
| if (GsonHelper.isNotNull(o.get("extattr"))) { | |||||
| JsonArray attrJsonElements = o.get("extattr").getAsJsonObject().get("attrs").getAsJsonArray(); | |||||
| for (JsonElement attrJsonElement : attrJsonElements) { | |||||
| WxCpUser.Attr attr = new WxCpUser.Attr( | |||||
| GsonHelper.getString(attrJsonElement.getAsJsonObject(), "name"), | |||||
| GsonHelper.getString(attrJsonElement.getAsJsonObject(), "value") | |||||
| ); | |||||
| user.getExtAttrs().add(attr); | |||||
| } | |||||
| if (GsonHelper.isNotNull(o.get(EXTATTR))) { | |||||
| this.buildExtraAttrs(o, user); | |||||
| } | } | ||||
| if (GsonHelper.isNotNull(o.get(EXTERNAL_PROFILE))) { | if (GsonHelper.isNotNull(o.get(EXTERNAL_PROFILE))) { | ||||
| JsonArray attrJsonElements = o.get(EXTERNAL_PROFILE).getAsJsonObject().get(EXTERNAL_ATTR).getAsJsonArray(); | |||||
| for (JsonElement element : attrJsonElements) { | |||||
| final Integer type = GsonHelper.getInteger(element.getAsJsonObject(), "type"); | |||||
| final String name = GsonHelper.getString(element.getAsJsonObject(), "name"); | |||||
| this.buildExternalAttrs(o, user); | |||||
| } | |||||
| switch (type) { | |||||
| case 0: { | |||||
| user.getExternalAttrs() | |||||
| .add(WxCpUser.ExternalAttribute.builder() | |||||
| .type(type) | |||||
| .name(name) | |||||
| .value(GsonHelper.getString(element.getAsJsonObject().get("text").getAsJsonObject(), "value")) | |||||
| .build() | |||||
| ); | |||||
| break; | |||||
| } | |||||
| case 1: { | |||||
| final JsonObject web = element.getAsJsonObject().get("web").getAsJsonObject(); | |||||
| user.getExternalAttrs() | |||||
| .add(WxCpUser.ExternalAttribute.builder() | |||||
| .type(type) | |||||
| .name(name) | |||||
| .url(GsonHelper.getString(web, "url")) | |||||
| .title(GsonHelper.getString(web, "title")) | |||||
| .build() | |||||
| ); | |||||
| break; | |||||
| } | |||||
| case 2: { | |||||
| final JsonObject miniprogram = element.getAsJsonObject().get("miniprogram").getAsJsonObject(); | |||||
| user.getExternalAttrs() | |||||
| .add(WxCpUser.ExternalAttribute.builder() | |||||
| .type(type) | |||||
| .name(name) | |||||
| .appid(GsonHelper.getString(miniprogram, "appid")) | |||||
| .pagePath(GsonHelper.getString(miniprogram, "pagepath")) | |||||
| .title(GsonHelper.getString(miniprogram, "title")) | |||||
| .build() | |||||
| ); | |||||
| break; | |||||
| } | |||||
| default://ignored | |||||
| return user; | |||||
| } | |||||
| private void buildExtraAttrs(JsonObject o, WxCpUser user) { | |||||
| JsonArray attrJsonElements = o.get(EXTATTR).getAsJsonObject().get("attrs").getAsJsonArray(); | |||||
| for (JsonElement attrJsonElement : attrJsonElements) { | |||||
| WxCpUser.Attr attr = new WxCpUser.Attr( | |||||
| GsonHelper.getString(attrJsonElement.getAsJsonObject(), "name"), | |||||
| GsonHelper.getString(attrJsonElement.getAsJsonObject(), "value") | |||||
| ); | |||||
| user.getExtAttrs().add(attr); | |||||
| } | |||||
| } | |||||
| private void buildExternalAttrs(JsonObject o, WxCpUser user) { | |||||
| JsonArray attrJsonElements = o.get(EXTERNAL_PROFILE).getAsJsonObject().get(EXTERNAL_ATTR).getAsJsonArray(); | |||||
| for (JsonElement element : attrJsonElements) { | |||||
| final Integer type = GsonHelper.getInteger(element.getAsJsonObject(), "type"); | |||||
| final String name = GsonHelper.getString(element.getAsJsonObject(), "name"); | |||||
| switch (type) { | |||||
| case 0: { | |||||
| user.getExternalAttrs() | |||||
| .add(WxCpUser.ExternalAttribute.builder() | |||||
| .type(type) | |||||
| .name(name) | |||||
| .value(GsonHelper.getString(element.getAsJsonObject().get("text").getAsJsonObject(), "value")) | |||||
| .build() | |||||
| ); | |||||
| break; | |||||
| } | } | ||||
| case 1: { | |||||
| final JsonObject web = element.getAsJsonObject().get("web").getAsJsonObject(); | |||||
| user.getExternalAttrs() | |||||
| .add(WxCpUser.ExternalAttribute.builder() | |||||
| .type(type) | |||||
| .name(name) | |||||
| .url(GsonHelper.getString(web, "url")) | |||||
| .title(GsonHelper.getString(web, "title")) | |||||
| .build() | |||||
| ); | |||||
| break; | |||||
| } | |||||
| case 2: { | |||||
| final JsonObject miniprogram = element.getAsJsonObject().get("miniprogram").getAsJsonObject(); | |||||
| user.getExternalAttrs() | |||||
| .add(WxCpUser.ExternalAttribute.builder() | |||||
| .type(type) | |||||
| .name(name) | |||||
| .appid(GsonHelper.getString(miniprogram, "appid")) | |||||
| .pagePath(GsonHelper.getString(miniprogram, "pagepath")) | |||||
| .title(GsonHelper.getString(miniprogram, "title")) | |||||
| .build() | |||||
| ); | |||||
| break; | |||||
| } | |||||
| default://ignored | |||||
| } | } | ||||
| } | } | ||||
| return user; | |||||
| } | } | ||||
| @Override | @Override | ||||
| @@ -133,11 +156,20 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||||
| } | } | ||||
| if (user.getDepartIds() != null) { | if (user.getDepartIds() != null) { | ||||
| JsonArray jsonArray = new JsonArray(); | JsonArray jsonArray = new JsonArray(); | ||||
| for (Integer departId : user.getDepartIds()) { | |||||
| for (Long departId : user.getDepartIds()) { | |||||
| jsonArray.add(new JsonPrimitive(departId)); | jsonArray.add(new JsonPrimitive(departId)); | ||||
| } | } | ||||
| o.add("department", jsonArray); | o.add("department", jsonArray); | ||||
| } | } | ||||
| if (user.getOrders() != null) { | |||||
| JsonArray jsonArray = new JsonArray(); | |||||
| for (Integer order : user.getOrders()) { | |||||
| jsonArray.add(new JsonPrimitive(order)); | |||||
| } | |||||
| o.add("order", jsonArray); | |||||
| } | |||||
| if (user.getPosition() != null) { | if (user.getPosition() != null) { | ||||
| o.addProperty("position", user.getPosition()); | o.addProperty("position", user.getPosition()); | ||||
| } | } | ||||
| @@ -191,14 +223,14 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||||
| } | } | ||||
| JsonObject attrsJson = new JsonObject(); | JsonObject attrsJson = new JsonObject(); | ||||
| attrsJson.add("attrs", attrsJsonArray); | attrsJson.add("attrs", attrsJsonArray); | ||||
| o.add("extattr", attrsJson); | |||||
| o.add(EXTATTR, attrsJson); | |||||
| } | } | ||||
| if (user.getExternalAttrs().size() > 0) { | if (user.getExternalAttrs().size() > 0) { | ||||
| JsonArray attrsJsonArray = new JsonArray(); | JsonArray attrsJsonArray = new JsonArray(); | ||||
| for (WxCpUser.ExternalAttribute attr : user.getExternalAttrs()) { | for (WxCpUser.ExternalAttribute attr : user.getExternalAttrs()) { | ||||
| JsonObject attrJson = new JsonObject(); | JsonObject attrJson = new JsonObject(); | ||||
| attrJson.addProperty("type",attr.getType()); | |||||
| attrJson.addProperty("type", attr.getType()); | |||||
| attrJson.addProperty("name", attr.getName()); | attrJson.addProperty("name", attr.getName()); | ||||
| switch (attr.getType()) { | switch (attr.getType()) { | ||||
| case 0: { | case 0: { | ||||
| @@ -1,11 +1,12 @@ | |||||
| package me.chanjar.weixin.cp.api; | package me.chanjar.weixin.cp.api; | ||||
| import org.testng.annotations.*; | |||||
| import com.google.inject.Inject; | import com.google.inject.Inject; | ||||
| import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import me.chanjar.weixin.cp.bean.WxCpMessage; | import me.chanjar.weixin.cp.bean.WxCpMessage; | ||||
| import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | ||||
| import org.testng.annotations.*; | |||||
| import static org.testng.Assert.*; | import static org.testng.Assert.*; | ||||
| @@ -14,7 +15,7 @@ import static org.testng.Assert.*; | |||||
| * @author Daniel Qian | * @author Daniel Qian | ||||
| * | * | ||||
| */ | */ | ||||
| @Test(groups = "customMessageAPI") | |||||
| @Test | |||||
| @Guice(modules = ApiTestModule.class) | @Guice(modules = ApiTestModule.class) | ||||
| public class WxCpMessageAPITest { | public class WxCpMessageAPITest { | ||||
| @@ -59,4 +60,51 @@ public class WxCpMessageAPITest { | |||||
| System.out.println(messageSendResult.getInvalidUserList()); | System.out.println(messageSendResult.getInvalidUserList()); | ||||
| System.out.println(messageSendResult.getInvalidTagList()); | System.out.println(messageSendResult.getInvalidTagList()); | ||||
| } | } | ||||
| @Test | |||||
| public void testSendMessage_markdown() throws WxErrorException { | |||||
| WxCpMessage message = WxCpMessage | |||||
| .MARKDOWN() | |||||
| .toUser(configStorage.getUserId()) | |||||
| .content("您的会议室已经预定,稍后会同步到`邮箱` \n" + | |||||
| " >**事项详情** \n" + | |||||
| " >事 项:<font color=\\\"info\\\">开会</font> \n" + | |||||
| " >组织者:@miglioguan \n" + | |||||
| " >参与者:@miglioguan、@kunliu、@jamdeezhou、@kanexiong、@kisonwang \n" + | |||||
| " > \n" + | |||||
| " >会议室:<font color=\\\"info\\\">广州TIT 1楼 301</font> \n" + | |||||
| " >日 期:<font color=\\\"warning\\\">2018年5月18日</font> \n" + | |||||
| " >时 间:<font color=\\\"comment\\\">上午9:00-11:00</font> \n" + | |||||
| " > \n" + | |||||
| " >请准时参加会议。 \n" + | |||||
| " > \n" + | |||||
| " >如需修改会议信息,请点击:[修改会议信息](https://work.weixin.qq.com)") | |||||
| .build(); | |||||
| WxCpMessageSendResult messageSendResult = this.wxService.messageSend(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 testSendMessage_textCard() throws WxErrorException { | |||||
| WxCpMessage message = WxCpMessage | |||||
| .TEXTCARD() | |||||
| .toUser(configStorage.getUserId()) | |||||
| .btnTxt("更多") | |||||
| .description( "<div class=\"gray\">2016年9月26日</div> <div class=\"normal\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\"highlight\">请于2016年10月10日前联系行政同事领取</div>") | |||||
| .url("URL") | |||||
| .title("领奖通知") | |||||
| .build(); | |||||
| WxCpMessageSendResult messageSendResult = this.wxService.messageSend(message); | |||||
| assertNotNull(messageSendResult); | |||||
| System.out.println(messageSendResult); | |||||
| System.out.println(messageSendResult.getInvalidPartyList()); | |||||
| System.out.println(messageSendResult.getInvalidUserList()); | |||||
| System.out.println(messageSendResult.getInvalidTagList()); | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,36 @@ | |||||
| 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 org.testng.annotations.Guice; | |||||
| import org.testng.annotations.Test; | |||||
| import static org.assertj.core.api.Assertions.assertThat; | |||||
| import static org.testng.Assert.*; | |||||
| /** | |||||
| * <pre> | |||||
| * Created by BinaryWang on 2019/3/31. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| @Test | |||||
| @Guice(modules = ApiTestModule.class) | |||||
| public class BaseWxCpServiceImplTest { | |||||
| @Inject | |||||
| protected WxCpService wxService; | |||||
| @Test | |||||
| public void testGetAgentJsapiTicket() throws WxErrorException { | |||||
| assertThat(this.wxService.getAgentJsapiTicket()).isNotEmpty(); | |||||
| assertThat(this.wxService.getAgentJsapiTicket(true)).isNotEmpty(); | |||||
| } | |||||
| @Test | |||||
| public void testJsCode2Session() throws WxErrorException { | |||||
| assertThat(this.wxService.jsCode2Session("111")).isNotNull(); | |||||
| } | |||||
| } | |||||
| @@ -2,15 +2,21 @@ package me.chanjar.weixin.cp.api.impl; | |||||
| import java.util.Arrays; | import java.util.Arrays; | ||||
| import me.chanjar.weixin.cp.bean.WxCpChat; | |||||
| import org.testng.Assert; | |||||
| import org.testng.annotations.Guice; | |||||
| import org.testng.annotations.Test; | |||||
| import org.testng.*; | |||||
| import org.testng.annotations.*; | |||||
| import com.google.common.collect.Lists; | |||||
| import com.google.inject.Inject; | import com.google.inject.Inject; | ||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.cp.WxCpConsts.AppChatMsgType; | |||||
| import me.chanjar.weixin.cp.api.ApiTestModule; | import me.chanjar.weixin.cp.api.ApiTestModule; | ||||
| import me.chanjar.weixin.cp.api.WxCpService; | import me.chanjar.weixin.cp.api.WxCpService; | ||||
| import me.chanjar.weixin.cp.bean.WxCpAppChatMessage; | |||||
| import me.chanjar.weixin.cp.bean.WxCpChat; | |||||
| import me.chanjar.weixin.cp.bean.article.MpnewsArticle; | |||||
| import me.chanjar.weixin.cp.bean.article.NewArticle; | |||||
| import static org.assertj.core.api.Assertions.assertThat; | |||||
| /** | /** | ||||
| * 测试群聊服务 | * 测试群聊服务 | ||||
| @@ -19,28 +25,134 @@ import me.chanjar.weixin.cp.api.WxCpService; | |||||
| */ | */ | ||||
| @Guice(modules = ApiTestModule.class) | @Guice(modules = ApiTestModule.class) | ||||
| public class WxCpChatServiceImplTest { | public class WxCpChatServiceImplTest { | ||||
| private String chatId; | |||||
| private String userId; | |||||
| @Inject | @Inject | ||||
| private WxCpService wxCpService; | |||||
| private WxCpService cpService; | |||||
| @BeforeTest | |||||
| public void init() { | |||||
| this.chatId = "mychatid"; | |||||
| this.userId = ((ApiTestModule.WxXmlCpInMemoryConfigStorage) this.cpService.getWxCpConfigStorage()).getUserId(); | |||||
| } | |||||
| @Test | @Test | ||||
| public void create() throws Exception { | |||||
| wxCpService.getChatService().chatCreate("测试群聊", "gaige_shen", Arrays.asList("gaige_shen", "ZhangXiaoMing"), "mychatid"); | |||||
| public void testChatCreate() throws Exception { | |||||
| final String result = cpService.getChatService().chatCreate("测试群聊", userId, | |||||
| Arrays.asList(userId, userId), chatId); | |||||
| assertThat(result).isNotEmpty(); | |||||
| assertThat(result).isEqualTo(chatId); | |||||
| } | } | ||||
| @Test | @Test | ||||
| public void get() throws Exception { | |||||
| WxCpChat chat = wxCpService.getChatService().chatGet("mychatid"); | |||||
| public void testChatGet() throws Exception { | |||||
| WxCpChat chat = this.cpService.getChatService().chatGet(chatId); | |||||
| System.out.println(chat); | System.out.println(chat); | ||||
| Assert.assertEquals(chat.getName(), "测试群聊"); | Assert.assertEquals(chat.getName(), "测试群聊"); | ||||
| } | } | ||||
| @Test | @Test | ||||
| public void update() throws Exception { | |||||
| wxCpService.getChatService().chatUpdate("mychatid", "", "", Arrays.asList("ZhengWuYao"), null); | |||||
| WxCpChat chat = wxCpService.getChatService().chatGet("mychatid"); | |||||
| public void testChatUpdate() throws Exception { | |||||
| this.cpService.getChatService().chatUpdate(chatId, "", "", Arrays.asList("ZhengWuYao"), null); | |||||
| WxCpChat chat = this.cpService.getChatService().chatGet(chatId); | |||||
| System.out.println(chat); | System.out.println(chat); | ||||
| Assert.assertEquals(chat.getUsers().size(), 3); | Assert.assertEquals(chat.getUsers().size(), 3); | ||||
| } | } | ||||
| @DataProvider | |||||
| public Object[][] messages() { | |||||
| return new Object[][]{ | |||||
| {WxCpAppChatMessage.builder() | |||||
| .msgType(AppChatMsgType.TEXT) | |||||
| .chatId(chatId) | |||||
| .content("你的快递已到\n请携带工卡前往邮件中心领取") | |||||
| .build() | |||||
| }, | |||||
| {WxCpAppChatMessage.builder() | |||||
| .msgType(AppChatMsgType.IMAGE) | |||||
| .chatId(chatId) | |||||
| .mediaId("3_xWGPXZhpOKZrlRISWrjhPrDUZqZ-jIEVzxd56jLuqM") | |||||
| .build() | |||||
| }, | |||||
| {WxCpAppChatMessage.builder() | |||||
| .msgType(AppChatMsgType.VOICE) | |||||
| .chatId(chatId) | |||||
| .mediaId("3X5t6HkdN1hUgB7OzrdRnc8v0yI0CqlAxFxnCkS3msTnTLanpYrV4esLv4foZVnlf") | |||||
| .build() | |||||
| }, | |||||
| {WxCpAppChatMessage.builder() | |||||
| .msgType(AppChatMsgType.VIDEO) | |||||
| .chatId(chatId) | |||||
| .mediaId("3otWyy_acbID8fyltmCOW5hGVD8oa0_p0za5jhukxKTUDoGT71lqTvtQAWoycXpQf") | |||||
| .title("aaaa") | |||||
| .description("ddddd") | |||||
| .build() | |||||
| }, | |||||
| {WxCpAppChatMessage.builder() | |||||
| .msgType(AppChatMsgType.FILE) | |||||
| .chatId(chatId) | |||||
| .mediaId("34AyVyDdndVhB4Z2tT-_FYKZ7Xqrr47LPC11GHH4oy7o") | |||||
| .build() | |||||
| }, | |||||
| {WxCpAppChatMessage.builder() | |||||
| .msgType(AppChatMsgType.TEXTCARD) | |||||
| .chatId(chatId) | |||||
| .btnTxt("更多") | |||||
| .title("领奖通知") | |||||
| .url("https://zhidao.baidu.com/question/2073647112026042748.html") | |||||
| .description("<div class=\"gray\">2016年9月26日</div> <div class=\"normal\"> 恭喜你抽中iPhone 7一台,领奖码:520258</div><div class=\"highlight\">请于2016年10月10日前联系行 政同事领取</div>") | |||||
| .build() | |||||
| }, | |||||
| {WxCpAppChatMessage.builder() | |||||
| .msgType(AppChatMsgType.NEWS) | |||||
| .chatId(chatId) | |||||
| .articles(Lists.newArrayList(NewArticle.builder() | |||||
| .title("领奖通知") | |||||
| .url("https://zhidao.baidu.com/question/2073647112026042748.html") | |||||
| .description("今年中秋节公司有豪礼相送") | |||||
| .picUrl("http://res.mail.qq.com/node/ww/wwopenmng/images/independent/doc/test_pic_msg1.png") | |||||
| .build() | |||||
| )) | |||||
| .build() | |||||
| }, | |||||
| {WxCpAppChatMessage.builder() | |||||
| .msgType(AppChatMsgType.MPNEWS) | |||||
| .chatId(chatId) | |||||
| .mpnewsArticles(Lists.newArrayList(MpnewsArticle.newBuilder() | |||||
| .title("地球一小时") | |||||
| .thumbMediaId("3_xWGPXZhpOKZrlRISWrjhPrDUZqZ-jIEVzxd56jLuqM") | |||||
| .author("Author") | |||||
| .contentSourceUrl("https://work.weixin.qq.com") | |||||
| .content("3月24日20:30-21:30 \n办公区将关闭照明一小时,请各部门同事相互转告") | |||||
| .digest("3月24日20:30-21:30 \n办公区将关闭照明一小时") | |||||
| .build() | |||||
| )) | |||||
| .build() | |||||
| }, | |||||
| {WxCpAppChatMessage.builder() | |||||
| .msgType(AppChatMsgType.MARKDOWN) | |||||
| .chatId(chatId) | |||||
| .content("您的会议室已经预定,稍后会同步到`邮箱` \n" + | |||||
| " >**事项详情** \n" + | |||||
| " >事 项:<font color=\\\"info\\\">开会</font> \n" + | |||||
| " >组织者:@miglioguan \n" + | |||||
| " >参与者:@miglioguan、@kunliu、@jamdeezhou、@kanexiong、@kisonwang \n" + | |||||
| " > \n" + | |||||
| " >会议室:<font color=\\\"info\\\">广州TIT 1楼 301</font> \n" + | |||||
| " >日 期:<font color=\\\"warning\\\">2018年5月18日</font> \n" + | |||||
| " >时 间:<font color=\\\"comment\\\">上午9:00-11:00</font> \n" + | |||||
| " > \n" + | |||||
| " >请准时参加会议。 \n" + | |||||
| " > \n" + | |||||
| " >如需修改会议信息,请点击:[修改会议信息](https://work.weixin.qq.com)") | |||||
| .build() | |||||
| }, | |||||
| }; | |||||
| } | |||||
| @Test(dataProvider = "messages") | |||||
| public void testSendMsg(WxCpAppChatMessage message) throws WxErrorException { | |||||
| this.cpService.getChatService().sendMsg(message); | |||||
| } | |||||
| } | } | ||||
| @@ -31,7 +31,7 @@ public class WxCpDepartmentServiceImplTest { | |||||
| cpDepart.setName("子部门" + System.currentTimeMillis()); | cpDepart.setName("子部门" + System.currentTimeMillis()); | ||||
| cpDepart.setParentId(1L); | cpDepart.setParentId(1L); | ||||
| cpDepart.setOrder(1L); | cpDepart.setOrder(1L); | ||||
| Integer departId = this.wxCpService.getDepartmentService().create(cpDepart); | |||||
| Long departId = this.wxCpService.getDepartmentService().create(cpDepart); | |||||
| System.out.println(departId); | System.out.println(departId); | ||||
| } | } | ||||
| @@ -0,0 +1,49 @@ | |||||
| package me.chanjar.weixin.cp.api.impl; | |||||
| import com.google.common.collect.Lists; | |||||
| import com.google.gson.Gson; | |||||
| import com.google.inject.Inject; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.cp.api.ApiTestModule; | |||||
| import me.chanjar.weixin.cp.api.WxCpService; | |||||
| import me.chanjar.weixin.cp.bean.WxCpCheckinData; | |||||
| import me.chanjar.weixin.cp.bean.WxCpCheckinOption; | |||||
| import org.apache.commons.lang3.time.DateFormatUtils; | |||||
| import org.testng.annotations.Guice; | |||||
| import org.testng.annotations.Test; | |||||
| import java.text.ParseException; | |||||
| import java.util.ArrayList; | |||||
| import java.util.Date; | |||||
| import java.util.List; | |||||
| import static org.assertj.core.api.Assertions.assertThat; | |||||
| /** | |||||
| * @author Element | |||||
| * @date 2019-04-20 13:46 | |||||
| */ | |||||
| @Guice(modules = ApiTestModule.class) | |||||
| public class WxCpOAServiceImplTest { | |||||
| @Inject | |||||
| protected WxCpService wxService; | |||||
| @Test | |||||
| 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<WxCpCheckinData> results = wxService.getOAService() | |||||
| .getCheckinData(1, startTime, endTime, Lists.newArrayList("binary")); | |||||
| assertThat(results).isNotNull(); | |||||
| } | |||||
| @Test | |||||
| public void testGetCheckinOption() throws WxErrorException { | |||||
| Date now = new Date(); | |||||
| List<WxCpCheckinOption> results = wxService.getOAService() | |||||
| .getCheckinOption(now, Lists.newArrayList("binary")); | |||||
| assertThat(results).isNotNull(); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,65 @@ | |||||
| 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.WxCpMessage; | |||||
| import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | |||||
| import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton; | |||||
| import org.testng.annotations.Guice; | |||||
| import org.testng.annotations.Test; | |||||
| import java.util.Arrays; | |||||
| import static org.testng.Assert.assertNotNull; | |||||
| /** | |||||
| * 测试任务卡片服务 | |||||
| * | |||||
| * @author <a href="https://github.com/domainname">Jeff</a> | |||||
| * @date 2019-05-16 | |||||
| */ | |||||
| @Guice(modules = ApiTestModule.class) | |||||
| public class WxCpTaskCardServiceImplTest { | |||||
| @Inject | |||||
| private WxCpService wxCpService; | |||||
| @Test | |||||
| public void testSendTaskCard() throws WxErrorException { | |||||
| TaskCardButton btn1 = TaskCardButton.builder() | |||||
| .key("key1") | |||||
| .name("同意") | |||||
| .replaceName("已同意") | |||||
| .bold(true) | |||||
| .build(); | |||||
| TaskCardButton btn2 = TaskCardButton.builder() | |||||
| .key("key2") | |||||
| .name("拒绝") | |||||
| .replaceName("已拒绝") | |||||
| .color("red") | |||||
| .build(); | |||||
| WxCpMessage message = WxCpMessage.TASKCARD() | |||||
| .toUser("jeff|mr.t") | |||||
| .title("有一个待审批的请求") | |||||
| .description("申请:购买图书\n金额:100 元") | |||||
| .taskId("task_1") | |||||
| .url("http://www.qq.com") | |||||
| .buttons(Arrays.asList(btn1, btn2)) | |||||
| .build(); | |||||
| WxCpMessageSendResult messageSendResult = this.wxCpService.messageSend(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 testUpdate() throws Exception { | |||||
| wxCpService.getTaskCardService().update(Arrays.asList("jeff", "mr.t"), "task_1", "key1"); | |||||
| } | |||||
| } | |||||
| @@ -42,7 +42,7 @@ public class WxCpUserServiceImplTest { | |||||
| WxCpUser user = new WxCpUser(); | WxCpUser user = new WxCpUser(); | ||||
| user.setUserId(userId); | user.setUserId(userId); | ||||
| user.setName("Some Woman"); | user.setName("Some Woman"); | ||||
| user.setDepartIds(new Integer[]{2}); | |||||
| user.setDepartIds(new Long[]{2L}); | |||||
| user.setEmail("none@none.com"); | user.setEmail("none@none.com"); | ||||
| user.setGender(Gender.FEMALE); | user.setGender(Gender.FEMALE); | ||||
| user.setMobile("13560084979"); | user.setMobile("13560084979"); | ||||
| @@ -1,7 +1,7 @@ | |||||
| package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
| import org.testng.Assert; | |||||
| import org.testng.annotations.Test; | |||||
| import org.testng.*; | |||||
| import org.testng.annotations.*; | |||||
| /** | /** | ||||
| * Created by huansinho on 2018/4/13. | * Created by huansinho on 2018/4/13. | ||||
| @@ -10,7 +10,13 @@ import org.testng.annotations.Test; | |||||
| public class WxCpAgentTest { | public class WxCpAgentTest { | ||||
| public void testDeserialize() { | public void testDeserialize() { | ||||
| String json = "{\"errcode\": 0,\"errmsg\": \"ok\",\"agentid\": 9,\"name\": \"测试应用\",\"square_logo_url\": \"http://wx.qlogo.cn/mmhead/alksjf;lasdjf;lasjfuodiuj3rj2o34j/0\",\"description\": \"这是一个企业号应用\",\"allow_userinfos\": {\"user\": [{\"userid\": \"0009854\"}, {\"userid\": \"1723\"}, {\"userid\": \"5625\"}]},\"allow_partys\": {\"partyid\": [42762742]},\"allow_tags\": {\"tagid\": [23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7]},\"close\": 0,\"redirect_domain\": \"weixin.com.cn\",\"report_location_flag\": 0,\"isreportenter\": 0,\"home_url\": \"\"}"; | |||||
| String json = "{\"errcode\": 0,\"errmsg\": \"ok\",\"agentid\": 9,\"name\": \"测试应用\"," + | |||||
| "\"square_logo_url\": \"http://wx.qlogo.cn/mmhead/alksjf;lasdjf;lasjfuodiuj3rj2o34j/0\"," + | |||||
| "\"description\": \"这是一个企业号应用\",\"allow_userinfos\": {\"user\": [{\"userid\": \"0009854\"}," + | |||||
| " {\"userid\": \"1723\"}, {\"userid\": \"5625\"}]},\"allow_partys\": {\"partyid\": [42762742]}," + | |||||
| "\"allow_tags\": {\"tagid\": [23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7]}," + | |||||
| "\"close\": 0,\"redirect_domain\": \"weixin.com.cn\",\"report_location_flag\": 0," + | |||||
| "\"isreportenter\": 0,\"home_url\": \"\"}"; | |||||
| WxCpAgent wxCpAgent = WxCpAgent.fromJson(json); | WxCpAgent wxCpAgent = WxCpAgent.fromJson(json); | ||||
| @@ -18,7 +24,8 @@ public class WxCpAgentTest { | |||||
| Assert.assertEquals(new Integer[]{42762742}, wxCpAgent.getAllowParties().getPartyIds().toArray()); | Assert.assertEquals(new Integer[]{42762742}, wxCpAgent.getAllowParties().getPartyIds().toArray()); | ||||
| Assert.assertEquals(new Integer[]{23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7}, wxCpAgent.getAllowTags().getTagIds().toArray()); | |||||
| Assert.assertEquals(new Integer[]{23, 22, 35, 19, 32, 125, 133, 46, 150, 38, 183, 9, 7}, | |||||
| wxCpAgent.getAllowTags().getTagIds().toArray()); | |||||
| } | } | ||||
| @@ -2,10 +2,12 @@ package me.chanjar.weixin.cp.bean; | |||||
| import me.chanjar.weixin.cp.bean.article.MpnewsArticle; | import me.chanjar.weixin.cp.bean.article.MpnewsArticle; | ||||
| import me.chanjar.weixin.cp.bean.article.NewArticle; | import me.chanjar.weixin.cp.bean.article.NewArticle; | ||||
| import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton; | |||||
| import org.testng.annotations.Test; | import org.testng.annotations.Test; | ||||
| import java.util.Arrays; | |||||
| import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
| import static org.testng.Assert.assertEquals; | |||||
| @Test | @Test | ||||
| public class WxCpMessageTest { | public class WxCpMessageTest { | ||||
| @@ -19,12 +21,16 @@ public class WxCpMessageTest { | |||||
| public void testTextCardBuild() { | public void testTextCardBuild() { | ||||
| WxCpMessage reply = WxCpMessage.TEXTCARD().toUser("OPENID") | WxCpMessage reply = WxCpMessage.TEXTCARD().toUser("OPENID") | ||||
| .title("领奖通知") | .title("领奖通知") | ||||
| .description("<div class=\"gray\">2016年9月26日</div> <div class=\"normal\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\"highlight\">请于2016年10月10日前联系行政同事领取</div>") | |||||
| .description("<div class=\"gray\">2016年9月26日</div> <div class=\"normal\">恭喜你抽中iPhone 7一台," + | |||||
| "领奖码:xxxx</div><div class=\"highlight\">请于2016年10月10日前联系行政同事领取</div>") | |||||
| .url("http://www.qq.com") | .url("http://www.qq.com") | ||||
| .btnTxt("更多") | .btnTxt("更多") | ||||
| .build(); | .build(); | ||||
| assertThat(reply.toJson()) | assertThat(reply.toJson()) | ||||
| .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"textcard\",\"textcard\":{\"title\":\"领奖通知\",\"description\":\"<div class=\\\"gray\\\">2016年9月26日</div> <div class=\\\"normal\\\">恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\\\"highlight\\\">请于2016年10月10日前联系行政同事领取</div>\",\"url\":\"http://www.qq.com\",\"btntxt\":\"更多\"},\"safe\":\"0\"}"); | |||||
| .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"textcard\",\"textcard\":{\"title\":\"领奖通知\"," + | |||||
| "\"description\":\"<div class=\\\"gray\\\">2016年9月26日</div> <div class=\\\"normal\\\">" + | |||||
| "恭喜你抽中iPhone 7一台,领奖码:xxxx</div><div class=\\\"highlight\\\">请于2016年10月10日前联系行政同事领取</div>\"," + | |||||
| "\"url\":\"http://www.qq.com\",\"btntxt\":\"更多\"},\"safe\":\"0\"}"); | |||||
| } | } | ||||
| public void testImageBuild() { | public void testImageBuild() { | ||||
| @@ -40,9 +46,11 @@ public class WxCpMessageTest { | |||||
| } | } | ||||
| public void testVideoBuild() { | public void testVideoBuild() { | ||||
| WxCpMessage reply = WxCpMessage.VIDEO().toUser("OPENID").title("TITLE").mediaId("MEDIA_ID").thumbMediaId("MEDIA_ID").description("DESCRIPTION").build(); | |||||
| WxCpMessage reply = WxCpMessage.VIDEO().toUser("OPENID").title("TITLE").mediaId("MEDIA_ID").thumbMediaId("MEDIA_ID") | |||||
| .description("DESCRIPTION").build(); | |||||
| assertThat(reply.toJson()) | assertThat(reply.toJson()) | ||||
| .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"video\",\"safe\":\"0\",\"video\":{\"media_id\":\"MEDIA_ID\",\"thumb_media_id\":\"MEDIA_ID\",\"title\":\"TITLE\",\"description\":\"DESCRIPTION\"}}"); | |||||
| .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"video\",\"video\":{\"media_id\":\"MEDIA_ID\"," + | |||||
| "\"thumb_media_id\":\"MEDIA_ID\",\"title\":\"TITLE\",\"description\":\"DESCRIPTION\"},\"safe\":\"0\"}"); | |||||
| } | } | ||||
| public void testNewsBuild() { | public void testNewsBuild() { | ||||
| @@ -61,7 +69,10 @@ public class WxCpMessageTest { | |||||
| WxCpMessage reply = WxCpMessage.NEWS().toUser("OPENID").addArticle(article1).addArticle(article2).build(); | WxCpMessage reply = WxCpMessage.NEWS().toUser("OPENID").addArticle(article1).addArticle(article2).build(); | ||||
| assertThat(reply.toJson()) | assertThat(reply.toJson()) | ||||
| .isEqualTo( "{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"safe\":\"0\",\"news\":{\"articles\":[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"},{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}}"); | |||||
| .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"news\",\"news\":{\"articles\":" + | |||||
| "[{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}," + | |||||
| "{\"title\":\"Happy Day\",\"description\":\"Is Really A Happy Day\",\"url\":\"URL\",\"picurl\":\"PIC_URL\"}]}," + | |||||
| "\"safe\":\"0\"}"); | |||||
| } | } | ||||
| public void testMpnewsBuild_with_articles() { | public void testMpnewsBuild_with_articles() { | ||||
| @@ -88,14 +99,45 @@ public class WxCpMessageTest { | |||||
| WxCpMessage reply = WxCpMessage.MPNEWS().toUser("OPENID").addArticle(article1, article2).build(); | WxCpMessage reply = WxCpMessage.MPNEWS().toUser("OPENID").addArticle(article1, article2).build(); | ||||
| assertThat(reply.toJson()) | assertThat(reply.toJson()) | ||||
| .isEqualTo( "{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"safe\":\"0\",\"mpnews\":{\"articles\":[{\"title\":\"Happy Day\",\"thumb_media_id\":\"thumb\",\"author\":\"aaaaaa\",\"content_source_url\":\"nice url\",\"content\":\"hahaha\",\"digest\":\"digest\",\"show_cover_pic\":\"heihei\"},{\"title\":\"Happy Day\",\"thumb_media_id\":\"thumb\",\"author\":\"aaaaaa\",\"content_source_url\":\"nice url\",\"content\":\"hahaha\",\"digest\":\"digest\",\"show_cover_pic\":\"heihei\"}]}}"); | |||||
| .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"mpnews\":{\"articles\":" + | |||||
| "[{\"title\":\"Happy Day\",\"thumb_media_id\":\"thumb\",\"author\":\"aaaaaa\"," + | |||||
| "\"content_source_url\":\"nice url\",\"content\":\"hahaha\",\"digest\":\"digest\",\"show_cover_pic\":\"heihei\"}" + | |||||
| ",{\"title\":\"Happy Day\",\"thumb_media_id\":\"thumb\",\"author\":\"aaaaaa\"," + | |||||
| "\"content_source_url\":\"nice url\",\"content\":\"hahaha\",\"digest\":\"digest\",\"show_cover_pic\":\"heihei\"}]}," + | |||||
| "\"safe\":\"0\"}"); | |||||
| } | } | ||||
| public void testMpnewsBuild_with_media_id() { | public void testMpnewsBuild_with_media_id() { | ||||
| WxCpMessage reply = WxCpMessage.MPNEWS().toUser("OPENID").mediaId("mmm").build(); | WxCpMessage reply = WxCpMessage.MPNEWS().toUser("OPENID").mediaId("mmm").build(); | ||||
| assertThat(reply.toJson()) | assertThat(reply.toJson()) | ||||
| .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"safe\":\"0\",\"mpnews\":{\"media_id\":\"mmm\"}}"); | |||||
| .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"mpnews\",\"mpnews\":{\"media_id\":\"mmm\"},\"safe\":\"0\"}"); | |||||
| } | |||||
| public void testTaskCardBuilder() { | |||||
| TaskCardButton button1 = TaskCardButton.builder() | |||||
| .key("yes") | |||||
| .name("批准") | |||||
| .replaceName("已批准") | |||||
| .color("blue") | |||||
| .bold(true) | |||||
| .build(); | |||||
| TaskCardButton button2 = TaskCardButton.builder() | |||||
| .key("yes") | |||||
| .name("拒绝") | |||||
| .replaceName("已拒绝") | |||||
| .color("red") | |||||
| .bold(false) | |||||
| .build(); | |||||
| WxCpMessage reply = WxCpMessage.TASKCARD().toUser("OPENID") | |||||
| .title("任务卡片") | |||||
| .description("有一条待处理任务") | |||||
| .url("http://www.qq.com") | |||||
| .taskId("task_123") | |||||
| .buttons(Arrays.asList(button1, button2)) | |||||
| .build(); | |||||
| assertThat(reply.toJson()) | |||||
| .isEqualTo("{\"touser\":\"OPENID\",\"msgtype\":\"taskcard\",\"taskcard\":{\"title\":\"任务卡片\",\"description\":\"有一条待处理任务\",\"url\":\"http://www.qq.com\",\"task_id\":\"task_123\",\"btn\":[{\"key\":\"yes\",\"name\":\"批准\",\"replace_name\":\"已批准\",\"color\":\"blue\",\"is_bold\":true},{\"key\":\"yes\",\"name\":\"拒绝\",\"replace_name\":\"已拒绝\",\"color\":\"red\",\"is_bold\":false}]}}"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,9 +1,11 @@ | |||||
| package me.chanjar.weixin.cp.bean; | package me.chanjar.weixin.cp.bean; | ||||
| import me.chanjar.weixin.common.api.WxConsts; | import me.chanjar.weixin.common.api.WxConsts; | ||||
| import org.testng.annotations.*; | |||||
| import org.testng.annotations.Test; | |||||
| import static org.testng.Assert.*; | |||||
| import static me.chanjar.weixin.cp.WxCpConsts.EventType.TASKCARD_CLICK; | |||||
| import static org.testng.Assert.assertEquals; | |||||
| import static org.testng.Assert.assertNotNull; | |||||
| @Test | @Test | ||||
| public class WxCpXmlMessageTest { | public class WxCpXmlMessageTest { | ||||
| @@ -117,4 +119,58 @@ public class WxCpXmlMessageTest { | |||||
| assertEquals(wxMessage.getSendPicsInfo().getPicList().get(0).getPicMd5Sum(), "aef52ae501537e552725c5d7f99c1741"); | assertEquals(wxMessage.getSendPicsInfo().getPicList().get(0).getPicMd5Sum(), "aef52ae501537e552725c5d7f99c1741"); | ||||
| assertEquals(wxMessage.getSendPicsInfo().getPicList().get(1).getPicMd5Sum(), "c4564632a4fab91378c39bea6aad6f9e"); | assertEquals(wxMessage.getSendPicsInfo().getPicList().get(1).getPicMd5Sum(), "c4564632a4fab91378c39bea6aad6f9e"); | ||||
| } | } | ||||
| public void testExtAttr() { | |||||
| String xml = "<xml>" + | |||||
| " <ToUserName><![CDATA[w56c9fe3d50ad1ea2]]></ToUserName>" + | |||||
| " <FromUserName><![CDATA[sys]]></FromUserName>" + | |||||
| " <CreateTime>1557241961</CreateTime>" + | |||||
| " <MsgType><![CDATA[event]]></MsgType>" + | |||||
| " <Event><![CDATA[change_contact]]></Event>" + | |||||
| " <ChangeType><![CDATA[update_user]]></ChangeType>" + | |||||
| " <UserID><![CDATA[zhangsan]]></UserID>" + | |||||
| " <ExtAttr>" + | |||||
| " <Item><Name><![CDATA[爱好]]></Name><Value><![CDATA[111]]></Value><Text><Value><![CDATA[111]]></Value></Text></Item>" + | |||||
| " <Item><Name><![CDATA[入职时间]]></Name><Value><![CDATA[11111]]></Value><Text><Value><![CDATA[11111]]></Value></Text></Item>" + | |||||
| " <Item><Name><![CDATA[城市]]></Name><Value><![CDATA[11111]]></Value><Text><Value><![CDATA[11111]]></Value></Text></Item>" + | |||||
| " </ExtAttr>" + | |||||
| " <Address><![CDATA[11111]]></Address>" + | |||||
| "</xml>"; | |||||
| WxCpXmlMessage wxMessage = WxCpXmlMessage.fromXml(xml); | |||||
| assertEquals(wxMessage.getToUserName(), "w56c9fe3d50ad1ea2"); | |||||
| assertEquals(wxMessage.getFromUserName(), "sys"); | |||||
| assertEquals(wxMessage.getCreateTime(), new Long(1557241961)); | |||||
| assertEquals(wxMessage.getMsgType(), WxConsts.XmlMsgType.EVENT); | |||||
| assertEquals(wxMessage.getEvent(), "change_contact"); | |||||
| assertEquals(wxMessage.getChangeType(), "update_user"); | |||||
| assertEquals(wxMessage.getUserId(), "zhangsan"); | |||||
| assertNotNull(wxMessage.getExtAttrs()); | |||||
| assertNotNull(wxMessage.getExtAttrs().getItems()); | |||||
| assertEquals(wxMessage.getExtAttrs().getItems().size(), 3); | |||||
| assertEquals(wxMessage.getExtAttrs().getItems().get(0).getName(), "爱好"); | |||||
| } | |||||
| public void testTaskCardEvent() { | |||||
| String xml = "<xml>" + | |||||
| "<ToUserName><![CDATA[toUser]]></ToUserName>" + | |||||
| "<FromUserName><![CDATA[FromUser]]></FromUserName>" + | |||||
| "<CreateTime>123456789</CreateTime>" + | |||||
| "<MsgType><![CDATA[event]]></MsgType>" + | |||||
| "<Event><![CDATA[taskcard_click]]></Event>" + | |||||
| "<EventKey><![CDATA[key111]]></EventKey>" + | |||||
| "<TaskId><![CDATA[taskid111]]></TaskId >" + | |||||
| "<AgentID>1</AgentID>" + | |||||
| "</xml>"; | |||||
| WxCpXmlMessage wxMessage = WxCpXmlMessage.fromXml(xml); | |||||
| assertEquals(wxMessage.getToUserName(), "toUser"); | |||||
| assertEquals(wxMessage.getFromUserName(), "FromUser"); | |||||
| assertEquals(wxMessage.getCreateTime(), Long.valueOf(123456789L)); | |||||
| assertEquals(wxMessage.getMsgType(), WxConsts.XmlMsgType.EVENT); | |||||
| assertEquals(wxMessage.getAgentId(), Integer.valueOf(1)); | |||||
| assertEquals(wxMessage.getEvent(), TASKCARD_CLICK); | |||||
| assertEquals(wxMessage.getEventKey(), "key111"); | |||||
| assertEquals(wxMessage.getTaskId(), "taskid111"); | |||||
| } | |||||
| } | } | ||||
| @@ -1,28 +1,23 @@ | |||||
| package me.chanjar.weixin.cp.demo; | package me.chanjar.weixin.cp.demo; | ||||
| import java.io.InputStream; | |||||
| import com.thoughtworks.xstream.XStream; | import com.thoughtworks.xstream.XStream; | ||||
| import com.thoughtworks.xstream.annotations.XStreamAlias; | import com.thoughtworks.xstream.annotations.XStreamAlias; | ||||
| import lombok.ToString; | |||||
| import me.chanjar.weixin.common.util.xml.XStreamInitializer; | import me.chanjar.weixin.common.util.xml.XStreamInitializer; | ||||
| import me.chanjar.weixin.cp.config.WxCpInMemoryConfigStorage; | import me.chanjar.weixin.cp.config.WxCpInMemoryConfigStorage; | ||||
| import java.io.InputStream; | |||||
| /** | /** | ||||
| * @author Daniel Qian | * @author Daniel Qian | ||||
| */ | */ | ||||
| @XStreamAlias("xml") | @XStreamAlias("xml") | ||||
| class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage { | |||||
| @ToString | |||||
| public class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage { | |||||
| public static WxCpDemoInMemoryConfigStorage fromXml(InputStream is) { | public static WxCpDemoInMemoryConfigStorage fromXml(InputStream is) { | ||||
| XStream xstream = XStreamInitializer.getInstance(); | XStream xstream = XStreamInitializer.getInstance(); | ||||
| xstream.processAnnotations(WxCpDemoInMemoryConfigStorage.class); | xstream.processAnnotations(WxCpDemoInMemoryConfigStorage.class); | ||||
| return (WxCpDemoInMemoryConfigStorage) xstream.fromXML(is); | return (WxCpDemoInMemoryConfigStorage) xstream.fromXML(is); | ||||
| } | } | ||||
| @Override | |||||
| public String toString() { | |||||
| return "SimpleWxConfigProvider [appidOrCorpid=" + this.corpId + ", corpSecret=" + this.corpSecret + ", accessToken=" + this.accessToken | |||||
| + ", expiresTime=" + this.expiresTime + ", token=" + this.token + ", aesKey=" + this.aesKey + "]"; | |||||
| } | |||||
| } | } | ||||
| @@ -76,6 +76,13 @@ public class WxCpUserGsonAdapterTest { | |||||
| final WxCpUser user = WxCpUser.fromJson(userJson); | final WxCpUser user = WxCpUser.fromJson(userJson); | ||||
| assertThat(user).isNotNull(); | assertThat(user).isNotNull(); | ||||
| assertThat(user.getOrders()).isNotEmpty(); | |||||
| assertThat(user.getOrders().length).isEqualTo(2); | |||||
| assertThat(user.getOrders()[0]).isEqualTo(1); | |||||
| assertThat(user.getOrders()[1]).isEqualTo(2); | |||||
| assertThat(user.getExternalAttrs()).isNotEmpty(); | assertThat(user.getExternalAttrs()).isNotEmpty(); | ||||
| final WxCpUser.ExternalAttribute externalAttr1 = user.getExternalAttrs().get(0); | final WxCpUser.ExternalAttribute externalAttr1 = user.getExternalAttrs().get(0); | ||||
| @@ -100,6 +107,7 @@ public class WxCpUserGsonAdapterTest { | |||||
| @Test | @Test | ||||
| public void testSerialize() { | public void testSerialize() { | ||||
| WxCpUser user = new WxCpUser(); | WxCpUser user = new WxCpUser(); | ||||
| user.setOrders(new Integer[]{1, 2}); | |||||
| user.addExternalAttr(WxCpUser.ExternalAttribute.builder() | user.addExternalAttr(WxCpUser.ExternalAttribute.builder() | ||||
| .type(0) | .type(0) | ||||
| .name("文本名称") | .name("文本名称") | ||||
| @@ -119,6 +127,10 @@ public class WxCpUserGsonAdapterTest { | |||||
| .title("my miniprogram") | .title("my miniprogram") | ||||
| .build()); | .build()); | ||||
| assertThat(user.toJson()).isEqualTo("{\"external_profile\":{\"external_attr\":[{\"type\":0,\"name\":\"文本名称\",\"text\":{\"value\":\"文本\"}},{\"type\":1,\"name\":\"网页名称\",\"web\":{\"url\":\"http://www.test.com\",\"title\":\"标题\"}},{\"type\":2,\"name\":\"测试app\",\"miniprogram\":{\"appid\":\"wx8bd80126147df384\",\"pagepath\":\"/index\",\"title\":\"my miniprogram\"}}]}}"); | |||||
| assertThat(user.toJson()).isEqualTo("{\"order\":[1,2],\"external_profile\":{\"external_attr\":" + | |||||
| "[{\"type\":0,\"name\":\"文本名称\",\"text\":{\"value\":\"文本\"}}," + | |||||
| "{\"type\":1,\"name\":\"网页名称\",\"web\":{\"url\":\"http://www.test.com\",\"title\":\"标题\"}}," + | |||||
| "{\"type\":2,\"name\":\"测试app\"," + | |||||
| "\"miniprogram\":{\"appid\":\"wx8bd80126147df384\",\"pagepath\":\"/index\",\"title\":\"my miniprogram\"}}]}}"); | |||||
| } | } | ||||
| } | } | ||||
| @@ -7,11 +7,12 @@ | |||||
| <parent> | <parent> | ||||
| <groupId>com.github.binarywang</groupId> | <groupId>com.github.binarywang</groupId> | ||||
| <artifactId>wx-java</artifactId> | <artifactId>wx-java</artifactId> | ||||
| <version>3.3.0</version> | |||||
| <version>3.4.0</version> | |||||
| </parent> | </parent> | ||||
| <artifactId>weixin-java-miniapp</artifactId> | <artifactId>weixin-java-miniapp</artifactId> | ||||
| <name>WxJava - MiniApp</name> | |||||
| <description>微信小程序Java SDK</description> | |||||
| <name>WxJava - MiniApp Java SDK</name> | |||||
| <description>微信小程序 Java SDK</description> | |||||
| <dependencies> | <dependencies> | ||||
| <dependency> | <dependency> | ||||
| @@ -70,6 +71,11 @@ | |||||
| <groupId>redis.clients</groupId> | <groupId>redis.clients</groupId> | ||||
| <artifactId>jedis</artifactId> | <artifactId>jedis</artifactId> | ||||
| </dependency> | </dependency> | ||||
| <dependency> | |||||
| <groupId>org.bouncycastle</groupId> | |||||
| <artifactId>bcpkix-jdk15on</artifactId> | |||||
| <version>1.59</version> | |||||
| </dependency> | |||||
| <dependency> | <dependency> | ||||
| <groupId>org.projectlombok</groupId> | <groupId>org.projectlombok</groupId> | ||||
| <artifactId>lombok</artifactId> | <artifactId>lombok</artifactId> | ||||
| @@ -1,14 +1,10 @@ | |||||
| package cn.binarywang.wx.miniapp.api; | package cn.binarywang.wx.miniapp.api; | ||||
| import java.util.List; | |||||
| import cn.binarywang.wx.miniapp.bean.code.WxMaCategory; | |||||
| import cn.binarywang.wx.miniapp.bean.code.WxMaCodeAuditStatus; | |||||
| 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.bean.code.*; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import java.util.List; | |||||
| /** | /** | ||||
| * 小程序代码管理相关 API(大部分只能是第三方平台调用) | * 小程序代码管理相关 API(大部分只能是第三方平台调用) | ||||
| * 文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140610_Uavc4&token=&lang=zh_CN | * 文档:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140610_Uavc4&token=&lang=zh_CN | ||||
| @@ -18,7 +14,7 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||||
| */ | */ | ||||
| public interface WxMaCodeService { | public interface WxMaCodeService { | ||||
| /** | /** | ||||
| * 为授权的小程序帐号上传小程序代码 | |||||
| * 为授权的小程序帐号上传小程序代码. | |||||
| */ | */ | ||||
| String COMMIT_URL = "https://api.weixin.qq.com/wxa/commit"; | String COMMIT_URL = "https://api.weixin.qq.com/wxa/commit"; | ||||
| String GET_QRCODE_URL = "https://api.weixin.qq.com/wxa/get_qrcode"; | String GET_QRCODE_URL = "https://api.weixin.qq.com/wxa/get_qrcode"; | ||||
| @@ -35,7 +31,7 @@ public interface WxMaCodeService { | |||||
| String UNDO_CODE_AUDIT_URL = "https://api.weixin.qq.com/wxa/undocodeaudit"; | String UNDO_CODE_AUDIT_URL = "https://api.weixin.qq.com/wxa/undocodeaudit"; | ||||
| /** | /** | ||||
| * 为授权的小程序帐号上传小程序代码(仅仅支持第三方开放平台) | |||||
| * 为授权的小程序帐号上传小程序代码(仅仅支持第三方开放平台). | |||||
| * | * | ||||
| * @param commitRequest 参数 | * @param commitRequest 参数 | ||||
| * @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档 | * @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档 | ||||
| @@ -43,19 +39,19 @@ public interface WxMaCodeService { | |||||
| void commit(WxMaCodeCommitRequest commitRequest) throws WxErrorException; | void commit(WxMaCodeCommitRequest commitRequest) throws WxErrorException; | ||||
| /** | /** | ||||
| * 获取体验小程序的体验二维码 | |||||
| * 获取体验小程序的体验二维码. | |||||
| * 文档地址: | * 文档地址: | ||||
| * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140610_Uavc4&token=&lang=zh_CN | * https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1489140610_Uavc4&token=&lang=zh_CN | ||||
| * | * | ||||
| * @param path 指定体验版二维码跳转到某个具体页面(如果不需要的话,则不需要填path参数,可在路径后以“?参数”方式传入参数) | * @param path 指定体验版二维码跳转到某个具体页面(如果不需要的话,则不需要填path参数,可在路径后以“?参数”方式传入参数) | ||||
| * 具体的路径加参数需要urlencode(方法内部处理),比如page/index?action=1编码后得到page%2Findex%3Faction%3D1 | |||||
| * 具体的路径加参数需要urlencode(方法内部处理),比如page/index?action=1编码后得到page%2Findex%3Faction%3D1 | |||||
| * @return 二维码 bytes | * @return 二维码 bytes | ||||
| * @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档 | * @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档 | ||||
| */ | */ | ||||
| byte[] getQrCode(String path) throws WxErrorException; | byte[] getQrCode(String path) throws WxErrorException; | ||||
| /** | /** | ||||
| * 获取授权小程序帐号的可选类目 | |||||
| * 获取授权小程序帐号的可选类目. | |||||
| * | * | ||||
| * @return List<WxMaCategory> | * @return List<WxMaCategory> | ||||
| * @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档 | * @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档 | ||||
| @@ -63,7 +59,7 @@ public interface WxMaCodeService { | |||||
| List<WxMaCategory> getCategory() throws WxErrorException; | List<WxMaCategory> getCategory() throws WxErrorException; | ||||
| /** | /** | ||||
| * 获取小程序的第三方提交代码的页面配置(仅供第三方开发者代小程序调用) | |||||
| * 获取小程序的第三方提交代码的页面配置(仅供第三方开发者代小程序调用). | |||||
| * | * | ||||
| * @return page_list 页面配置列表 | * @return page_list 页面配置列表 | ||||
| * @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档 | * @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档 | ||||
| @@ -71,7 +67,7 @@ public interface WxMaCodeService { | |||||
| List<String> getPage() throws WxErrorException; | List<String> getPage() throws WxErrorException; | ||||
| /** | /** | ||||
| * 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用) | |||||
| * 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用). | |||||
| * | * | ||||
| * @param auditRequest 提交审核参数 | * @param auditRequest 提交审核参数 | ||||
| * @return 审核编号 | * @return 审核编号 | ||||
| @@ -80,7 +76,7 @@ public interface WxMaCodeService { | |||||
| long submitAudit(WxMaCodeSubmitAuditRequest auditRequest) throws WxErrorException; | long submitAudit(WxMaCodeSubmitAuditRequest auditRequest) throws WxErrorException; | ||||
| /** | /** | ||||
| * 查询某个指定版本的审核状态(仅供第三方代小程序调用) | |||||
| * 查询某个指定版本的审核状态(仅供第三方代小程序调用). | |||||
| * | * | ||||
| * @param auditId 提交审核时获得的审核id | * @param auditId 提交审核时获得的审核id | ||||
| * @return 审核状态 | * @return 审核状态 | ||||
| @@ -89,7 +85,7 @@ public interface WxMaCodeService { | |||||
| WxMaCodeAuditStatus getAuditStatus(long auditId) throws WxErrorException; | WxMaCodeAuditStatus getAuditStatus(long auditId) throws WxErrorException; | ||||
| /** | /** | ||||
| * 查询最新一次提交的审核状态(仅供第三方代小程序调用) | |||||
| * 查询最新一次提交的审核状态(仅供第三方代小程序调用). | |||||
| * | * | ||||
| * @return 审核状态 | * @return 审核状态 | ||||
| * @throws WxErrorException 查询失败时返回,具体错误码请看此接口的注释文档 | * @throws WxErrorException 查询失败时返回,具体错误码请看此接口的注释文档 | ||||
| @@ -97,14 +93,14 @@ public interface WxMaCodeService { | |||||
| WxMaCodeAuditStatus getLatestAuditStatus() throws WxErrorException; | WxMaCodeAuditStatus getLatestAuditStatus() throws WxErrorException; | ||||
| /** | /** | ||||
| * 发布已通过审核的小程序(仅供第三方代小程序调用) | |||||
| * 发布已通过审核的小程序(仅供第三方代小程序调用). | |||||
| * | * | ||||
| * @throws WxErrorException 发布失败时抛出,具体错误码请看此接口的注释文档 | * @throws WxErrorException 发布失败时抛出,具体错误码请看此接口的注释文档 | ||||
| */ | */ | ||||
| void release() throws WxErrorException; | void release() throws WxErrorException; | ||||
| /** | /** | ||||
| * 修改小程序线上代码的可见状态(仅供第三方代小程序调用) | |||||
| * 修改小程序线上代码的可见状态(仅供第三方代小程序调用). | |||||
| * | * | ||||
| * @param action 设置可访问状态,发布后默认可访问,close为不可见,open为可见 | * @param action 设置可访问状态,发布后默认可访问,close为不可见,open为可见 | ||||
| * @throws WxErrorException 发布失败时抛出,具体错误码请看此接口的注释文档 | * @throws WxErrorException 发布失败时抛出,具体错误码请看此接口的注释文档 | ||||
| @@ -112,14 +108,14 @@ public interface WxMaCodeService { | |||||
| void changeVisitStatus(String action) throws WxErrorException; | void changeVisitStatus(String action) throws WxErrorException; | ||||
| /** | /** | ||||
| * 小程序版本回退(仅供第三方代小程序调用) | |||||
| * 小程序版本回退(仅供第三方代小程序调用). | |||||
| * | * | ||||
| * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | ||||
| */ | */ | ||||
| void revertCodeRelease() throws WxErrorException; | void revertCodeRelease() throws WxErrorException; | ||||
| /** | /** | ||||
| * 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用) | |||||
| * 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用). | |||||
| * | * | ||||
| * @return 小程序版本分布信息 | * @return 小程序版本分布信息 | ||||
| * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | ||||
| @@ -127,7 +123,7 @@ public interface WxMaCodeService { | |||||
| WxMaCodeVersionDistribution getSupportVersion() throws WxErrorException; | WxMaCodeVersionDistribution getSupportVersion() throws WxErrorException; | ||||
| /** | /** | ||||
| * 设置最低基础库版本(仅供第三方代小程序调用) | |||||
| * 设置最低基础库版本(仅供第三方代小程序调用). | |||||
| * | * | ||||
| * @param version 版本 | * @param version 版本 | ||||
| * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | ||||
| @@ -135,7 +131,7 @@ public interface WxMaCodeService { | |||||
| void setSupportVersion(String version) throws WxErrorException; | void setSupportVersion(String version) throws WxErrorException; | ||||
| /** | /** | ||||
| * 小程序审核撤回 | |||||
| * 小程序审核撤回. | |||||
| * 单个帐号每天审核撤回次数最多不超过1次,一个月不超过10次 | * 单个帐号每天审核撤回次数最多不超过1次,一个月不超过10次 | ||||
| * | * | ||||
| * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | * @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | ||||
| @@ -20,7 +20,7 @@ public interface WxMaMsgService { | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 发送客服消息 | * 发送客服消息 | ||||
| * 详情请见: <a href="https://mp.weixin.qq.com/debug/wxadoc/dev/api/custommsg/conversation.html">发送客服消息</a> | |||||
| * 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/customerServiceMessage.send.html">发送客服消息</a> | |||||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN | * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=ACCESS_TOKEN | ||||
| * </pre> | * </pre> | ||||
| */ | */ | ||||
| @@ -29,7 +29,7 @@ public interface WxMaMsgService { | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 发送模板消息 | * 发送模板消息 | ||||
| * 详情请见: <a href="https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#接口说明">发送模板消息</a> | |||||
| * 详情请见: <a href="https://developers.weixin.qq.com/miniprogram/dev/api-backend/templateMessage.send.html">发送模板消息</a> | |||||
| * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN | * 接口url格式:https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=ACCESS_TOKEN | ||||
| * </pre> | * </pre> | ||||
| */ | */ | ||||
| @@ -17,6 +17,10 @@ public interface WxMaService { | |||||
| String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; | String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"; | ||||
| String JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session"; | String JSCODE_TO_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session"; | ||||
| /** | |||||
| * getPaidUnionId | |||||
| */ | |||||
| String GET_PAID_UNION_ID_URL = "https://api.weixin.qq.com/wxa/getpaidunionid"; | |||||
| /** | /** | ||||
| * 获取登录后的session信息. | * 获取登录后的session信息. | ||||
| @@ -56,6 +60,22 @@ public interface WxMaService { | |||||
| */ | */ | ||||
| String getAccessToken(boolean forceRefresh) throws WxErrorException; | String getAccessToken(boolean forceRefresh) throws WxErrorException; | ||||
| /** | |||||
| * <pre> | |||||
| * 用户支付完成后,获取该用户的 UnionId,无需用户授权。本接口支持第三方平台代理查询。 | |||||
| * | |||||
| * 注意:调用前需要用户完成支付,且在支付后的五分钟内有效。 | |||||
| * 请求地址: GET https://api.weixin.qq.com/wxa/getpaidunionid?access_token=ACCESS_TOKEN&openid=OPENID | |||||
| * 文档地址:https://developers.weixin.qq.com/miniprogram/dev/api/getPaidUnionId.html | |||||
| * </pre> | |||||
| * | |||||
| * @param openid 必填 支付用户唯一标识 | |||||
| * @param transactionId 非必填 微信支付订单号 | |||||
| * @param mchId 非必填 微信支付分配的商户号,和商户订单号配合使用 | |||||
| * @param outTradeNo 非必填 微信支付商户订单号,和商户号配合使用 | |||||
| */ | |||||
| String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo) throws WxErrorException; | |||||
| /** | /** | ||||
| * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. | * 当本Service没有实现某个API的时候,可以用这个,针对所有微信API中的GET请求. | ||||
| */ | */ | ||||
| @@ -168,18 +188,21 @@ public interface WxMaService { | |||||
| /** | /** | ||||
| * 返回分享相关查询服务. | * 返回分享相关查询服务. | ||||
| * | |||||
| * @return WxMaShareService | * @return WxMaShareService | ||||
| */ | */ | ||||
| WxMaShareService getShareService(); | WxMaShareService getShareService(); | ||||
| /** | /** | ||||
| * 返回微信运动相关接口服务对象. | * 返回微信运动相关接口服务对象. | ||||
| * | |||||
| * @return WxMaShareService | * @return WxMaShareService | ||||
| */ | */ | ||||
| WxMaRunService getRunService(); | WxMaRunService getRunService(); | ||||
| /** | /** | ||||
| * 返回内容安全相关接口服务对象. | * 返回内容安全相关接口服务对象. | ||||
| * | |||||
| * @return WxMaShareService | * @return WxMaShareService | ||||
| */ | */ | ||||
| WxMaSecCheckService getSecCheckService(); | WxMaSecCheckService getSecCheckService(); | ||||
| @@ -1,47 +1,33 @@ | |||||
| package cn.binarywang.wx.miniapp.api.impl; | package cn.binarywang.wx.miniapp.api.impl; | ||||
| import java.io.IOException; | |||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.locks.Lock; | |||||
| import org.apache.http.HttpHost; | |||||
| import org.apache.http.client.config.RequestConfig; | |||||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||||
| import org.apache.http.client.methods.HttpGet; | |||||
| import org.apache.http.impl.client.BasicResponseHandler; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaAnalysisService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaCodeService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaJsapiService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaMediaService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaMsgService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaQrcodeService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaRunService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaSecCheckService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaSettingService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaShareService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaTemplateService; | |||||
| import cn.binarywang.wx.miniapp.api.WxMaUserService; | |||||
| import cn.binarywang.wx.miniapp.api.*; | |||||
| import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult; | ||||
| import cn.binarywang.wx.miniapp.config.WxMaConfig; | import cn.binarywang.wx.miniapp.config.WxMaConfig; | ||||
| import com.google.common.base.Joiner; | import com.google.common.base.Joiner; | ||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||
| import com.google.gson.JsonParser; | |||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
| import me.chanjar.weixin.common.WxType; | |||||
| import me.chanjar.weixin.common.bean.WxAccessToken; | import me.chanjar.weixin.common.bean.WxAccessToken; | ||||
| import me.chanjar.weixin.common.error.WxError; | import me.chanjar.weixin.common.error.WxError; | ||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import me.chanjar.weixin.common.util.DataUtils; | import me.chanjar.weixin.common.util.DataUtils; | ||||
| import me.chanjar.weixin.common.util.crypto.SHA1; | import me.chanjar.weixin.common.util.crypto.SHA1; | ||||
| import me.chanjar.weixin.common.util.http.HttpType; | |||||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.*; | |||||
| import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | ||||
| import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | ||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.apache.http.HttpHost; | |||||
| import org.apache.http.client.config.RequestConfig; | |||||
| import org.apache.http.client.methods.CloseableHttpResponse; | |||||
| import org.apache.http.client.methods.HttpGet; | |||||
| import org.apache.http.impl.client.BasicResponseHandler; | |||||
| import org.apache.http.impl.client.CloseableHttpClient; | |||||
| import java.io.IOException; | |||||
| import java.util.HashMap; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.locks.Lock; | |||||
| import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*; | import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*; | ||||
| @@ -50,6 +36,7 @@ import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*; | |||||
| */ | */ | ||||
| @Slf4j | @Slf4j | ||||
| public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpClient, HttpHost> { | public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpClient, HttpHost> { | ||||
| private static final JsonParser JSON_PARSER = new JsonParser(); | |||||
| private CloseableHttpClient httpClient; | private CloseableHttpClient httpClient; | ||||
| private HttpHost httpProxy; | private HttpHost httpProxy; | ||||
| private WxMaConfig wxMaConfig; | private WxMaConfig wxMaConfig; | ||||
| @@ -114,40 +101,68 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||||
| @Override | @Override | ||||
| public String getAccessToken(boolean forceRefresh) throws WxErrorException { | public String getAccessToken(boolean forceRefresh) throws WxErrorException { | ||||
| if (!this.getWxMaConfig().isAccessTokenExpired() && !forceRefresh) { | |||||
| return this.getWxMaConfig().getAccessToken(); | |||||
| } | |||||
| Lock lock = this.getWxMaConfig().getAccessTokenLock(); | Lock lock = this.getWxMaConfig().getAccessTokenLock(); | ||||
| lock.lock(); | |||||
| try { | try { | ||||
| lock.lock(); | |||||
| if (this.getWxMaConfig().isAccessTokenExpired() || forceRefresh) { | |||||
| String url = String.format(WxMaService.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(), | |||||
| this.getWxMaConfig().getSecret()); | |||||
| try { | |||||
| HttpGet httpGet = new HttpGet(url); | |||||
| if (this.getRequestHttpProxy() != null) { | |||||
| RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build(); | |||||
| httpGet.setConfig(config); | |||||
| } | |||||
| try (CloseableHttpResponse response = getRequestHttpClient().execute(httpGet)) { | |||||
| String resultContent = new BasicResponseHandler().handleResponse(response); | |||||
| WxError error = WxError.fromJson(resultContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.getWxMaConfig().updateAccessToken(accessToken.getAccessToken(), | |||||
| accessToken.getExpiresIn()); | |||||
| } finally { | |||||
| httpGet.releaseConnection(); | |||||
| String url = String.format(WxMaService.GET_ACCESS_TOKEN_URL, this.getWxMaConfig().getAppid(), | |||||
| this.getWxMaConfig().getSecret()); | |||||
| try { | |||||
| HttpGet httpGet = new HttpGet(url); | |||||
| if (this.getRequestHttpProxy() != null) { | |||||
| RequestConfig config = RequestConfig.custom().setProxy(this.getRequestHttpProxy()).build(); | |||||
| httpGet.setConfig(config); | |||||
| } | |||||
| try (CloseableHttpResponse response = getRequestHttpClient().execute(httpGet)) { | |||||
| String resultContent = new BasicResponseHandler().handleResponse(response); | |||||
| WxError error = WxError.fromJson(resultContent); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | } | ||||
| } catch (IOException e) { | |||||
| throw new RuntimeException(e); | |||||
| WxAccessToken accessToken = WxAccessToken.fromJson(resultContent); | |||||
| this.getWxMaConfig().updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn()); | |||||
| return this.getWxMaConfig().getAccessToken(); | |||||
| } finally { | |||||
| httpGet.releaseConnection(); | |||||
| } | } | ||||
| } catch (IOException e) { | |||||
| throw new RuntimeException(e); | |||||
| } | } | ||||
| } finally { | } finally { | ||||
| lock.unlock(); | lock.unlock(); | ||||
| } | } | ||||
| return this.getWxMaConfig().getAccessToken(); | |||||
| } | |||||
| @Override | |||||
| public String getPaidUnionId(String openid, String transactionId, String mchId, String outTradeNo) | |||||
| throws WxErrorException { | |||||
| Map<String, String> params = new HashMap<>(8); | |||||
| params.put("openid", openid); | |||||
| if (StringUtils.isNotEmpty(transactionId)) { | |||||
| params.put("transaction_id", transactionId); | |||||
| } | |||||
| if (StringUtils.isNotEmpty(mchId)) { | |||||
| params.put("mch_id", mchId); | |||||
| } | |||||
| if (StringUtils.isNotEmpty(outTradeNo)) { | |||||
| params.put("out_trade_no", outTradeNo); | |||||
| } | |||||
| String responseContent = this.get(GET_PAID_UNION_ID_URL, Joiner.on("&").withKeyValueSeparator("=").join(params)); | |||||
| WxError error = WxError.fromJson(responseContent, WxType.MiniApp); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | |||||
| return JSON_PARSER.parse(responseContent).getAsJsonObject().get("unionid").getAsString(); | |||||
| } | } | ||||
| @Override | @Override | ||||
| @@ -168,7 +183,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||||
| try { | try { | ||||
| return SHA1.gen(this.getWxMaConfig().getToken(), timestamp, nonce).equals(signature); | return SHA1.gen(this.getWxMaConfig().getToken(), timestamp, nonce).equals(signature); | ||||
| } catch (Exception e) { | } catch (Exception e) { | ||||
| this.log.error("Checking signature failed, and the reason is :" + e.getMessage()); | |||||
| log.error("Checking signature failed, and the reason is :" + e.getMessage()); | |||||
| return false; | return false; | ||||
| } | } | ||||
| } | } | ||||
| @@ -246,7 +261,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||||
| if (error.getErrorCode() == ERR_40001 | if (error.getErrorCode() == ERR_40001 | ||||
| || error.getErrorCode() == ERR_42001 | || error.getErrorCode() == ERR_42001 | ||||
| || error.getErrorCode() == ERR_40014) { | || error.getErrorCode() == ERR_40014) { | ||||
| // 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||||
| // 强制设置WxMaConfig的access token过期了,这样在下一次请求里就会刷新access token | |||||
| this.getWxMaConfig().expireAccessToken(); | this.getWxMaConfig().expireAccessToken(); | ||||
| if (this.getWxMaConfig().autoRefreshToken()) { | if (this.getWxMaConfig().autoRefreshToken()) { | ||||
| return this.execute(executor, uri, data); | return this.execute(executor, uri, data); | ||||
| @@ -41,20 +41,26 @@ public class WxMaKefuMessage implements Serializable { | |||||
| @Data | @Data | ||||
| @AllArgsConstructor | @AllArgsConstructor | ||||
| public static class KfText { | |||||
| public static class KfText implements Serializable { | |||||
| private static final long serialVersionUID = 151122958720941270L; | |||||
| private String content; | private String content; | ||||
| } | } | ||||
| @Data | @Data | ||||
| @AllArgsConstructor | @AllArgsConstructor | ||||
| public static class KfImage { | |||||
| public static class KfImage implements Serializable { | |||||
| private static final long serialVersionUID = -5409342945117300782L; | |||||
| @SerializedName("media_id") | @SerializedName("media_id") | ||||
| private String mediaId; | private String mediaId; | ||||
| } | } | ||||
| @Data | @Data | ||||
| @Builder | @Builder | ||||
| public static class KfLink { | |||||
| public static class KfLink implements Serializable { | |||||
| private static final long serialVersionUID = -6728776817556127413L; | |||||
| private String title; | private String title; | ||||
| private String description; | private String description; | ||||
| private String url; | private String url; | ||||
| @@ -65,7 +71,9 @@ public class WxMaKefuMessage implements Serializable { | |||||
| @Data | @Data | ||||
| @Builder | @Builder | ||||
| public static class KfMaPage { | |||||
| public static class KfMaPage implements Serializable { | |||||
| private static final long serialVersionUID = -5633492281871634466L; | |||||
| private String title; | private String title; | ||||
| @SerializedName("pagepath") | @SerializedName("pagepath") | ||||
| @@ -5,7 +5,7 @@ import lombok.NoArgsConstructor; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * | |||||
| * 参考文档 https://developers.weixin.qq.com/miniprogram/dev/api-backend/templateMessage.send.html | |||||
| * Created by Binary Wang on 2018/9/23. | * Created by Binary Wang on 2018/9/23. | ||||
| * </pre> | * </pre> | ||||
| * | * | ||||
| @@ -29,4 +29,5 @@ public class WxMaTemplateData { | |||||
| this.color = color; | this.color = color; | ||||
| } | } | ||||
| } | } | ||||
| @@ -13,7 +13,7 @@ import lombok.Setter; | |||||
| /** | /** | ||||
| * 模板消息. | * 模板消息. | ||||
| * 参考 https://mp.weixin.qq.com/debug/wxadoc/dev/api/notice.html#接口说明 模板消息部分 | |||||
| * 参考 https://developers.weixin.qq.com/miniprogram/dev/api-backend/templateMessage.send.html | |||||
| * | * | ||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | * @author <a href="https://github.com/binarywang">Binary Wang</a> | ||||
| */ | */ | ||||
| @@ -75,16 +75,6 @@ public class WxMaTemplateMessage implements Serializable { | |||||
| */ | */ | ||||
| private List<WxMaTemplateData> data; | private List<WxMaTemplateData> data; | ||||
| /** | |||||
| * 模板内容字体的颜色,不填默认黑色. | |||||
| * <pre> | |||||
| * 参数:color | |||||
| * 是否必填: 否 | |||||
| * 描述: 模板内容字体的颜色,不填默认黑色 | |||||
| * </pre> | |||||
| */ | |||||
| private String color; | |||||
| /** | /** | ||||
| * 模板需要放大的关键词,不填则默认无放大. | * 模板需要放大的关键词,不填则默认无放大. | ||||
| * <pre> | * <pre> | ||||
| @@ -104,5 +104,11 @@ public class WxMaUniformMessage implements Serializable { | |||||
| * 加入此字段是基于微信官方接口变化多端的考虑 | * 加入此字段是基于微信官方接口变化多端的考虑 | ||||
| */ | */ | ||||
| private boolean usePath = false; | private boolean usePath = false; | ||||
| /** | |||||
| * 是否使用pagePath,否则使用pagepath. | |||||
| * 加入此字段是基于微信官方接口变化多端的考虑 | |||||
| */ | |||||
| private boolean usePagePath = false; | |||||
| } | } | ||||
| } | } | ||||
| @@ -18,18 +18,24 @@ import java.io.Serializable; | |||||
| public class WxMaCodeAuditStatus implements Serializable { | public class WxMaCodeAuditStatus implements Serializable { | ||||
| private static final long serialVersionUID = 4655119308692217268L; | private static final long serialVersionUID = 4655119308692217268L; | ||||
| /** | /** | ||||
| * 审核 ID | |||||
| * 审核 ID. | |||||
| */ | */ | ||||
| @SerializedName(value = "auditId", alternate = {"auditid"}) | @SerializedName(value = "auditId", alternate = {"auditid"}) | ||||
| private Long auditId; | private Long auditId; | ||||
| /** | /** | ||||
| * 审核状态,其中0为审核成功,1为审核失败,2为审核中 | |||||
| * 审核状态. | |||||
| * 其中0为审核成功,1为审核失败,2为审核中 | |||||
| */ | */ | ||||
| private Integer status; | private Integer status; | ||||
| /** | /** | ||||
| * 当status=1,审核被拒绝时,返回的拒绝原因 | |||||
| * 当status=1,审核被拒绝时,返回的拒绝原因. | |||||
| */ | */ | ||||
| private String reason; | private String reason; | ||||
| /** | |||||
| * 当status=1,审核被拒绝时,会返回审核失败的小程序截图示例。 xxx丨yyy丨zzz是media_id可通过获取永久素材接口 拉取截图内容). | |||||
| */ | |||||
| @SerializedName(value = "screenshot") | |||||
| private String screenShot; | |||||
| public static WxMaCodeAuditStatus fromJson(String json) { | public static WxMaCodeAuditStatus fromJson(String json) { | ||||
| return WxMaGsonBuilder.create().fromJson(json, WxMaCodeAuditStatus.class); | return WxMaGsonBuilder.create().fromJson(json, WxMaCodeAuditStatus.class); | ||||
| @@ -1,19 +1,27 @@ | |||||
| package cn.binarywang.wx.miniapp.util.crypt; | package cn.binarywang.wx.miniapp.util.crypt; | ||||
| import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||||
| import me.chanjar.weixin.common.util.crypto.PKCS7Encoder; | |||||
| import org.apache.commons.codec.binary.Base64; | |||||
| import java.nio.charset.Charset; | |||||
| import java.nio.charset.StandardCharsets; | |||||
| import java.security.AlgorithmParameters; | |||||
| import java.security.Key; | |||||
| import java.security.Security; | |||||
| import java.util.Arrays; | |||||
| import javax.crypto.Cipher; | import javax.crypto.Cipher; | ||||
| import javax.crypto.spec.IvParameterSpec; | import javax.crypto.spec.IvParameterSpec; | ||||
| import javax.crypto.spec.SecretKeySpec; | import javax.crypto.spec.SecretKeySpec; | ||||
| import java.nio.charset.StandardCharsets; | |||||
| import java.security.AlgorithmParameters; | |||||
| import org.apache.commons.codec.binary.Base64; | |||||
| import org.bouncycastle.jce.provider.BouncyCastleProvider; | |||||
| import cn.binarywang.wx.miniapp.config.WxMaConfig; | |||||
| import me.chanjar.weixin.common.util.crypto.PKCS7Encoder; | |||||
| /** | /** | ||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | * @author <a href="https://github.com/binarywang">Binary Wang</a> | ||||
| */ | */ | ||||
| public class WxMaCryptUtils extends me.chanjar.weixin.common.util.crypto.WxCryptUtil { | public class WxMaCryptUtils extends me.chanjar.weixin.common.util.crypto.WxCryptUtil { | ||||
| private static final Charset UTF_8 = StandardCharsets.UTF_8; | |||||
| public WxMaCryptUtils(WxMaConfig config) { | public WxMaCryptUtils(WxMaConfig config) { | ||||
| this.appidOrCorpid = config.getAppid(); | this.appidOrCorpid = config.getAppid(); | ||||
| this.token = config.getToken(); | this.token = config.getToken(); | ||||
| @@ -21,8 +29,9 @@ public class WxMaCryptUtils extends me.chanjar.weixin.common.util.crypto.WxCrypt | |||||
| } | } | ||||
| /** | /** | ||||
| * AES解密 | |||||
| * AES解密. | |||||
| * | * | ||||
| * @param sessionKey session_key | |||||
| * @param encryptedData 消息密文 | * @param encryptedData 消息密文 | ||||
| * @param ivStr iv字符串 | * @param ivStr iv字符串 | ||||
| */ | */ | ||||
| @@ -34,9 +43,40 @@ public class WxMaCryptUtils extends me.chanjar.weixin.common.util.crypto.WxCrypt | |||||
| Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding"); | ||||
| cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(sessionKey), "AES"), params); | cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(sessionKey), "AES"), params); | ||||
| return new String(PKCS7Encoder.decode(cipher.doFinal(Base64.decodeBase64(encryptedData))), StandardCharsets.UTF_8); | |||||
| return new String(PKCS7Encoder.decode(cipher.doFinal(Base64.decodeBase64(encryptedData))), UTF_8); | |||||
| } catch (Exception e) { | |||||
| throw new RuntimeException("AES解密失败!", e); | |||||
| } | |||||
| } | |||||
| /** | |||||
| * AES解密. | |||||
| * | |||||
| * @param sessionKey session_key | |||||
| * @param encryptedData 消息密文 | |||||
| * @param ivStr iv字符串 | |||||
| */ | |||||
| public static String decryptAnotherWay(String sessionKey, String encryptedData, String ivStr) { | |||||
| byte[] keyBytes = Base64.decodeBase64(sessionKey.getBytes(UTF_8)); | |||||
| int base = 16; | |||||
| if (keyBytes.length % base != 0) { | |||||
| int groups = keyBytes.length / base + (keyBytes.length % base != 0 ? 1 : 0); | |||||
| byte[] temp = new byte[groups * base]; | |||||
| Arrays.fill(temp, (byte) 0); | |||||
| System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length); | |||||
| keyBytes = temp; | |||||
| } | |||||
| Security.addProvider(new BouncyCastleProvider()); | |||||
| Key key = new SecretKeySpec(keyBytes, "AES"); | |||||
| try { | |||||
| Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC"); | |||||
| 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) { | } catch (Exception e) { | ||||
| throw new RuntimeException("AES解密失败", e); | |||||
| throw new RuntimeException("AES解密失败!", e); | |||||
| } | } | ||||
| } | } | ||||
| @@ -27,10 +27,6 @@ public class WxMaTemplateMessageGsonAdapter implements JsonSerializer<WxMaTempla | |||||
| messageJson.addProperty("form_id", message.getFormId()); | messageJson.addProperty("form_id", message.getFormId()); | ||||
| } | } | ||||
| if (message.getColor() != null) { | |||||
| messageJson.addProperty("color", message.getColor()); | |||||
| } | |||||
| if (message.getEmphasisKeyword() != null) { | if (message.getEmphasisKeyword() != null) { | ||||
| messageJson.addProperty("emphasis_keyword", message.getEmphasisKeyword()); | messageJson.addProperty("emphasis_keyword", message.getEmphasisKeyword()); | ||||
| } | } | ||||
| @@ -45,9 +41,6 @@ public class WxMaTemplateMessageGsonAdapter implements JsonSerializer<WxMaTempla | |||||
| for (WxMaTemplateData datum : message.getData()) { | for (WxMaTemplateData datum : message.getData()) { | ||||
| JsonObject dataJson = new JsonObject(); | JsonObject dataJson = new JsonObject(); | ||||
| dataJson.addProperty("value", datum.getValue()); | dataJson.addProperty("value", datum.getValue()); | ||||
| if (datum.getColor() != null) { | |||||
| dataJson.addProperty("color", datum.getColor()); | |||||
| } | |||||
| data.add(datum.getName(), dataJson); | data.add(datum.getName(), dataJson); | ||||
| } | } | ||||
| @@ -36,6 +36,8 @@ public class WxMaUniformMessageGsonAdapter implements JsonSerializer<WxMaUniform | |||||
| miniProgramJson.addProperty("appid", miniProgram.getAppid()); | miniProgramJson.addProperty("appid", miniProgram.getAppid()); | ||||
| if (miniProgram.isUsePath()) { | if (miniProgram.isUsePath()) { | ||||
| miniProgramJson.addProperty("path", miniProgram.getPagePath()); | miniProgramJson.addProperty("path", miniProgram.getPagePath()); | ||||
| } else if (miniProgram.isUsePagePath()) { | |||||
| miniProgramJson.addProperty("pagePath", miniProgram.getPagePath()); | |||||
| } else { | } else { | ||||
| miniProgramJson.addProperty("pagepath", miniProgram.getPagePath()); | miniProgramJson.addProperty("pagepath", miniProgram.getPagePath()); | ||||
| } | } | ||||
| @@ -79,9 +81,6 @@ public class WxMaUniformMessageGsonAdapter implements JsonSerializer<WxMaUniform | |||||
| for (WxMaTemplateData templateData : message.getData()) { | for (WxMaTemplateData templateData : message.getData()) { | ||||
| JsonObject dataJson = new JsonObject(); | JsonObject dataJson = new JsonObject(); | ||||
| dataJson.addProperty("value", templateData.getValue()); | dataJson.addProperty("value", templateData.getValue()); | ||||
| if (templateData.getColor() != null) { | |||||
| dataJson.addProperty("color", templateData.getColor()); | |||||
| } | |||||
| data.add(templateData.getName(), dataJson); | data.add(templateData.getName(), dataJson); | ||||
| } | } | ||||
| } | } | ||||
| @@ -10,9 +10,9 @@ import com.google.gson.JsonParseException; | |||||
| import me.chanjar.weixin.common.util.json.GsonHelper; | import me.chanjar.weixin.common.util.json.GsonHelper; | ||||
| import java.lang.reflect.Type; | import java.lang.reflect.Type; | ||||
| import java.util.Hashtable; | |||||
| import java.util.LinkedHashMap; | import java.util.LinkedHashMap; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| import java.util.concurrent.ConcurrentHashMap; | |||||
| /** | /** | ||||
| * @author <a href="https://github.com/charmingoh">Charming</a> | * @author <a href="https://github.com/charmingoh">Charming</a> | ||||
| @@ -36,7 +36,7 @@ public class WxMaVisitDistributionGsonAdapter implements JsonDeserializer<WxMaVi | |||||
| } | } | ||||
| JsonArray listArray = object.getAsJsonArray("list"); | JsonArray listArray = object.getAsJsonArray("list"); | ||||
| Map<String, Map<Integer, Integer>> list = new Hashtable<>(listArray.size()); | |||||
| Map<String, Map<Integer, Integer>> list = new ConcurrentHashMap<>(listArray.size()); | |||||
| for (JsonElement indexElement : listArray) { | for (JsonElement indexElement : listArray) { | ||||
| JsonObject indexObject = indexElement.getAsJsonObject(); | JsonObject indexObject = indexElement.getAsJsonObject(); | ||||
| String index = GsonHelper.getString(indexObject, "index"); | String index = GsonHelper.getString(indexObject, "index"); | ||||
| @@ -48,10 +48,10 @@ public class WxMaMsgServiceImplTest { | |||||
| .formId("FORMID") | .formId("FORMID") | ||||
| .page("index") | .page("index") | ||||
| .data(Lists.newArrayList( | .data(Lists.newArrayList( | ||||
| new WxMaTemplateData("keyword1", "339208499", "#173177"), | |||||
| new WxMaTemplateData("keyword2", dateFormat.format(new Date()), "#173177"), | |||||
| new WxMaTemplateData("keyword3", "粤海喜来登酒店", "#173177"), | |||||
| new WxMaTemplateData("keyword4", "广州市天河区天河路208号", "#173177"))) | |||||
| new WxMaTemplateData("keyword1", "339208499"), | |||||
| new WxMaTemplateData("keyword2", dateFormat.format(new Date())), | |||||
| new WxMaTemplateData("keyword3", "粤海喜来登酒店"), | |||||
| new WxMaTemplateData("keyword4", "广州市天河区天河路208号"))) | |||||
| .templateId(config.getTemplateId()) | .templateId(config.getTemplateId()) | ||||
| .emphasisKeyword("keyword1.DATA") | .emphasisKeyword("keyword1.DATA") | ||||
| .build(); | .build(); | ||||
| @@ -1,7 +1,5 @@ | |||||
| package cn.binarywang.wx.miniapp.api.impl; | package cn.binarywang.wx.miniapp.api.impl; | ||||
| import java.io.File; | |||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import org.testng.annotations.*; | import org.testng.annotations.*; | ||||
| @@ -11,6 +9,7 @@ import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||||
| import com.google.inject.Inject; | import com.google.inject.Inject; | ||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import static org.assertj.core.api.Assertions.assertThat; | |||||
| import static org.testng.Assert.*; | import static org.testng.Assert.*; | ||||
| /** | /** | ||||
| @@ -33,4 +32,9 @@ public class WxMaServiceImplTest { | |||||
| assertTrue(StringUtils.isNotBlank(after)); | assertTrue(StringUtils.isNotBlank(after)); | ||||
| } | } | ||||
| @Test(expectedExceptions = {WxErrorException.class}) | |||||
| public void testGetPaidUnionId() throws WxErrorException { | |||||
| final String unionId = this.wxService.getPaidUnionId("1", null, "3", "4"); | |||||
| assertThat(unionId).isNotEmpty(); | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,35 @@ | |||||
| package cn.binarywang.wx.miniapp.util.crypt; | |||||
| import org.testng.annotations.*; | |||||
| import static org.assertj.core.api.Assertions.assertThat; | |||||
| /** | |||||
| * <pre> | |||||
| * | |||||
| * Created by Binary Wang on 2018/12/25. | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | |||||
| */ | |||||
| public class WxMaCryptUtilsTest { | |||||
| @Test | |||||
| public void testDecrypt() { | |||||
| String sessionKey = "7MG7jbTToVVRWRXVA885rg=="; | |||||
| String encryptedData = "BY6VOgcWbwGcyrunK0ECWI8rnDsT69DucZ+M78tc1aL9aM/3bEAHFYd4fu7kRjWhD4YfjObw44T9vUqKyHIjbKs6hvtEasZZEIW35x4a91xVgN48ZqZ7MTQqUlP13kDUlkuwYh+/8g8yceu4kNbjowYrhihx+SV7CfjKCveJ7TSepr5Z7aLv1o+rfeelfOwn++WN/YoQsuZ6S3L4fWlWe5DAAUnFUI6cJvxxCohVzbrVXhyH2AqQdSjH2WnMYFeaGFIbcoxMznlk7oEwFn+hBj63dyT/swdYQfEdzuyCBmKXy8d6l1RKVX6Y65coTD8kIlbr+FKsqYrXVUIUBSwehqYuOdhYWZ9Bntl5DWU1oqzAPCnMn2cAIoQpQPKP7IGSxMOvCNAMhVXbE7BvnWuVuGF+AM5tXAa9IVUhcMImGwLQqm4iV5uBd+5OcFObh3A4VJk9iBCBWSkBHa/rV9CVoY0bFv2F9/2Hv82++Ybl274="; | |||||
| String ivStr = "TarMFjnzHVxy8pdS93wQbw=="; | |||||
| System.out.println(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr)); | |||||
| // System.out.println(WxMaCryptUtils.decryptAnotherWay(sessionKey, encryptedData, ivStr)); | |||||
| } | |||||
| @Test | |||||
| public void testDecryptAnotherWay() { | |||||
| String encryptedData = "CiyLU1Aw2KjvrjMdj8YKliAjtP4gsMZMQmRzooG2xrDcvSnxIMXFufNstNGTyaGS9uT5geRa0W4oTOb1WT7fJlAC+oNPdbB+3hVbJSRgv+4lGOETKUQz6OYStslQ142dNCuabNPGBzlooOmB231qMM85d2/fV6ChevvXvQP8Hkue1poOFtnEtpyxVLW1zAo6/1Xx1COxFvrc2d7UL/lmHInNlxuacJXwu0fjpXfz/YqYzBIBzD6WUfTIF9GRHpOn/Hz7saL8xz+W//FRAUid1OksQaQx4CMs8LOddcQhULW4ucetDf96JcR3g0gfRK4PC7E/r7Z6xNrXd2UIeorGj5Ef7b1pJAYB6Y5anaHqZ9J6nKEBvB4DnNLIVWSgARns/8wR2SiRS7MNACwTyrGvt9ts8p12PKFdlqYTopNHR1Vf7XjfhQlVsAJdNiKdYmYVoKlaRv85IfVunYzO0IKXsyl7JCUjCpoG20f0a04COwfneQAGGwd5oa+T8yO5hzuyDb/XcxxmK01EpqOyuxINew=="; | |||||
| String ivStr = "r7BXXKkLb8qrSNn05n0qiA=="; | |||||
| String sessionKey = "tiihtNczf5v6AKRyjwEUhQ=="; | |||||
| assertThat(WxMaCryptUtils.decrypt(sessionKey, encryptedData, ivStr)) | |||||
| .isEqualTo(WxMaCryptUtils.decryptAnotherWay(sessionKey, encryptedData, ivStr)); | |||||
| } | |||||
| } | |||||
| @@ -1,10 +1,9 @@ | |||||
| package cn.binarywang.wx.miniapp.util.json; | package cn.binarywang.wx.miniapp.util.json; | ||||
| import org.testng.annotations.*; | |||||
| import cn.binarywang.wx.miniapp.bean.WxMaTemplateData; | import cn.binarywang.wx.miniapp.bean.WxMaTemplateData; | ||||
| import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; | import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; | ||||
| import com.google.gson.JsonParser; | import com.google.gson.JsonParser; | ||||
| import org.testng.annotations.Test; | |||||
| import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||
| @@ -26,7 +25,7 @@ public class WxMaUniformMessageGsonAdapterTest { | |||||
| .appid("APPID") | .appid("APPID") | ||||
| .templateId("TEMPLATE_ID") | .templateId("TEMPLATE_ID") | ||||
| .url("http://weixin.qq.com/download") | .url("http://weixin.qq.com/download") | ||||
| .miniProgram(new WxMaUniformMessage.MiniProgram("xiaochengxuappid12345", "index?foo=bar", false)) | |||||
| .miniProgram(new WxMaUniformMessage.MiniProgram("xiaochengxuappid12345", "index?foo=bar", false, false)) | |||||
| .build(); | .build(); | ||||
| message.addData(new WxMaTemplateData("first", "恭喜你购买成功!", "#173177")) | message.addData(new WxMaTemplateData("first", "恭喜你购买成功!", "#173177")) | ||||
| .addData(new WxMaTemplateData("keyword1", "巧克力", "#173177")) | .addData(new WxMaTemplateData("keyword1", "巧克力", "#173177")) | ||||
| @@ -72,7 +71,7 @@ public class WxMaUniformMessageGsonAdapterTest { | |||||
| @Test | @Test | ||||
| public void testSerialize_ma() { | public void testSerialize_ma() { | ||||
| WxMaUniformMessage message = WxMaUniformMessage.builder() | |||||
| WxMaUniformMessage message = WxMaUniformMessage.builder() | |||||
| .isMpTemplateMsg(false) | .isMpTemplateMsg(false) | ||||
| .toUser("OPENID") | .toUser("OPENID") | ||||
| .page("page/page/index") | .page("page/page/index") | ||||
| @@ -7,10 +7,11 @@ | |||||
| <parent> | <parent> | ||||
| <groupId>com.github.binarywang</groupId> | <groupId>com.github.binarywang</groupId> | ||||
| <artifactId>wx-java</artifactId> | <artifactId>wx-java</artifactId> | ||||
| <version>3.3.0</version> | |||||
| <version>3.4.0</version> | |||||
| </parent> | </parent> | ||||
| <artifactId>weixin-java-mp</artifactId> | <artifactId>weixin-java-mp</artifactId> | ||||
| <name>WxJava - MP</name> | |||||
| <name>WxJava - MP Java SDK</name> | |||||
| <description>微信公众号Java SDK</description> | <description>微信公众号Java SDK</description> | ||||
| <dependencies> | <dependencies> | ||||
| @@ -1,10 +1,10 @@ | |||||
| package me.chanjar.weixin.mp.api; | package me.chanjar.weixin.mp.api; | ||||
| import java.io.File; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import me.chanjar.weixin.mp.enums.AiLangType; | import me.chanjar.weixin.mp.enums.AiLangType; | ||||
| import java.io.File; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 微信AI开放接口(语音识别,微信翻译). | * 微信AI开放接口(语音识别,微信翻译). | ||||
| @@ -15,24 +15,15 @@ import java.io.File; | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | * @author <a href="https://github.com/binarywang">Binary Wang</a> | ||||
| */ | */ | ||||
| public interface WxMpAiOpenService { | public interface WxMpAiOpenService { | ||||
| String TRANSLATE_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?lfrom=%s<o=%s"; | |||||
| String VOICE_UPLOAD_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?format=%s&voice_id=%s&lang=%s"; | String VOICE_UPLOAD_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?format=%s&voice_id=%s&lang=%s"; | ||||
| String VOICE_QUERY_RESULT_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext"; | String VOICE_QUERY_RESULT_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext"; | ||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * 提交语音. | * 提交语音. | ||||
| * 接口调用请求说明 | |||||
| * | |||||
| * http请求方式: POST | * http请求方式: POST | ||||
| * http://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?access_token=ACCESS_TOKEN&format=&voice_id=xxxxxx&lang=zh_CN | * http://api.weixin.qq.com/cgi-bin/media/voice/addvoicetorecofortext?access_token=ACCESS_TOKEN&format=&voice_id=xxxxxx&lang=zh_CN | ||||
| * 参数说明 | |||||
| * | |||||
| * 参数 是否必须 说明 | |||||
| * access_token 是 接口调用凭证 | |||||
| * format 是 文件格式 (只支持mp3,16k,单声道,最大1M) | |||||
| * voice_id 是 语音唯一标识 | |||||
| * lang 否 语言,zh_CN 或 en_US,默认中文 | |||||
| * 语音内容放body里或者上传文件的形式 | |||||
| * </pre> | * </pre> | ||||
| * | * | ||||
| * @param lang 语言,zh_CN 或 en_US,默认中文 | * @param lang 语言,zh_CN 或 en_US,默认中文 | ||||
| @@ -46,16 +37,9 @@ public interface WxMpAiOpenService { | |||||
| * 获取语音识别结果. | * 获取语音识别结果. | ||||
| * 接口调用请求说明 | * 接口调用请求说明 | ||||
| * | * | ||||
| * http请求方式: POST | |||||
| * http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext?access_token=ACCESS_TOKEN&voice_id=xxxxxx&lang=zh_CN | * http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext?access_token=ACCESS_TOKEN&voice_id=xxxxxx&lang=zh_CN | ||||
| * 请注意,添加完文件之后10s内调用这个接口 | * 请注意,添加完文件之后10s内调用这个接口 | ||||
| * | * | ||||
| * 参数说明 | |||||
| * | |||||
| * 参数 是否必须 说明 | |||||
| * access_token 是 接口调用凭证 | |||||
| * voice_id 是 语音唯一标识 | |||||
| * lang 否 语言,zh_CN 或 en_US,默认中文 | |||||
| * </pre> | * </pre> | ||||
| * | * | ||||
| * @param lang 语言,zh_CN 或 en_US,默认中文 | * @param lang 语言,zh_CN 或 en_US,默认中文 | ||||
| @@ -80,18 +64,12 @@ public interface WxMpAiOpenService { | |||||
| * | * | ||||
| * http请求方式: POST | * http请求方式: POST | ||||
| * http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?access_token=ACCESS_TOKEN&lfrom=xxx<o=xxx | * http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?access_token=ACCESS_TOKEN&lfrom=xxx<o=xxx | ||||
| * 参数说明 | |||||
| * | * | ||||
| * 参数 是否必须 说明 | |||||
| * access_token 是 接口调用凭证 | |||||
| * lfrom 是 源语言,zh_CN 或 en_US | |||||
| * lto 是 目标语言,zh_CN 或 en_US | |||||
| * 源内容放body里或者上传文件的形式(utf8格式,最大600Byte) | |||||
| * </pre> | * </pre> | ||||
| * | * | ||||
| * @param langFrom 源语言,zh_CN 或 en_US | * @param langFrom 源语言,zh_CN 或 en_US | ||||
| * @param langTo 目标语言,zh_CN 或 en_US | |||||
| * @param content 要翻译的文本内容 | |||||
| * @param langTo 目标语言,zh_CN 或 en_US | |||||
| * @param content 要翻译的文本内容 | |||||
| */ | */ | ||||
| String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException; | String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException; | ||||
| } | } | ||||
| @@ -2,10 +2,8 @@ package me.chanjar.weixin.mp.api; | |||||
| import me.chanjar.weixin.common.bean.WxCardApiSignature; | import me.chanjar.weixin.common.bean.WxCardApiSignature; | ||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateRequest; | |||||
| import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateResult; | |||||
| import me.chanjar.weixin.mp.bean.card.WxMpCardQrcodeCreateResult; | |||||
| import me.chanjar.weixin.mp.bean.result.WxMpCardResult; | |||||
| import me.chanjar.weixin.mp.bean.card.*; | |||||
| import me.chanjar.weixin.mp.bean.card.WxMpCardResult; | |||||
| /** | /** | ||||
| * 卡券相关接口 | * 卡券相关接口 | ||||
| @@ -14,6 +12,7 @@ import me.chanjar.weixin.mp.bean.result.WxMpCardResult; | |||||
| * @author yuanqixun 2018-08-29 | * @author yuanqixun 2018-08-29 | ||||
| */ | */ | ||||
| public interface WxMpCardService { | public interface WxMpCardService { | ||||
| String CARD_CREATE = "https://api.weixin.qq.com/card/create"; | |||||
| String CARD_GET = "https://api.weixin.qq.com/card/get"; | String CARD_GET = "https://api.weixin.qq.com/card/get"; | ||||
| String CARD_GET_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=wx_card"; | String CARD_GET_TICKET = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=wx_card"; | ||||
| String CARD_CODE_DECRYPT = "https://api.weixin.qq.com/card/code/decrypt"; | String CARD_CODE_DECRYPT = "https://api.weixin.qq.com/card/code/decrypt"; | ||||
| @@ -28,6 +27,11 @@ public interface WxMpCardService { | |||||
| */ | */ | ||||
| String CARD_CODE_UNAVAILABLE = "https://api.weixin.qq.com/card/code/unavailable"; | String CARD_CODE_UNAVAILABLE = "https://api.weixin.qq.com/card/code/unavailable"; | ||||
| /** | |||||
| * 卡券删除 | |||||
| */ | |||||
| String CARD_DELETE = "https://api.weixin.qq.com/card/delete"; | |||||
| /** | /** | ||||
| * 得到WxMpService | * 得到WxMpService | ||||
| */ | */ | ||||
| @@ -81,8 +85,8 @@ public interface WxMpCardService { | |||||
| String decryptCardCode(String encryptCode) throws WxErrorException; | String decryptCardCode(String encryptCode) throws WxErrorException; | ||||
| /** | /** | ||||
| * 卡券Code查询 | |||||
| * | |||||
| * 卡券Code查询. | |||||
| * 文档地址: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1451025272&anchor=1 | |||||
| * @param cardId 卡券ID代表一类卡券 | * @param cardId 卡券ID代表一类卡券 | ||||
| * @param code 单张卡券的唯一标准 | * @param code 单张卡券的唯一标准 | ||||
| * @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同 | * @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同 | ||||
| @@ -142,6 +146,14 @@ public interface WxMpCardService { | |||||
| */ | */ | ||||
| String addTestWhiteList(String openid) throws WxErrorException; | String addTestWhiteList(String openid) throws WxErrorException; | ||||
| /** | |||||
| * | |||||
| * @param cardCreateMessage | |||||
| * @return | |||||
| * @throws WxErrorException | |||||
| */ | |||||
| WxMpCardCreateResult createCard(WxMpCardCreateMessage cardCreateMessage) throws WxErrorException; | |||||
| /** | /** | ||||
| * 创建卡券二维码 | * 创建卡券二维码 | ||||
| * | * | ||||
| @@ -182,4 +194,12 @@ public interface WxMpCardService { | |||||
| */ | */ | ||||
| String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException; | String unavailableCardCode(String cardId, String code, String reason) throws WxErrorException; | ||||
| /** | |||||
| * 删除卡券接口 | |||||
| * @param cardId | |||||
| * @return | |||||
| * @throws WxErrorException | |||||
| */ | |||||
| WxMpCardDeleteResult deleteCard(String cardId) throws WxErrorException; | |||||
| } | } | ||||
| @@ -0,0 +1,71 @@ | |||||
| package me.chanjar.weixin.mp.api; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.mp.bean.marketing.WxMpAdLeadFilter; | |||||
| import me.chanjar.weixin.mp.bean.marketing.WxMpAdLeadResult; | |||||
| import me.chanjar.weixin.mp.bean.marketing.WxMpUserAction; | |||||
| import me.chanjar.weixin.mp.bean.marketing.WxMpUserActionSet; | |||||
| import java.io.IOException; | |||||
| import java.util.Date; | |||||
| import java.util.List; | |||||
| /** | |||||
| * <pre> | |||||
| * 微信营销接口 | |||||
| * </pre> | |||||
| * | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public interface WxMpMarketingService { | |||||
| String API_URL_PREFIX = "https://api.weixin.qq.com/marketing/"; | |||||
| /** | |||||
| * <pre> | |||||
| * 创建数据源 | |||||
| * 接口调用请求说明 | |||||
| * https://wximg.qq.com/wxp/pdftool/get.html?id=rkalQXDBM&pa=39 | |||||
| * </pre> | |||||
| * | |||||
| * @param type 用户行为源类型 | |||||
| * @param name 用户行为源名称 必填 | |||||
| * @param description 用户行为源描述,字段长度最小 1 字节,长度最大 128 字节 | |||||
| */ | |||||
| long addUserActionSets(String type, String name, String description) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 获取数据源信息 | |||||
| * </pre> | |||||
| * | |||||
| * @param userActionSetId 数据源唯一ID | |||||
| */ | |||||
| List<WxMpUserActionSet> getUserActionSets(Long userActionSetId) throws WxErrorException; | |||||
| /** | |||||
| * 回传数据 | |||||
| * 接口调用请求说明 | |||||
| * https://wximg.qq.com/wxp/pdftool/get.html?id=rkalQXDBM&pa=39 | |||||
| * | |||||
| * @param actions 用户行为源类型 | |||||
| */ | |||||
| void addUserAction(List<WxMpUserAction> actions) throws WxErrorException; | |||||
| /** | |||||
| * <pre> | |||||
| * 获取朋友圈销售线索数据接口 | |||||
| * 接口调用请求说明 | |||||
| * | |||||
| * http请求方式: POST | |||||
| * http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?access_token=ACCESS_TOKEN&lfrom=xxx<o=xxx | |||||
| * | |||||
| * </pre> | |||||
| * | |||||
| * @param beginDate 开始日期 | |||||
| * @param endDate 结束日期 | |||||
| * @param filtering 过滤条件 | |||||
| * @param page 页码,获取指定页数据 | |||||
| * @param page_size 一页获取的数据条数(1-100) | |||||
| */ | |||||
| WxMpAdLeadResult getAdLeads(Date beginDate, Date endDate, List<WxMpAdLeadFilter> filtering, Integer page, Integer page_size) throws WxErrorException, IOException; | |||||
| } | |||||
| @@ -6,12 +6,7 @@ import me.chanjar.weixin.mp.bean.card.MemberCardActivateUserFormRequest; | |||||
| import me.chanjar.weixin.mp.bean.card.MemberCardActivateUserFormResult; | import me.chanjar.weixin.mp.bean.card.MemberCardActivateUserFormResult; | ||||
| import me.chanjar.weixin.mp.bean.card.MemberCardUpdateRequest; | import me.chanjar.weixin.mp.bean.card.MemberCardUpdateRequest; | ||||
| import me.chanjar.weixin.mp.bean.card.WxMpCardCreateResult; | import me.chanjar.weixin.mp.bean.card.WxMpCardCreateResult; | ||||
| import me.chanjar.weixin.mp.bean.membercard.ActivatePluginParam; | |||||
| import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardActivatedMessage; | |||||
| import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardCreateMessage; | |||||
| import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateMessage; | |||||
| import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateResult; | |||||
| import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUserInfoResult; | |||||
| import me.chanjar.weixin.mp.bean.membercard.*; | |||||
| /** | /** | ||||
| * 会员卡相关接口. | * 会员卡相关接口. | ||||
| @@ -22,14 +17,14 @@ import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUserInfoResult; | |||||
| * @date 2018-08-30 | * @date 2018-08-30 | ||||
| */ | */ | ||||
| public interface WxMpMemberCardService { | public interface WxMpMemberCardService { | ||||
| String MEMBER_CARD_CREAET = "https://api.weixin.qq.com/card/create"; | |||||
| String MEMBER_CARD_CREATE = "https://api.weixin.qq.com/card/create"; | |||||
| String MEMBER_CARD_ACTIVATE = "https://api.weixin.qq.com/card/membercard/activate"; | String MEMBER_CARD_ACTIVATE = "https://api.weixin.qq.com/card/membercard/activate"; | ||||
| String MEMBER_CARD_USER_INFO_GET = "https://api.weixin.qq.com/card/membercard/userinfo/get"; | String MEMBER_CARD_USER_INFO_GET = "https://api.weixin.qq.com/card/membercard/userinfo/get"; | ||||
| String MEMBER_CARD_UPDATE_USER = "https://api.weixin.qq.com/card/membercard/updateuser"; | String MEMBER_CARD_UPDATE_USER = "https://api.weixin.qq.com/card/membercard/updateuser"; | ||||
| /** | /** | ||||
| * 会员卡激活之微信开卡接口(wx_activate=true情况调用). | * 会员卡激活之微信开卡接口(wx_activate=true情况调用). | ||||
| */ | */ | ||||
| String MEMBER_CARD_ACTIVATEUSERFORM = "https://api.weixin.qq.com/card/membercard/activateuserform/set"; | |||||
| String MEMBER_CARD_ACTIVATE_USER_FORM = "https://api.weixin.qq.com/card/membercard/activateuserform/set"; | |||||
| /** | /** | ||||
| * 获取会员卡开卡插件参数. | * 获取会员卡开卡插件参数. | ||||
| @@ -41,19 +36,34 @@ public interface WxMpMemberCardService { | |||||
| */ | */ | ||||
| String MEMBER_CARD_UPDATE = "https://api.weixin.qq.com/card/update"; | String MEMBER_CARD_UPDATE = "https://api.weixin.qq.com/card/update"; | ||||
| /** | |||||
| * 跳转型会员卡开卡字段. | |||||
| * 获取用户提交资料(wx_activate=true情况调用),开发者根据activate_ticket获取到用户填写的信息 | |||||
| */ | |||||
| String MEMBER_CARD_ACTIVATE_TEMP_INFO = "https://api.weixin.qq.com/card/membercard/activatetempinfo/get"; | |||||
| /** | /** | ||||
| * 得到WxMpService. | * 得到WxMpService. | ||||
| * | |||||
| * @return WxMpService | |||||
| */ | */ | ||||
| WxMpService getWxMpService(); | WxMpService getWxMpService(); | ||||
| /** | /** | ||||
| * 会员卡创建接口. | * 会员卡创建接口. | ||||
| * | |||||
| * @param createJson 会员卡json字符串 | |||||
| * @return 返回json字符串 | |||||
| * @throws WxErrorException 接口调用失败抛出的异常 | |||||
| */ | */ | ||||
| WxMpCardCreateResult createMemberCard(String createJson) throws WxErrorException; | WxMpCardCreateResult createMemberCard(String createJson) throws WxErrorException; | ||||
| /** | /** | ||||
| * 会员卡创建接口. | |||||
| * 会员卡创建接口 | |||||
| * | |||||
| * @param createMessageMessage 会员卡创建对象 | |||||
| * @return 会员卡信息的结果对象 | |||||
| * @throws WxErrorException 接口调用失败抛出的异常 | |||||
| */ | */ | ||||
| WxMpCardCreateResult createMemberCard(WxMpMemberCardCreateMessage createMessageMessage) throws WxErrorException; | WxMpCardCreateResult createMemberCard(WxMpMemberCardCreateMessage createMessageMessage) throws WxErrorException; | ||||
| @@ -61,7 +71,7 @@ public interface WxMpMemberCardService { | |||||
| * 会员卡激活接口. | * 会员卡激活接口. | ||||
| * | * | ||||
| * @param activatedMessage 激活所需参数 | * @param activatedMessage 激活所需参数 | ||||
| * @return 返回json字符串 | |||||
| * @return 会员卡激活后的json字符串 | |||||
| * @throws WxErrorException 接口调用失败抛出的异常 | * @throws WxErrorException 接口调用失败抛出的异常 | ||||
| */ | */ | ||||
| String activateMemberCard(WxMpMemberCardActivatedMessage activatedMessage) throws WxErrorException; | String activateMemberCard(WxMpMemberCardActivatedMessage activatedMessage) throws WxErrorException; | ||||
| @@ -91,16 +101,40 @@ public interface WxMpMemberCardService { | |||||
| /** | /** | ||||
| * 设置会员卡激活的字段(会员卡设置:wx_activate=true 时需要). | * 设置会员卡激活的字段(会员卡设置:wx_activate=true 时需要). | ||||
| * | |||||
| * @param userFormRequest 会员卡激活字段对象 | |||||
| * @return 会员卡激活后结果对象 | |||||
| * @throws WxErrorException 接口调用失败抛出的异常 | |||||
| */ | */ | ||||
| MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException; | MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException; | ||||
| /** | /** | ||||
| * 获取会员卡开卡插件参数(跳转型开卡组件需要参数). | * 获取会员卡开卡插件参数(跳转型开卡组件需要参数). | ||||
| * | |||||
| * @param cardId 会员卡的CardId,微信分配 | |||||
| * @param outStr 会员卡设置商户的渠道 | |||||
| * @return 会员卡开卡插件参数结果对象 | |||||
| * @throws WxErrorException 接口调用失败抛出的异常 | |||||
| */ | */ | ||||
| ActivatePluginParam getActivatePluginParam(String cardId, String outStr) throws WxErrorException; | ActivatePluginParam getActivatePluginParam(String cardId, String outStr) throws WxErrorException; | ||||
| /** | /** | ||||
| * 更新会员卡信息. | * 更新会员卡信息. | ||||
| * | |||||
| * @param memberCardUpdateRequest 会员卡更新对象 | |||||
| * @return 会员卡更新后结果对象 | |||||
| * @throws WxErrorException 接口调用失败抛出的异常 | |||||
| */ | */ | ||||
| CardUpdateResult updateCardInfo(MemberCardUpdateRequest memberCardUpdateRequest) throws WxErrorException; | CardUpdateResult updateCardInfo(MemberCardUpdateRequest memberCardUpdateRequest) throws WxErrorException; | ||||
| /** | |||||
| * 解析跳转型开卡字段用户提交的资料. | |||||
| * 开发者在URL上截取ticket后须先进行urldecode | |||||
| * | |||||
| * @param activateTicket 用户提交的资料 | |||||
| * @return 开卡字段的会员信息对象 | |||||
| * @throws WxErrorException 接口调用失败抛出的异常 | |||||
| */ | |||||
| WxMpMemberCardActivateTempInfoResult getActivateTempInfo(String activateTicket) throws WxErrorException; | |||||
| } | } | ||||
| @@ -5,6 +5,7 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor; | ||||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | import me.chanjar.weixin.common.util.http.RequestExecutor; | ||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | import me.chanjar.weixin.common.util.http.RequestHttp; | ||||
| import me.chanjar.weixin.mp.api.impl.BaseWxMpServiceImpl; | |||||
| import me.chanjar.weixin.mp.bean.WxMpSemanticQuery; | import me.chanjar.weixin.mp.bean.WxMpSemanticQuery; | ||||
| import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo; | import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo; | ||||
| import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | ||||
| @@ -12,6 +13,8 @@ import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult; | |||||
| import me.chanjar.weixin.mp.bean.result.WxMpUser; | import me.chanjar.weixin.mp.bean.result.WxMpUser; | ||||
| import me.chanjar.weixin.mp.enums.TicketType; | import me.chanjar.weixin.mp.enums.TicketType; | ||||
| import java.util.Map; | |||||
| /** | /** | ||||
| * 微信公众号API的Service. | * 微信公众号API的Service. | ||||
| * | * | ||||
| @@ -302,10 +305,50 @@ public interface WxMpService { | |||||
| WxMpConfigStorage getWxMpConfigStorage(); | WxMpConfigStorage getWxMpConfigStorage(); | ||||
| /** | /** | ||||
| * 注入 {@link WxMpConfigStorage} 的实现. | |||||
| * 设置 {@link WxMpConfigStorage} 的实现. 兼容老版本 | |||||
| */ | */ | ||||
| void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider); | void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider); | ||||
| /** | |||||
| * {@link Map<String, WxMpConfigStorage>} 加入新的 {@link WxMpConfigStorage},适用于动态添加新的微信公众号配置 | |||||
| * @param configStorage 新的微信配置 | |||||
| */ | |||||
| void addConfigStorage(String mpId, WxMpConfigStorage configStorage); | |||||
| /** | |||||
| * 从{@link Map<String, WxMpConfigStorage>} 移除 {@link String mpId} 所对应的 {@link WxMpConfigStorage},适用于动态移除微信公众号配置 | |||||
| * @param mpId 对应公众号的标识 | |||||
| */ | |||||
| void removeConfigStorage(String mpId); | |||||
| /** | |||||
| * 注入多个 {@link WxMpConfigStorage} 的实现. 并为每个 {@link WxMpConfigStorage} 赋予不同的 {@link String mpId} 值 | |||||
| * 随机采用一个{@link String mpId}进行Http初始化操作 | |||||
| * @param configStorages WxMpConfigStorage map | |||||
| */ | |||||
| void setMultiConfigStorages(Map<String, WxMpConfigStorage> configStorages); | |||||
| /** | |||||
| * 注入多个 {@link WxMpConfigStorage} 的实现. 并为每个 {@link WxMpConfigStorage} 赋予不同的 {@link String label} 值 | |||||
| * @param configStorages WxMpConfigStorage map | |||||
| * @param defaultMpId 设置一个{@link WxMpConfigStorage} 所对应的{@link String mpId}进行Http初始化 | |||||
| */ | |||||
| void setMultiConfigStorages(Map<String, WxMpConfigStorage> configStorages, String defaultMpId); | |||||
| /** | |||||
| * 进行相应的公众号切换 | |||||
| * @param mpId 公众号标识 | |||||
| * @return 切换是否成功 | |||||
| */ | |||||
| boolean switchover(String mpId); | |||||
| /** | |||||
| * 进行相应的公众号切换 | |||||
| * @param mpId 公众号标识 | |||||
| * @return 切换成功,则返回当前对象,方便链式调用,否则抛出异常 | |||||
| */ | |||||
| WxMpService switchoverTo(String mpId); | |||||
| /** | /** | ||||
| * 返回客服接口方法实现类,以方便调用其各个接口. | * 返回客服接口方法实现类,以方便调用其各个接口. | ||||
| * | * | ||||
| @@ -411,6 +454,13 @@ public interface WxMpService { | |||||
| */ | */ | ||||
| WxMpMemberCardService getMemberCardService(); | WxMpMemberCardService getMemberCardService(); | ||||
| /** | |||||
| * 返回营销相关接口方法的实现类对象,以方便调用其各个接口. | |||||
| * | |||||
| * @return WxMpMarketingService | |||||
| */ | |||||
| WxMpMarketingService getMarketingService(); | |||||
| /** | /** | ||||
| * 初始化http请求对象. | * 初始化http请求对象. | ||||
| */ | */ | ||||
| @@ -473,4 +523,6 @@ public interface WxMpService { | |||||
| void setMassMessageService(WxMpMassMessageService massMessageService); | void setMassMessageService(WxMpMassMessageService massMessageService); | ||||
| void setAiOpenService(WxMpAiOpenService aiOpenService); | void setAiOpenService(WxMpAiOpenService aiOpenService); | ||||
| void setMarketingService(WxMpMarketingService marketingService); | |||||
| } | } | ||||
| @@ -1,12 +1,7 @@ | |||||
| package me.chanjar.weixin.mp.api.impl; | package me.chanjar.weixin.mp.api.impl; | ||||
| import java.io.IOException; | |||||
| import java.util.concurrent.locks.Lock; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import com.google.common.collect.ImmutableMap; | |||||
| import com.google.common.collect.Maps; | |||||
| import com.google.gson.JsonArray; | import com.google.gson.JsonArray; | ||||
| import com.google.gson.JsonElement; | import com.google.gson.JsonElement; | ||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||
| @@ -19,37 +14,22 @@ import me.chanjar.weixin.common.session.WxSessionManager; | |||||
| import me.chanjar.weixin.common.util.DataUtils; | import me.chanjar.weixin.common.util.DataUtils; | ||||
| import me.chanjar.weixin.common.util.RandomUtils; | import me.chanjar.weixin.common.util.RandomUtils; | ||||
| import me.chanjar.weixin.common.util.crypto.SHA1; | import me.chanjar.weixin.common.util.crypto.SHA1; | ||||
| import me.chanjar.weixin.common.util.http.RequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.RequestHttp; | |||||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||||
| import me.chanjar.weixin.common.util.http.URIUtil; | |||||
| import me.chanjar.weixin.mp.api.WxMpAiOpenService; | |||||
| import me.chanjar.weixin.mp.api.WxMpCardService; | |||||
| import me.chanjar.weixin.mp.api.WxMpConfigStorage; | |||||
| import me.chanjar.weixin.mp.api.WxMpDataCubeService; | |||||
| import me.chanjar.weixin.mp.api.WxMpDeviceService; | |||||
| import me.chanjar.weixin.mp.api.WxMpKefuService; | |||||
| import me.chanjar.weixin.mp.api.WxMpMassMessageService; | |||||
| import me.chanjar.weixin.mp.api.WxMpMaterialService; | |||||
| import me.chanjar.weixin.mp.api.WxMpMemberCardService; | |||||
| import me.chanjar.weixin.mp.api.WxMpMenuService; | |||||
| import me.chanjar.weixin.mp.api.WxMpQrcodeService; | |||||
| import me.chanjar.weixin.mp.api.WxMpService; | |||||
| import me.chanjar.weixin.mp.api.WxMpShakeService; | |||||
| import me.chanjar.weixin.mp.api.WxMpStoreService; | |||||
| import me.chanjar.weixin.mp.api.WxMpSubscribeMsgService; | |||||
| import me.chanjar.weixin.mp.api.WxMpTemplateMsgService; | |||||
| import me.chanjar.weixin.mp.api.WxMpUserBlacklistService; | |||||
| import me.chanjar.weixin.mp.api.WxMpUserService; | |||||
| import me.chanjar.weixin.mp.api.WxMpUserTagService; | |||||
| import me.chanjar.weixin.mp.api.WxMpWifiService; | |||||
| import me.chanjar.weixin.common.util.http.*; | |||||
| import me.chanjar.weixin.mp.api.*; | |||||
| import me.chanjar.weixin.mp.bean.WxMpSemanticQuery; | import me.chanjar.weixin.mp.bean.WxMpSemanticQuery; | ||||
| import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo; | import me.chanjar.weixin.mp.bean.result.WxMpCurrentAutoReplyInfo; | ||||
| import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | ||||
| import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult; | import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult; | ||||
| import me.chanjar.weixin.mp.bean.result.WxMpUser; | import me.chanjar.weixin.mp.bean.result.WxMpUser; | ||||
| import me.chanjar.weixin.mp.enums.TicketType; | import me.chanjar.weixin.mp.enums.TicketType; | ||||
| import me.chanjar.weixin.mp.util.WxMpConfigStorageHolder; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.io.IOException; | |||||
| import java.util.Map; | |||||
| import java.util.concurrent.locks.Lock; | |||||
| /** | /** | ||||
| * 基础实现类. | * 基础实现类. | ||||
| @@ -62,7 +42,6 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH | |||||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | protected final Logger log = LoggerFactory.getLogger(this.getClass()); | ||||
| protected WxSessionManager sessionManager = new StandardSessionManager(); | protected WxSessionManager sessionManager = new StandardSessionManager(); | ||||
| protected WxMpConfigStorage wxMpConfigStorage; | |||||
| private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); | private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); | ||||
| private WxMpMaterialService materialService = new WxMpMaterialServiceImpl(this); | private WxMpMaterialService materialService = new WxMpMaterialServiceImpl(this); | ||||
| private WxMpMenuService menuService = new WxMpMenuServiceImpl(this); | private WxMpMenuService menuService = new WxMpMenuServiceImpl(this); | ||||
| @@ -81,11 +60,13 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH | |||||
| private WxMpMassMessageService massMessageService = new WxMpMassMessageServiceImpl(this); | private WxMpMassMessageService massMessageService = new WxMpMassMessageServiceImpl(this); | ||||
| private WxMpAiOpenService aiOpenService = new WxMpAiOpenServiceImpl(this); | private WxMpAiOpenService aiOpenService = new WxMpAiOpenServiceImpl(this); | ||||
| private WxMpWifiService wifiService = new WxMpWifiServiceImpl(this); | private WxMpWifiService wifiService = new WxMpWifiServiceImpl(this); | ||||
| private WxMpMarketingService marketingService = new WxMpMarketingServiceImpl(this); | |||||
| private Map<String, WxMpConfigStorage> configStorageMap; | |||||
| private int retrySleepMillis = 1000; | private int retrySleepMillis = 1000; | ||||
| private int maxRetryTimes = 5; | private int maxRetryTimes = 5; | ||||
| @Override | @Override | ||||
| public boolean checkSignature(String timestamp, String nonce, String signature) { | public boolean checkSignature(String timestamp, String nonce, String signature) { | ||||
| try { | try { | ||||
| @@ -352,15 +333,70 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH | |||||
| @Override | @Override | ||||
| public WxMpConfigStorage getWxMpConfigStorage() { | public WxMpConfigStorage getWxMpConfigStorage() { | ||||
| return this.wxMpConfigStorage; | |||||
| if (this.configStorageMap.size() == 1) { | |||||
| // 只有一个公众号,直接返回其配置即可 | |||||
| return this.configStorageMap.values().iterator().next(); | |||||
| } | |||||
| return this.configStorageMap.get(WxMpConfigStorageHolder.get()); | |||||
| } | } | ||||
| @Override | @Override | ||||
| public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) { | public void setWxMpConfigStorage(WxMpConfigStorage wxConfigProvider) { | ||||
| this.wxMpConfigStorage = wxConfigProvider; | |||||
| final String defaultMpId = WxMpConfigStorageHolder.get(); | |||||
| this.setMultiConfigStorages(ImmutableMap.of(defaultMpId, wxConfigProvider), defaultMpId); | |||||
| } | |||||
| @Override | |||||
| public void setMultiConfigStorages(Map<String, WxMpConfigStorage> configStorages) { | |||||
| this.setMultiConfigStorages(configStorages, configStorages.keySet().iterator().next()); | |||||
| } | |||||
| @Override | |||||
| public void setMultiConfigStorages(Map<String, WxMpConfigStorage> configStorages, String defaultMpId) { | |||||
| this.configStorageMap = Maps.newHashMap(configStorages); | |||||
| WxMpConfigStorageHolder.set(defaultMpId); | |||||
| this.initHttp(); | this.initHttp(); | ||||
| } | } | ||||
| @Override | |||||
| public void addConfigStorage(String mpId, WxMpConfigStorage configStorages) { | |||||
| synchronized (this) { | |||||
| if (this.configStorageMap.containsKey(mpId)) { | |||||
| throw new RuntimeException("该公众号标识已存在,请更换其他标识!"); | |||||
| } | |||||
| this.configStorageMap.put(mpId, configStorages); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public void removeConfigStorage(String mpId) { | |||||
| synchronized (this) { | |||||
| this.configStorageMap.remove(mpId); | |||||
| } | |||||
| } | |||||
| @Override | |||||
| public WxMpService switchoverTo(String mpId) { | |||||
| if (this.configStorageMap.containsKey(mpId)) { | |||||
| WxMpConfigStorageHolder.set(mpId); | |||||
| return this; | |||||
| } | |||||
| throw new RuntimeException(String.format("无法找到对应【%s】的公众号配置信息,请核实!", mpId)); | |||||
| } | |||||
| @Override | |||||
| public boolean switchover(String mpId) { | |||||
| if (this.configStorageMap.containsKey(mpId)) { | |||||
| WxMpConfigStorageHolder.set(mpId); | |||||
| return true; | |||||
| } | |||||
| log.error("无法找到对应【{}】的公众号配置信息,请核实!", mpId); | |||||
| return false; | |||||
| } | |||||
| @Override | @Override | ||||
| public void setRetrySleepMillis(int retrySleepMillis) { | public void setRetrySleepMillis(int retrySleepMillis) { | ||||
| this.retrySleepMillis = retrySleepMillis; | this.retrySleepMillis = retrySleepMillis; | ||||
| @@ -545,4 +581,14 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH | |||||
| public WxMpWifiService getWifiService() { | public WxMpWifiService getWifiService() { | ||||
| return this.wifiService; | return this.wifiService; | ||||
| } | } | ||||
| @Override | |||||
| public WxMpMarketingService getMarketingService() { | |||||
| return this.marketingService; | |||||
| } | |||||
| @Override | |||||
| public void setMarketingService(WxMpMarketingService marketingService) { | |||||
| this.marketingService = marketingService; | |||||
| } | |||||
| } | } | ||||
| @@ -1,17 +1,16 @@ | |||||
| package me.chanjar.weixin.mp.api.impl; | package me.chanjar.weixin.mp.api.impl; | ||||
| import com.google.gson.JsonObject; | |||||
| import java.io.File; | |||||
| import com.google.gson.JsonParser; | import com.google.gson.JsonParser; | ||||
| import me.chanjar.weixin.common.WxType; | import me.chanjar.weixin.common.WxType; | ||||
| import me.chanjar.weixin.common.error.WxError; | import me.chanjar.weixin.common.error.WxError; | ||||
| import me.chanjar.weixin.common.error.WxErrorException; | import me.chanjar.weixin.common.error.WxErrorException; | ||||
| import me.chanjar.weixin.mp.enums.AiLangType; | |||||
| import me.chanjar.weixin.mp.api.WxMpAiOpenService; | import me.chanjar.weixin.mp.api.WxMpAiOpenService; | ||||
| import me.chanjar.weixin.mp.api.WxMpService; | import me.chanjar.weixin.mp.api.WxMpService; | ||||
| import me.chanjar.weixin.mp.enums.AiLangType; | |||||
| import me.chanjar.weixin.mp.util.requestexecuter.voice.VoiceUploadRequestExecutor; | import me.chanjar.weixin.mp.util.requestexecuter.voice.VoiceUploadRequestExecutor; | ||||
| import java.io.File; | |||||
| /** | /** | ||||
| * <pre> | * <pre> | ||||
| * Created by BinaryWang on 2018/6/9. | * Created by BinaryWang on 2018/6/9. | ||||
| @@ -20,9 +19,7 @@ import java.io.File; | |||||
| * @author <a href="https://github.com/binarywang">Binary Wang</a> | * @author <a href="https://github.com/binarywang">Binary Wang</a> | ||||
| */ | */ | ||||
| public class WxMpAiOpenServiceImpl implements WxMpAiOpenService { | public class WxMpAiOpenServiceImpl implements WxMpAiOpenService { | ||||
| private static final JsonParser JSON_PARSER = new JsonParser(); | private static final JsonParser JSON_PARSER = new JsonParser(); | ||||
| public static final String TRANSLATE_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/translatecontent?lfrom=%s<o=%s"; | |||||
| private WxMpService wxMpService; | private WxMpService wxMpService; | ||||
| public WxMpAiOpenServiceImpl(WxMpService wxMpService) { | public WxMpAiOpenServiceImpl(WxMpService wxMpService) { | ||||
| @@ -48,14 +45,14 @@ public class WxMpAiOpenServiceImpl implements WxMpAiOpenService { | |||||
| @Override | @Override | ||||
| public String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException { | public String translate(AiLangType langFrom, AiLangType langTo, String content) throws WxErrorException { | ||||
| final String responseContent = this.wxMpService.post(String.format(TRANSLATE_URL, langFrom.getCode(), langTo.getCode()), | |||||
| content); | |||||
| final JsonObject jsonObject = new JsonParser().parse(responseContent).getAsJsonObject(); | |||||
| if (jsonObject.get("errcode") == null || jsonObject.get("errcode").getAsInt() == 0) { | |||||
| return jsonObject.get("to_content").getAsString(); | |||||
| String response = this.wxMpService.post(String.format(TRANSLATE_URL, langFrom.getCode(), langTo.getCode()), content); | |||||
| WxError error = WxError.fromJson(response, WxType.MP); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | } | ||||
| throw new WxErrorException(WxError.fromJson(responseContent, WxType.MP)); | |||||
| return JSON_PARSER.parse(response).getAsJsonObject().get("to_content").getAsString(); | |||||
| } | } | ||||
| @Override | @Override | ||||
| @@ -64,13 +61,13 @@ public class WxMpAiOpenServiceImpl implements WxMpAiOpenService { | |||||
| lang = AiLangType.zh_CN; | lang = AiLangType.zh_CN; | ||||
| } | } | ||||
| final String responseContent = this.wxMpService.get(VOICE_QUERY_RESULT_URL, | |||||
| final String response = this.wxMpService.get(VOICE_QUERY_RESULT_URL, | |||||
| String.format("voice_id=%s&lang=%s", voiceId, lang.getCode())); | String.format("voice_id=%s&lang=%s", voiceId, lang.getCode())); | ||||
| final JsonObject jsonObject = JSON_PARSER.parse(responseContent).getAsJsonObject(); | |||||
| if (jsonObject.get("errcode") == null || jsonObject.get("errcode").getAsInt() == 0) { | |||||
| return jsonObject.get("result").getAsString(); | |||||
| WxError error = WxError.fromJson(response, WxType.MP); | |||||
| if (error.getErrorCode() != 0) { | |||||
| throw new WxErrorException(error); | |||||
| } | } | ||||
| throw new WxErrorException(WxError.fromJson(responseContent, WxType.MP)); | |||||
| return JSON_PARSER.parse(response).getAsJsonObject().get("result").getAsString(); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1,18 +1,6 @@ | |||||
| package me.chanjar.weixin.mp.api.impl; | package me.chanjar.weixin.mp.api.impl; | ||||
| import java.util.Arrays; | |||||
| import java.util.concurrent.locks.Lock; | |||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import com.google.gson.Gson; | |||||
| import com.google.gson.JsonArray; | |||||
| import com.google.gson.JsonElement; | |||||
| import com.google.gson.JsonObject; | |||||
| import com.google.gson.JsonParser; | |||||
| import com.google.gson.JsonPrimitive; | |||||
| import com.google.gson.*; | |||||
| import com.google.gson.reflect.TypeToken; | import com.google.gson.reflect.TypeToken; | ||||
| import me.chanjar.weixin.common.bean.WxCardApiSignature; | import me.chanjar.weixin.common.bean.WxCardApiSignature; | ||||
| import me.chanjar.weixin.common.error.WxError; | import me.chanjar.weixin.common.error.WxError; | ||||
| @@ -22,12 +10,15 @@ import me.chanjar.weixin.common.util.crypto.SHA1; | |||||
| import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | ||||
| import me.chanjar.weixin.mp.api.WxMpCardService; | import me.chanjar.weixin.mp.api.WxMpCardService; | ||||
| import me.chanjar.weixin.mp.api.WxMpService; | import me.chanjar.weixin.mp.api.WxMpService; | ||||
| import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateRequest; | |||||
| import me.chanjar.weixin.mp.bean.card.WxMpCardLandingPageCreateResult; | |||||
| import me.chanjar.weixin.mp.bean.card.WxMpCardQrcodeCreateResult; | |||||
| import me.chanjar.weixin.mp.bean.result.WxMpCardResult; | |||||
| import me.chanjar.weixin.mp.bean.card.*; | |||||
| import me.chanjar.weixin.mp.enums.TicketType; | import me.chanjar.weixin.mp.enums.TicketType; | ||||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | ||||
| import org.apache.commons.lang3.StringUtils; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.util.Arrays; | |||||
| import java.util.concurrent.locks.Lock; | |||||
| /** | /** | ||||
| * Created by Binary Wang on 2016/7/27. | * Created by Binary Wang on 2016/7/27. | ||||
| @@ -150,14 +141,6 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||||
| return jsonPrimitive.getAsString(); | return jsonPrimitive.getAsString(); | ||||
| } | } | ||||
| /** | |||||
| * 卡券Code查询. | |||||
| * | |||||
| * @param cardId 卡券ID代表一类卡券 | |||||
| * @param code 单张卡券的唯一标准 | |||||
| * @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同 | |||||
| * @return WxMpCardResult对象 | |||||
| */ | |||||
| @Override | @Override | ||||
| public WxMpCardResult queryCardCode(String cardId, String code, boolean checkConsume) throws WxErrorException { | public WxMpCardResult queryCardCode(String cardId, String code, boolean checkConsume) throws WxErrorException { | ||||
| JsonObject param = new JsonObject(); | JsonObject param = new JsonObject(); | ||||
| @@ -265,6 +248,13 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||||
| return respone; | return respone; | ||||
| } | } | ||||
| @Override | |||||
| public WxMpCardCreateResult createCard(WxMpCardCreateMessage cardCreateMessage) throws WxErrorException { | |||||
| String response = this.wxMpService.post(CARD_CREATE, GSON.toJson(cardCreateMessage)); | |||||
| return WxMpCardCreateResult.fromJson(response); | |||||
| } | |||||
| /** | /** | ||||
| * 创建卡券二维码. | * 创建卡券二维码. | ||||
| */ | */ | ||||
| @@ -324,4 +314,15 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||||
| jsonRequest.addProperty("reason", reason); | jsonRequest.addProperty("reason", reason); | ||||
| return this.wxMpService.post(CARD_CODE_UNAVAILABLE, GSON.toJson(jsonRequest)); | return this.wxMpService.post(CARD_CODE_UNAVAILABLE, GSON.toJson(jsonRequest)); | ||||
| } | } | ||||
| @Override | |||||
| public WxMpCardDeleteResult deleteCard(String cardId) throws WxErrorException { | |||||
| if (StringUtils.isEmpty(cardId)) { | |||||
| throw new WxErrorException(WxError.builder().errorCode(41012).errorMsg("cardId不能为空").build()); | |||||
| } | |||||
| JsonObject param = new JsonObject(); | |||||
| param.addProperty("card_id", cardId); | |||||
| String response = this.wxMpService.post(CARD_DELETE, param.toString()); | |||||
| return WxMpCardDeleteResult.fromJson(response); | |||||
| } | |||||
| } | } | ||||
| @@ -0,0 +1,92 @@ | |||||
| package me.chanjar.weixin.mp.api.impl; | |||||
| import com.google.gson.JsonArray; | |||||
| import com.google.gson.JsonElement; | |||||
| import com.google.gson.JsonObject; | |||||
| import com.google.gson.JsonParser; | |||||
| import me.chanjar.weixin.common.error.WxErrorException; | |||||
| import me.chanjar.weixin.mp.api.WxMpMarketingService; | |||||
| import me.chanjar.weixin.mp.api.WxMpService; | |||||
| import me.chanjar.weixin.mp.bean.marketing.WxMpAdLeadFilter; | |||||
| import me.chanjar.weixin.mp.bean.marketing.WxMpAdLeadResult; | |||||
| import me.chanjar.weixin.mp.bean.marketing.WxMpUserAction; | |||||
| import me.chanjar.weixin.mp.bean.marketing.WxMpUserActionSet; | |||||
| import org.apache.commons.lang3.time.DateFormatUtils; | |||||
| import org.slf4j.Logger; | |||||
| import org.slf4j.LoggerFactory; | |||||
| import java.io.IOException; | |||||
| import java.net.URLEncoder; | |||||
| import java.nio.charset.StandardCharsets; | |||||
| import java.util.Date; | |||||
| import java.util.List; | |||||
| /** | |||||
| * @author <a href="https://github.com/007gzs">007</a> | |||||
| */ | |||||
| public class WxMpMarketingServiceImpl implements WxMpMarketingService { | |||||
| protected final Logger log = LoggerFactory.getLogger(this.getClass()); | |||||
| private WxMpService wxMpService; | |||||
| public WxMpMarketingServiceImpl(WxMpService wxMpService) { | |||||
| this.wxMpService = wxMpService; | |||||
| } | |||||
| @Override | |||||
| public long addUserActionSets(String type, String name, String description) throws WxErrorException { | |||||
| String url = API_URL_PREFIX + "user_action_sets/add?version=v1.0"; | |||||
| JsonObject json = new JsonObject(); | |||||
| json.addProperty("type", type); | |||||
| json.addProperty("name", name); | |||||
| json.addProperty("description", description); | |||||
| String responseContent = wxMpService.post(url, json.toString()); | |||||
| JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||||
| return tmpJsonElement.getAsJsonObject().get("data").getAsJsonObject().get("user_action_set_id").getAsLong(); | |||||
| } | |||||
| @Override | |||||
| public List<WxMpUserActionSet> getUserActionSets(Long userActionSetId) throws WxErrorException { | |||||
| String url = API_URL_PREFIX + "user_action_sets/get"; | |||||
| String responseContent = wxMpService.get(url, "version=v1.0&user_action_set_id=" + userActionSetId); | |||||
| return WxMpUserActionSet.fromJson(responseContent); | |||||
| } | |||||
| @Override | |||||
| public void addUserAction(List<WxMpUserAction> actions) throws WxErrorException { | |||||
| String url = API_URL_PREFIX + "user_actions/add?version=v1.0"; | |||||
| JsonArray json = new JsonArray(); | |||||
| for (WxMpUserAction action : actions) { | |||||
| json.add(action.toJsonObject()); | |||||
| } | |||||
| wxMpService.post(url, json.toString()); | |||||
| } | |||||
| @Override | |||||
| public WxMpAdLeadResult getAdLeads(Date beginDate, Date endDate, List<WxMpAdLeadFilter> filtering, Integer page, Integer page_size) throws WxErrorException, IOException { | |||||
| Date today = new Date(); | |||||
| if (beginDate == null) { | |||||
| beginDate = today; | |||||
| } | |||||
| if (endDate == null) { | |||||
| endDate = today; | |||||
| } | |||||
| String url = API_URL_PREFIX + "wechat_ad_leads/get"; | |||||
| String params = "version=v1.0"; | |||||
| JsonObject dateRange = new JsonObject(); | |||||
| dateRange.addProperty("begin_date", DateFormatUtils.format(beginDate, "yyyy-MM-dd")); | |||||
| dateRange.addProperty("end_date", DateFormatUtils.format(endDate, "yyyy-MM-dd")); | |||||
| params += "&date_range=" + URLEncoder.encode(dateRange.toString(), StandardCharsets.UTF_8.name()); | |||||
| params += "&page=" + page; | |||||
| params += "&page_size=" + page_size; | |||||
| if (filtering != null) { | |||||
| JsonArray filterJson = new JsonArray(); | |||||
| for (WxMpAdLeadFilter filter : filtering) { | |||||
| filterJson.add(filter.toJsonObject()); | |||||
| } | |||||
| params += "&filtering=" + URLEncoder.encode(filterJson.toString(), StandardCharsets.UTF_8.name()); | |||||
| ; | |||||
| } | |||||
| String responseContent = wxMpService.get(url, params); | |||||
| return WxMpAdLeadResult.fromJson(responseContent); | |||||
| } | |||||
| } | |||||
| @@ -5,6 +5,7 @@ import java.net.URLDecoder; | |||||
| import java.util.HashMap; | import java.util.HashMap; | ||||
| import java.util.Map; | import java.util.Map; | ||||
| import me.chanjar.weixin.mp.bean.membercard.*; | |||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||
| @@ -30,13 +31,6 @@ import me.chanjar.weixin.mp.bean.card.WxMpCardCreateResult; | |||||
| import me.chanjar.weixin.mp.bean.card.enums.BusinessServiceType; | import me.chanjar.weixin.mp.bean.card.enums.BusinessServiceType; | ||||
| import me.chanjar.weixin.mp.bean.card.enums.CardColor; | import me.chanjar.weixin.mp.bean.card.enums.CardColor; | ||||
| import me.chanjar.weixin.mp.bean.card.enums.DateInfoType; | import me.chanjar.weixin.mp.bean.card.enums.DateInfoType; | ||||
| import me.chanjar.weixin.mp.bean.membercard.ActivatePluginParam; | |||||
| import me.chanjar.weixin.mp.bean.membercard.ActivatePluginParamResult; | |||||
| import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardActivatedMessage; | |||||
| import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardCreateMessage; | |||||
| import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateMessage; | |||||
| import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUpdateResult; | |||||
| import me.chanjar.weixin.mp.bean.membercard.WxMpMemberCardUserInfoResult; | |||||
| import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | import me.chanjar.weixin.mp.util.json.WxMpGsonBuilder; | ||||
| /** | /** | ||||
| @@ -51,7 +45,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||||
| private static final Gson GSON = WxMpGsonBuilder.create(); | private static final Gson GSON = WxMpGsonBuilder.create(); | ||||
| WxMpMemberCardServiceImpl(WxMpService wxMpService) { | |||||
| public WxMpMemberCardServiceImpl(WxMpService wxMpService) { | |||||
| this.wxMpService = wxMpService; | this.wxMpService = wxMpService; | ||||
| } | } | ||||
| @@ -76,7 +70,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||||
| return validResult; | return validResult; | ||||
| } | } | ||||
| String response = this.wxMpService.post(MEMBER_CARD_CREAET, GSON.toJson(createMessageMessage)); | |||||
| String response = this.wxMpService.post(MEMBER_CARD_CREATE, GSON.toJson(createMessageMessage)); | |||||
| return WxMpCardCreateResult.fromJson(response); | return WxMpCardCreateResult.fromJson(response); | ||||
| } | } | ||||
| @@ -249,7 +243,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||||
| @Override | @Override | ||||
| public MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException { | public MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException { | ||||
| String responseContent = this.getWxMpService().post(MEMBER_CARD_ACTIVATEUSERFORM, GSON.toJson(userFormRequest)); | |||||
| String responseContent = this.getWxMpService().post(MEMBER_CARD_ACTIVATE_USER_FORM, GSON.toJson(userFormRequest)); | |||||
| return MemberCardActivateUserFormResult.fromJson(responseContent); | return MemberCardActivateUserFormResult.fromJson(responseContent); | ||||
| } | } | ||||
| @@ -284,6 +278,15 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||||
| return result; | return result; | ||||
| } | } | ||||
| @Override | |||||
| public WxMpMemberCardActivateTempInfoResult getActivateTempInfo(String activateTicket) throws WxErrorException { | |||||
| JsonObject params = new JsonObject(); | |||||
| params.addProperty("activate_ticket", activateTicket); | |||||
| String response = this.wxMpService.post(MEMBER_CARD_ACTIVATE_TEMP_INFO, GSON.toJson(params)); | |||||
| WxMpMemberCardActivateTempInfoResult result = GSON.fromJson(response, WxMpMemberCardActivateTempInfoResult.class); | |||||
| return result; | |||||
| } | |||||
| private static String truncateUrlPage(String strURL) { | private static String truncateUrlPage(String strURL) { | ||||
| String strAllParam = null; | String strAllParam = null; | ||||
| String[] arrSplit; | String[] arrSplit; | ||||