@@ -42,6 +42,7 @@ Icon | |||
.Spotlight-V100 | |||
.TemporaryItems | |||
.Trashes | |||
.vscode | |||
.VolumeIcon.icns | |||
.AppleDB | |||
.AppleDesktop | |||
@@ -0,0 +1,3 @@ | |||
{ | |||
"java.configuration.updateBuildConfiguration": "interactive" | |||
} |
@@ -6,7 +6,7 @@ | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>com.github.binarywang</groupId> | |||
<artifactId>wx-java</artifactId> | |||
<version>3.3.0</version> | |||
<version>3.4.0</version> | |||
<packaging>pom</packaging> | |||
<name>WxJava - Weixin/Wechat Java SDK</name> | |||
<description>微信开发Java SDK</description> | |||
@@ -105,6 +105,8 @@ | |||
<module>weixin-java-pay</module> | |||
<module>weixin-java-miniapp</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--> | |||
</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"?> | |||
<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" | |||
xmlns="http://maven.apache.org/POM/4.0.0"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<parent> | |||
<groupId>com.github.binarywang</groupId> | |||
<artifactId>wx-java</artifactId> | |||
<version>3.3.0</version> | |||
<version>3.4.0</version> | |||
</parent> | |||
<artifactId>weixin-java-common</artifactId> | |||
<name>WxJava - Common</name> | |||
<name>WxJava - Common Java SDK</name> | |||
<description>微信开发Java SDK公共模块</description> | |||
<dependencies> | |||
@@ -62,6 +62,11 @@ public class WxConsts { | |||
* 图文消息(点击跳转到图文消息页面). | |||
*/ | |||
public static final String MPNEWS = "mpnews"; | |||
/** | |||
* markdown消息. | |||
* (目前仅支持markdown语法的子集,微工作台(原企业号)不支持展示markdown消息) | |||
*/ | |||
public static final String MARKDOWN = "markdown"; | |||
/** | |||
* 发送文件(CP专用). | |||
*/ | |||
@@ -83,6 +88,11 @@ public class WxConsts { | |||
* 小程序卡片(要求小程序与公众号已关联) | |||
*/ | |||
public static final String MINIPROGRAMPAGE = "miniprogrampage"; | |||
/** | |||
* 任务卡片消息 | |||
*/ | |||
public static final String TASKCARD = "taskcard"; | |||
} | |||
/** | |||
@@ -39,6 +39,7 @@ public class WxCryptUtil { | |||
try { | |||
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); | |||
factory.setExpandEntityReferences(false); | |||
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); | |||
return factory.newDocumentBuilder(); | |||
} catch (ParserConfigurationException exc) { | |||
throw new IllegalArgumentException(exc); | |||
@@ -57,7 +57,7 @@ public class ApacheMediaDownloadRequestExecutor extends BaseMediaDownloadRequest | |||
String fileName = new HttpResponseProxy(response).getFileName(); | |||
if (StringUtils.isBlank(fileName)) { | |||
return null; | |||
fileName = String.valueOf(System.currentTimeMillis()); | |||
} | |||
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.util.*; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
/** | |||
* An internationalization / localization helper class which reduces | |||
@@ -46,7 +47,7 @@ import java.util.*; | |||
*/ | |||
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; | |||
/** | |||
* The ResourceBundle for this StringManager. | |||
@@ -40,6 +40,7 @@ public class WxCryptUtilTest { | |||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); | |||
documentBuilderFactory.setExpandEntityReferences(false); | |||
documentBuilderFactory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); | |||
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); | |||
Document document = documentBuilder.parse(new InputSource(new StringReader(encryptedXml))); | |||
@@ -83,6 +84,8 @@ public class WxCryptUtilTest { | |||
String afterEncrpt = pc.encrypt(this.replyMsg); | |||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); | |||
dbf.setExpandEntityReferences(false); | |||
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); | |||
DocumentBuilder db = dbf.newDocumentBuilder(); | |||
StringReader sr = new StringReader(afterEncrpt); | |||
InputSource is = new InputSource(sr); | |||
@@ -7,11 +7,11 @@ | |||
<parent> | |||
<groupId>com.github.binarywang</groupId> | |||
<artifactId>wx-java</artifactId> | |||
<version>3.3.0</version> | |||
<version>3.4.0</version> | |||
</parent> | |||
<artifactId>weixin-java-cp</artifactId> | |||
<name>WxJava - CP</name> | |||
<name>WxJava - CP Java SDK</name> | |||
<description>微信企业号/企业微信 Java SDK</description> | |||
<dependencies> | |||
@@ -84,6 +84,11 @@ public class WxCpConsts { | |||
*/ | |||
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 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 me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.cp.bean.WxCpAppChatMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpChat; | |||
/** | |||
* 群聊服务 | |||
* 群聊服务. | |||
* | |||
* @author gaigeshen | |||
*/ | |||
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 | |||
* @return 创建群聊会话的结果,群聊的唯一标志 | |||
* @return 创建的群聊会话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列表,若不需要更新,则传递空对象或者空集合 | |||
* @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 群聊编号 | |||
* @return 群聊会话 | |||
* @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 | |||
* @throws WxErrorException 异常 | |||
*/ | |||
Integer create(WxCpDepart depart) throws WxErrorException; | |||
Long create(WxCpDepart depart) throws WxErrorException; | |||
/** | |||
* <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.RequestExecutor; | |||
import me.chanjar.weixin.common.util.http.RequestHttp; | |||
import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult; | |||
import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | |||
import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
@@ -16,6 +17,15 @@ import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
* @author chanjaster | |||
*/ | |||
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> | |||
* 验证推送过来的消息的正确性 | |||
@@ -68,6 +78,33 @@ public interface WxCpService { | |||
*/ | |||
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> | |||
* 创建调用jsapi时所需要的签名 | |||
@@ -89,6 +126,13 @@ public interface WxCpService { | |||
*/ | |||
WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException; | |||
/** | |||
* 小程序登录凭证校验 | |||
* | |||
* @param jsCode 登录时获取的 code | |||
*/ | |||
WxCpMaJsCode2SessionResult jsCode2Session(String jsCode) throws WxErrorException; | |||
/** | |||
* <pre> | |||
* 获取微信服务器的ip段 | |||
@@ -165,6 +209,13 @@ public interface WxCpService { | |||
*/ | |||
WxSession getSession(String id, boolean create); | |||
/** | |||
* 获取WxSessionManager 对象 | |||
* | |||
* @return WxSessionManager | |||
*/ | |||
WxSessionManager getSessionManager(); | |||
/** | |||
* <pre> | |||
* 设置WxSessionManager,只有当需要使用个性化的WxSessionManager的时候才需要调用此方法, | |||
@@ -250,8 +301,17 @@ public interface WxCpService { | |||
*/ | |||
WxCpChatService getChatService(); | |||
/** | |||
* 获取任务卡片服务 | |||
* | |||
* @return 任务卡片服务 | |||
*/ | |||
WxCpTaskCardService getTaskCardService(); | |||
WxCpAgentService getAgentService(); | |||
WxCpOAService getOAService(); | |||
/** | |||
* 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; | |||
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.JsonElement; | |||
import com.google.gson.JsonObject; | |||
import com.google.gson.JsonParser; | |||
import me.chanjar.weixin.common.bean.WxJsapiSignature; | |||
import me.chanjar.weixin.common.error.WxError; | |||
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.SimpleGetRequestExecutor; | |||
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.WxCpMessageSendResult; | |||
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()); | |||
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 WxCpTagService tagService = new WxCpTagServiceImpl(this); | |||
private WxCpAgentService agentService = new WxCpAgentServiceImpl(this); | |||
private WxCpOAService oaService = new WxCpOAServiceImpl(this); | |||
private WxCpTaskCardService taskCardService = new WxCpTaskCardServiceImpl(this); | |||
/** | |||
* 全局的是否正在刷新access token的锁 | |||
@@ -59,14 +58,19 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||
*/ | |||
protected final Object globalJsapiTicketRefreshLock = new Object(); | |||
/** | |||
* 全局的是否正在刷新agent的jsapi_ticket的锁 | |||
*/ | |||
protected final Object globalAgentJsapiTicketRefreshLock = new Object(); | |||
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 maxRetryTimes = 5; | |||
@@ -86,6 +90,30 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||
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 | |||
public String getJsapiTicket() throws WxErrorException { | |||
@@ -97,20 +125,18 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||
if (forceRefresh) { | |||
this.configStorage.expireJsapiTicket(); | |||
} | |||
if (this.configStorage.isJsapiTicketExpired()) { | |||
synchronized (this.globalJsapiTicketRefreshLock) { | |||
if (this.configStorage.isJsapiTicketExpired()) { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket"; | |||
String responseContent = execute(SimpleGetRequestExecutor.create(this), url, null); | |||
JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
JsonObject tmpJsonObject = tmpJsonElement.getAsJsonObject(); | |||
String jsapiTicket = tmpJsonObject.get("ticket").getAsString(); | |||
int expiresInSeconds = tmpJsonObject.get("expires_in").getAsInt(); | |||
this.configStorage.updateJsapiTicket(jsapiTicket, | |||
expiresInSeconds); | |||
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(); | |||
} | |||
@@ -139,18 +165,27 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||
@Override | |||
public WxCpMessageSendResult messageSend(WxCpMessage message) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/message/send"; | |||
Integer agentId = message.getAgentId(); | |||
if(null == agentId){ | |||
if (null == agentId) { | |||
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 | |||
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); | |||
JsonArray jsonArray = tmpJsonElement.getAsJsonObject().get("ip_list").getAsJsonArray(); | |||
String[] ips = new String[jsonArray.size()]; | |||
@@ -171,7 +206,7 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||
} | |||
/** | |||
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求 | |||
* 向微信端发送请求,在这里执行的策略是当发生access_token过期时才去刷新,然后重新执行请求,而不是全局定时请求. | |||
*/ | |||
@Override | |||
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; | |||
} | |||
@Override | |||
public WxSessionManager getSessionManager() { | |||
return this.sessionManager; | |||
} | |||
@Override | |||
public String replaceParty(String mediaId) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceparty"; | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("media_id", mediaId); | |||
return post(url, jsonObject.toString()); | |||
return post(WxCpService.BATCH_REPLACE_PARTY, jsonObject.toString()); | |||
} | |||
@Override | |||
public String replaceUser(String mediaId) throws WxErrorException { | |||
String url = "https://qyapi.weixin.qq.com/cgi-bin/batch/replaceuser"; | |||
JsonObject jsonObject = new JsonObject(); | |||
jsonObject.addProperty("media_id", mediaId); | |||
return post(url, jsonObject.toString()); | |||
return post(WxCpService.BATCH_REPLACE_USER, jsonObject.toString()); | |||
} | |||
@Override | |||
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); | |||
} | |||
@@ -350,6 +388,16 @@ public abstract class WxCpServiceAbstractImpl<H, P> implements WxCpService, Requ | |||
return chatService; | |||
} | |||
@Override | |||
public WxCpOAService getOAService() { | |||
return oaService; | |||
} | |||
@Override | |||
public WxCpTaskCardService getTaskCardService() { | |||
return taskCardService; | |||
} | |||
@Override | |||
public RequestHttp<?, ?> getRequestHttp() { | |||
return this; |
@@ -1,36 +1,35 @@ | |||
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 me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.json.WxGsonBuilder; | |||
import me.chanjar.weixin.cp.api.WxCpChatService; | |||
import me.chanjar.weixin.cp.api.WxCpService; | |||
import me.chanjar.weixin.cp.bean.WxCpAppChatMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpChat; | |||
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 | |||
*/ | |||
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 | |||
@@ -48,12 +47,18 @@ public class WxCpChatServiceImpl implements WxCpChatService { | |||
if (StringUtils.isNotBlank(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(); | |||
} | |||
@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); | |||
if (StringUtils.isNotBlank(chatId)) { | |||
data.put("chatid", chatId); | |||
@@ -70,14 +75,30 @@ public class WxCpChatServiceImpl implements WxCpChatService { | |||
if (usersToDelete != null && !usersToDelete.isEmpty()) { | |||
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 | |||
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 | |||
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 responseContent = this.mainService.post(url, depart.toJson()); | |||
JsonElement tmpJsonElement = new JsonParser().parse(responseContent); | |||
return GsonHelper.getAsInteger(tmpJsonElement.getAsJsonObject().get("id")); | |||
return GsonHelper.getAsLong(tmpJsonElement.getAsJsonObject().get("id")); | |||
} | |||
@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.apache.ApacheHttpClientBuilder; | |||
import me.chanjar.weixin.common.util.http.apache.DefaultApacheHttpClientBuilder; | |||
import me.chanjar.weixin.cp.api.WxCpOAService; | |||
import me.chanjar.weixin.cp.config.WxCpConfigStorage; | |||
import org.apache.http.HttpHost; | |||
import org.apache.http.client.config.RequestConfig; | |||
@@ -18,7 +19,7 @@ import org.apache.http.impl.client.CloseableHttpClient; | |||
import java.io.IOException; | |||
public class WxCpServiceApacheHttpClientImpl extends WxCpServiceAbstractImpl<CloseableHttpClient, HttpHost> { | |||
public class WxCpServiceApacheHttpClientImpl extends BaseWxCpServiceImpl<CloseableHttpClient, HttpHost> { | |||
protected CloseableHttpClient httpClient; | |||
protected HttpHost httpProxy; | |||
@@ -39,37 +40,37 @@ public class WxCpServiceApacheHttpClientImpl extends WxCpServiceAbstractImpl<Clo | |||
@Override | |||
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(); | |||
@@ -8,7 +8,7 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.http.HttpType; | |||
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 ProxyInfo httpProxy; | |||
@@ -30,30 +30,29 @@ public class WxCpServiceJoddHttpImpl extends WxCpServiceAbstractImpl<HttpConnect | |||
@Override | |||
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(); | |||
} | |||
@@ -11,7 +11,7 @@ import okhttp3.*; | |||
import java.io.IOException; | |||
public class WxCpServiceOkHttpImpl extends WxCpServiceAbstractImpl<OkHttpClient, OkHttpProxyInfo> { | |||
public class WxCpServiceOkHttpImpl extends BaseWxCpServiceImpl<OkHttpClient, OkHttpProxyInfo> { | |||
protected OkHttpClient httpClient; | |||
protected OkHttpProxyInfo httpProxy; | |||
@@ -33,34 +33,33 @@ public class WxCpServiceOkHttpImpl extends WxCpServiceAbstractImpl<OkHttpClient, | |||
@Override | |||
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(); | |||
} | |||
@@ -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; | |||
@SerializedName("invaliduser") | |||
private String invalidUsers; | |||
private String[] invalidUsers; | |||
@SerializedName("invalidparty") | |||
private String[] invalidParties; | |||
@@ -45,16 +45,4 @@ public class WxCpInviteResult implements Serializable { | |||
@SerializedName("invalidtag") | |||
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; | |||
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 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.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<MpnewsArticle> mpnewsArticles = new ArrayList<>(); | |||
/** | |||
* 任务卡片特有的属性 | |||
*/ | |||
private String taskId; | |||
private List<TaskCardButton> taskButtons = new ArrayList<>(); | |||
/** | |||
* 获得文本消息builder. | |||
*/ | |||
@@ -94,6 +96,13 @@ public class WxCpMessage implements Serializable { | |||
return new MpnewsBuilder(); | |||
} | |||
/** | |||
* 获得markdown消息builder. | |||
*/ | |||
public static MarkdownMsgBuilder MARKDOWN() { | |||
return new MarkdownMsgBuilder(); | |||
} | |||
/** | |||
* 获得文件消息builder. | |||
*/ | |||
@@ -101,17 +110,26 @@ public class WxCpMessage implements Serializable { | |||
return new FileBuilder(); | |||
} | |||
/** | |||
* 获得任务卡片消息builder. | |||
*/ | |||
public static TaskCardBuilder TASKCARD() { | |||
return new TaskCardBuilder(); | |||
} | |||
/** | |||
* <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> | |||
* | |||
* @param msgType 消息类型 | |||
@@ -121,7 +139,162 @@ public class WxCpMessage implements Serializable { | |||
} | |||
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 String userId; | |||
private String name; | |||
private Integer[] departIds; | |||
private Long[] departIds; | |||
private Integer[] orders; | |||
private String position; | |||
private String mobile; | |||
private Gender gender; | |||
@@ -1,24 +1,26 @@ | |||
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.XStreamConverter; | |||
import com.thoughtworks.xstream.annotations.XStreamImplicit; | |||
import lombok.Data; | |||
import lombok.extern.slf4j.Slf4j; | |||
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.cp.config.WxCpConfigStorage; | |||
import me.chanjar.weixin.cp.util.crypto.WxCpCryptUtil; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
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> | |||
@@ -36,6 +38,11 @@ import me.chanjar.weixin.cp.util.xml.XStreamTransformer; | |||
public class WxCpXmlMessage implements Serializable { | |||
private static final long serialVersionUID = -1042994982179476410L; | |||
/** | |||
* 使用dom4j解析的存放所有xml属性和值的map. | |||
*/ | |||
private Map<String, Object> allFieldsMap; | |||
/////////////////////// | |||
// 以下都是微信推送过来的消息的xml的element所对应的属性 | |||
/////////////////////// | |||
@@ -149,6 +156,10 @@ public class WxCpXmlMessage implements Serializable { | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String recognition; | |||
@XStreamAlias("TaskId") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String taskId; | |||
/** | |||
* 通讯录变更事件. | |||
* 请参考常量 me.chanjar.weixin.cp.WxCpConsts.ContactChangeType | |||
@@ -240,6 +251,13 @@ public class WxCpXmlMessage implements Serializable { | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String telephone; | |||
/** | |||
* 地址. | |||
*/ | |||
@XStreamAlias("Address") | |||
@XStreamConverter(value = XStreamCDataConverter.class) | |||
private String address; | |||
/** | |||
* 扩展属性. | |||
*/ | |||
@@ -320,17 +338,20 @@ public class WxCpXmlMessage implements Serializable { | |||
*/ | |||
@XStreamAlias("TotalCount") | |||
private Integer totalCount; | |||
/** | |||
* 过滤. | |||
* (过滤是指特定地区、性别的过滤、用户设置拒收的过滤,用户接收已超4条的过滤)后,准备发送的粉丝数,原则上,filterCount = sentCount + errorCount | |||
*/ | |||
@XStreamAlias("FilterCount") | |||
private Integer filterCount; | |||
/** | |||
* 发送成功的粉丝数. | |||
*/ | |||
@XStreamAlias("SentCount") | |||
private Integer sentCount; | |||
/** | |||
* 发送失败的粉丝数. | |||
*/ | |||
@@ -349,7 +370,9 @@ public class WxCpXmlMessage implements Serializable { | |||
protected static WxCpXmlMessage fromXml(String xml) { | |||
//修改微信变态的消息内容格式,方便解析 | |||
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) { | |||
@@ -402,9 +425,11 @@ public class WxCpXmlMessage implements Serializable { | |||
@Data | |||
public static class ExtAttr { | |||
@XStreamAlias("Item") | |||
@XStreamImplicit(itemFieldName = "Item") | |||
protected final List<Item> items = new ArrayList<>(); | |||
@XStreamAlias("Item") | |||
@Data | |||
public static class Item { | |||
@XStreamAlias("Name") | |||
@@ -1,9 +1,12 @@ | |||
package me.chanjar.weixin.cp.bean.article; | |||
import lombok.Data; | |||
import java.io.Serializable; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Builder; | |||
import lombok.Data; | |||
import lombok.NoArgsConstructor; | |||
/** | |||
* <pre> | |||
* Created by BinaryWang on 2017/3/27. | |||
@@ -12,6 +15,9 @@ import java.io.Serializable; | |||
* @author Binary Wang | |||
*/ | |||
@Data | |||
@Builder | |||
@AllArgsConstructor | |||
@NoArgsConstructor | |||
public class NewArticle implements Serializable { | |||
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); | |||
String getAgentJsapiTicket(); | |||
boolean isAgentJsapiTicketExpired(); | |||
/** | |||
* 强制将jsapi ticket过期掉 | |||
*/ | |||
void expireAgentJsapiTicket(); | |||
/** | |||
* 应该是线程安全的 | |||
*/ | |||
void updateAgentJsapiTicket(String jsapiTicket, int expiresInSeconds); | |||
String getCorpId(); | |||
String getCorpSecret(); | |||
@@ -1,11 +1,11 @@ | |||
package me.chanjar.weixin.cp.config; | |||
import java.io.File; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; | |||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder; | |||
import java.io.File; | |||
/** | |||
* 基于内存的微信配置provider,在实际生产环境中应该将这些配置持久化 | |||
* | |||
@@ -32,6 +32,9 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
protected volatile String jsapiTicket; | |||
protected volatile long jsapiTicketExpiresTime; | |||
protected volatile String agentJsapiTicket; | |||
protected volatile long agentJsapiTicketExpiresTime; | |||
protected volatile File tmpDirFile; | |||
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder; | |||
@@ -95,6 +98,28 @@ public class WxCpInMemoryConfigStorage implements WxCpConfigStorage { | |||
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 | |||
public void expireJsapiTicket() { | |||
this.jsapiTicketExpiresTime = 0; | |||
@@ -9,26 +9,21 @@ import redis.clients.jedis.JedisPoolConfig; | |||
import java.io.File; | |||
/** | |||
* Jedis client implementor for wechat config storage. | |||
* <pre> | |||
* 使用说明:本实现仅供参考,并不完整, | |||
* 使用说明:本实现仅供参考,并不完整. | |||
* 比如为减少项目依赖,未加入redis分布式锁的实现,如有需要请自行实现。 | |||
* </pre> | |||
* | |||
* @author gaigeshen | |||
*/ | |||
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_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_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 volatile String corpId; | |||
private volatile String corpSecret; | |||
@@ -46,7 +41,7 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||
public WxCpJedisConfigStorage(JedisPool jedisPool) { | |||
this.jedisPool = jedisPool; | |||
} | |||
public WxCpJedisConfigStorage(String host, int port) { | |||
jedisPool = new JedisPool(host, port); | |||
} | |||
@@ -83,8 +78,7 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||
String expiresTimeStr = jedis.get(ACCESS_TOKEN_EXPIRES_TIME_KEY); | |||
if (expiresTimeStr != null) { | |||
Long expiresTime = Long.parseLong(expiresTimeStr); | |||
return System.currentTimeMillis() > expiresTime; | |||
return System.currentTimeMillis() > Long.parseLong(expiresTimeStr); | |||
} | |||
return true; | |||
@@ -123,17 +117,15 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||
@Override | |||
public boolean isJsapiTicketExpired() { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
String expiresTimeStr = jedis.get(JS_API_TICKET_EXPIRES_TIME_KEY); | |||
if (expiresTimeStr != null) { | |||
Long expiresTime = Long.parseLong(expiresTimeStr); | |||
long expiresTime = Long.parseLong(expiresTimeStr); | |||
return System.currentTimeMillis() > expiresTime; | |||
} | |||
return true; | |||
} | |||
} | |||
@@ -146,16 +138,51 @@ public class WxCpJedisConfigStorage implements WxCpConfigStorage { | |||
@Override | |||
public synchronized void updateJsapiTicket(String jsapiTicket, int expiresInSeconds) { | |||
try (Jedis jedis = this.jedisPool.getResource()) { | |||
jedis.set(JS_API_TICKET_KEY, jsapiTicket); | |||
jedis.set(JS_API_TICKET_EXPIRES_TIME_KEY, | |||
(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 | |||
public String getCorpId() { | |||
return this.corpId; | |||
@@ -73,7 +73,7 @@ public class WxCpMessageRouter { | |||
this.wxCpService = wxCpService; | |||
this.executorService = Executors.newFixedThreadPool(DEFAULT_THREAD_POOL_SIZE); | |||
this.messageDuplicateChecker = new WxMessageInMemoryDuplicateChecker(); | |||
this.sessionManager = new StandardSessionManager(); | |||
this.sessionManager = wxCpService.getSessionManager(); | |||
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.cp.bean.WxCpChat; | |||
import me.chanjar.weixin.cp.bean.WxCpDepart; | |||
import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpTag; | |||
import me.chanjar.weixin.cp.bean.WxCpUser; | |||
@@ -20,7 +19,6 @@ public class WxCpGsonBuilder { | |||
static { | |||
INSTANCE.disableHtmlEscaping(); | |||
INSTANCE.registerTypeAdapter(WxCpMessage.class, new WxCpMessageGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxCpChat.class, new WxCpChatGsonAdapter()); | |||
INSTANCE.registerTypeAdapter(WxCpDepart.class, new WxCpDepartGsonAdapter()); | |||
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 | |||
* is not permitted without written permission of the KINGSTAR MEDIA SOLUTIONS LTD. | |||
*/ | |||
package me.chanjar.weixin.cp.util.json; | |||
import java.lang.reflect.Type; | |||
@@ -24,11 +25,14 @@ import me.chanjar.weixin.cp.bean.Gender; | |||
import me.chanjar.weixin.cp.bean.WxCpUser; | |||
/** | |||
* cp user gson adapter. | |||
* | |||
* @author Daniel Qian | |||
*/ | |||
public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSerializer<WxCpUser> { | |||
private static final String EXTERNAL_PROFILE = "external_profile"; | |||
private static final String EXTERNAL_ATTR = "external_attr"; | |||
private static final String EXTATTR = "extattr"; | |||
@Override | |||
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) { | |||
JsonArray departJsonArray = o.get("department").getAsJsonArray(); | |||
Integer[] departIds = new Integer[departJsonArray.size()]; | |||
Long[] departIds = new Long[departJsonArray.size()]; | |||
int i = 0; | |||
for (JsonElement jsonElement : departJsonArray) { | |||
departIds[i++] = jsonElement.getAsInt(); | |||
departIds[i++] = jsonElement.getAsLong(); | |||
} | |||
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.setName(GsonHelper.getString(o, "name")); | |||
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.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))) { | |||
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 | |||
@@ -133,11 +156,20 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||
} | |||
if (user.getDepartIds() != null) { | |||
JsonArray jsonArray = new JsonArray(); | |||
for (Integer departId : user.getDepartIds()) { | |||
for (Long departId : user.getDepartIds()) { | |||
jsonArray.add(new JsonPrimitive(departId)); | |||
} | |||
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) { | |||
o.addProperty("position", user.getPosition()); | |||
} | |||
@@ -191,14 +223,14 @@ public class WxCpUserGsonAdapter implements JsonDeserializer<WxCpUser>, JsonSeri | |||
} | |||
JsonObject attrsJson = new JsonObject(); | |||
attrsJson.add("attrs", attrsJsonArray); | |||
o.add("extattr", attrsJson); | |||
o.add(EXTATTR, attrsJson); | |||
} | |||
if (user.getExternalAttrs().size() > 0) { | |||
JsonArray attrsJsonArray = new JsonArray(); | |||
for (WxCpUser.ExternalAttribute attr : user.getExternalAttrs()) { | |||
JsonObject attrJson = new JsonObject(); | |||
attrJson.addProperty("type",attr.getType()); | |||
attrJson.addProperty("type", attr.getType()); | |||
attrJson.addProperty("name", attr.getName()); | |||
switch (attr.getType()) { | |||
case 0: { | |||
@@ -1,11 +1,12 @@ | |||
package me.chanjar.weixin.cp.api; | |||
import org.testng.annotations.*; | |||
import com.google.inject.Inject; | |||
import me.chanjar.weixin.common.api.WxConsts; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.cp.bean.WxCpMessage; | |||
import me.chanjar.weixin.cp.bean.WxCpMessageSendResult; | |||
import org.testng.annotations.*; | |||
import static org.testng.Assert.*; | |||
@@ -14,7 +15,7 @@ import static org.testng.Assert.*; | |||
* @author Daniel Qian | |||
* | |||
*/ | |||
@Test(groups = "customMessageAPI") | |||
@Test | |||
@Guice(modules = ApiTestModule.class) | |||
public class WxCpMessageAPITest { | |||
@@ -59,4 +60,51 @@ public class WxCpMessageAPITest { | |||
System.out.println(messageSendResult.getInvalidUserList()); | |||
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 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 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.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) | |||
public class WxCpChatServiceImplTest { | |||
private String chatId; | |||
private String userId; | |||
@Inject | |||
private WxCpService wxCpService; | |||
private WxCpService cpService; | |||
@BeforeTest | |||
public void init() { | |||
this.chatId = "mychatid"; | |||
this.userId = ((ApiTestModule.WxXmlCpInMemoryConfigStorage) this.cpService.getWxCpConfigStorage()).getUserId(); | |||
} | |||
@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 | |||
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); | |||
Assert.assertEquals(chat.getName(), "测试群聊"); | |||
} | |||
@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); | |||
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.setParentId(1L); | |||
cpDepart.setOrder(1L); | |||
Integer departId = this.wxCpService.getDepartmentService().create(cpDepart); | |||
Long departId = this.wxCpService.getDepartmentService().create(cpDepart); | |||
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(); | |||
user.setUserId(userId); | |||
user.setName("Some Woman"); | |||
user.setDepartIds(new Integer[]{2}); | |||
user.setDepartIds(new Long[]{2L}); | |||
user.setEmail("none@none.com"); | |||
user.setGender(Gender.FEMALE); | |||
user.setMobile("13560084979"); | |||
@@ -1,7 +1,7 @@ | |||
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. | |||
@@ -10,7 +10,13 @@ import org.testng.annotations.Test; | |||
public class WxCpAgentTest { | |||
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); | |||
@@ -18,7 +24,8 @@ public class WxCpAgentTest { | |||
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.NewArticle; | |||
import me.chanjar.weixin.cp.bean.taskcard.TaskCardButton; | |||
import org.testng.annotations.Test; | |||
import java.util.Arrays; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.testng.Assert.assertEquals; | |||
@Test | |||
public class WxCpMessageTest { | |||
@@ -19,12 +21,16 @@ public class WxCpMessageTest { | |||
public void testTextCardBuild() { | |||
WxCpMessage reply = WxCpMessage.TEXTCARD().toUser("OPENID") | |||
.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") | |||
.btnTxt("更多") | |||
.build(); | |||
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() { | |||
@@ -40,9 +46,11 @@ public class WxCpMessageTest { | |||
} | |||
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()) | |||
.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() { | |||
@@ -61,7 +69,10 @@ public class WxCpMessageTest { | |||
WxCpMessage reply = WxCpMessage.NEWS().toUser("OPENID").addArticle(article1).addArticle(article2).build(); | |||
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() { | |||
@@ -88,14 +99,45 @@ public class WxCpMessageTest { | |||
WxCpMessage reply = WxCpMessage.MPNEWS().toUser("OPENID").addArticle(article1, article2).build(); | |||
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() { | |||
WxCpMessage reply = WxCpMessage.MPNEWS().toUser("OPENID").mediaId("mmm").build(); | |||
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; | |||
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 | |||
public class WxCpXmlMessageTest { | |||
@@ -117,4 +119,58 @@ public class WxCpXmlMessageTest { | |||
assertEquals(wxMessage.getSendPicsInfo().getPicList().get(0).getPicMd5Sum(), "aef52ae501537e552725c5d7f99c1741"); | |||
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; | |||
import java.io.InputStream; | |||
import com.thoughtworks.xstream.XStream; | |||
import com.thoughtworks.xstream.annotations.XStreamAlias; | |||
import lombok.ToString; | |||
import me.chanjar.weixin.common.util.xml.XStreamInitializer; | |||
import me.chanjar.weixin.cp.config.WxCpInMemoryConfigStorage; | |||
import java.io.InputStream; | |||
/** | |||
* @author Daniel Qian | |||
*/ | |||
@XStreamAlias("xml") | |||
class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage { | |||
@ToString | |||
public class WxCpDemoInMemoryConfigStorage extends WxCpInMemoryConfigStorage { | |||
public static WxCpDemoInMemoryConfigStorage fromXml(InputStream is) { | |||
XStream xstream = XStreamInitializer.getInstance(); | |||
xstream.processAnnotations(WxCpDemoInMemoryConfigStorage.class); | |||
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); | |||
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(); | |||
final WxCpUser.ExternalAttribute externalAttr1 = user.getExternalAttrs().get(0); | |||
@@ -100,6 +107,7 @@ public class WxCpUserGsonAdapterTest { | |||
@Test | |||
public void testSerialize() { | |||
WxCpUser user = new WxCpUser(); | |||
user.setOrders(new Integer[]{1, 2}); | |||
user.addExternalAttr(WxCpUser.ExternalAttribute.builder() | |||
.type(0) | |||
.name("文本名称") | |||
@@ -119,6 +127,10 @@ public class WxCpUserGsonAdapterTest { | |||
.title("my miniprogram") | |||
.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> | |||
<groupId>com.github.binarywang</groupId> | |||
<artifactId>wx-java</artifactId> | |||
<version>3.3.0</version> | |||
<version>3.4.0</version> | |||
</parent> | |||
<artifactId>weixin-java-miniapp</artifactId> | |||
<name>WxJava - MiniApp</name> | |||
<description>微信小程序Java SDK</description> | |||
<name>WxJava - MiniApp Java SDK</name> | |||
<description>微信小程序 Java SDK</description> | |||
<dependencies> | |||
<dependency> | |||
@@ -70,6 +71,11 @@ | |||
<groupId>redis.clients</groupId> | |||
<artifactId>jedis</artifactId> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.bouncycastle</groupId> | |||
<artifactId>bcpkix-jdk15on</artifactId> | |||
<version>1.59</version> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.projectlombok</groupId> | |||
<artifactId>lombok</artifactId> | |||
@@ -1,14 +1,10 @@ | |||
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 java.util.List; | |||
/** | |||
* 小程序代码管理相关 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 | |||
@@ -18,7 +14,7 @@ import me.chanjar.weixin.common.error.WxErrorException; | |||
*/ | |||
public interface WxMaCodeService { | |||
/** | |||
* 为授权的小程序帐号上传小程序代码 | |||
* 为授权的小程序帐号上传小程序代码. | |||
*/ | |||
String COMMIT_URL = "https://api.weixin.qq.com/wxa/commit"; | |||
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"; | |||
/** | |||
* 为授权的小程序帐号上传小程序代码(仅仅支持第三方开放平台) | |||
* 为授权的小程序帐号上传小程序代码(仅仅支持第三方开放平台). | |||
* | |||
* @param commitRequest 参数 | |||
* @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档 | |||
@@ -43,19 +39,19 @@ public interface WxMaCodeService { | |||
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 | |||
* | |||
* @param path 指定体验版二维码跳转到某个具体页面(如果不需要的话,则不需要填path参数,可在路径后以“?参数”方式传入参数) | |||
* 具体的路径加参数需要urlencode(方法内部处理),比如page/index?action=1编码后得到page%2Findex%3Faction%3D1 | |||
* 具体的路径加参数需要urlencode(方法内部处理),比如page/index?action=1编码后得到page%2Findex%3Faction%3D1 | |||
* @return 二维码 bytes | |||
* @throws WxErrorException 上传失败时抛出,具体错误码请看类注释文档 | |||
*/ | |||
byte[] getQrCode(String path) throws WxErrorException; | |||
/** | |||
* 获取授权小程序帐号的可选类目 | |||
* 获取授权小程序帐号的可选类目. | |||
* | |||
* @return List<WxMaCategory> | |||
* @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档 | |||
@@ -63,7 +59,7 @@ public interface WxMaCodeService { | |||
List<WxMaCategory> getCategory() throws WxErrorException; | |||
/** | |||
* 获取小程序的第三方提交代码的页面配置(仅供第三方开发者代小程序调用) | |||
* 获取小程序的第三方提交代码的页面配置(仅供第三方开发者代小程序调用). | |||
* | |||
* @return page_list 页面配置列表 | |||
* @throws WxErrorException 获取失败时返回,具体错误码请看此接口的注释文档 | |||
@@ -71,7 +67,7 @@ public interface WxMaCodeService { | |||
List<String> getPage() throws WxErrorException; | |||
/** | |||
* 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用) | |||
* 将第三方提交的代码包提交审核(仅供第三方开发者代小程序调用). | |||
* | |||
* @param auditRequest 提交审核参数 | |||
* @return 审核编号 | |||
@@ -80,7 +76,7 @@ public interface WxMaCodeService { | |||
long submitAudit(WxMaCodeSubmitAuditRequest auditRequest) throws WxErrorException; | |||
/** | |||
* 查询某个指定版本的审核状态(仅供第三方代小程序调用) | |||
* 查询某个指定版本的审核状态(仅供第三方代小程序调用). | |||
* | |||
* @param auditId 提交审核时获得的审核id | |||
* @return 审核状态 | |||
@@ -89,7 +85,7 @@ public interface WxMaCodeService { | |||
WxMaCodeAuditStatus getAuditStatus(long auditId) throws WxErrorException; | |||
/** | |||
* 查询最新一次提交的审核状态(仅供第三方代小程序调用) | |||
* 查询最新一次提交的审核状态(仅供第三方代小程序调用). | |||
* | |||
* @return 审核状态 | |||
* @throws WxErrorException 查询失败时返回,具体错误码请看此接口的注释文档 | |||
@@ -97,14 +93,14 @@ public interface WxMaCodeService { | |||
WxMaCodeAuditStatus getLatestAuditStatus() throws WxErrorException; | |||
/** | |||
* 发布已通过审核的小程序(仅供第三方代小程序调用) | |||
* 发布已通过审核的小程序(仅供第三方代小程序调用). | |||
* | |||
* @throws WxErrorException 发布失败时抛出,具体错误码请看此接口的注释文档 | |||
*/ | |||
void release() throws WxErrorException; | |||
/** | |||
* 修改小程序线上代码的可见状态(仅供第三方代小程序调用) | |||
* 修改小程序线上代码的可见状态(仅供第三方代小程序调用). | |||
* | |||
* @param action 设置可访问状态,发布后默认可访问,close为不可见,open为可见 | |||
* @throws WxErrorException 发布失败时抛出,具体错误码请看此接口的注释文档 | |||
@@ -112,14 +108,14 @@ public interface WxMaCodeService { | |||
void changeVisitStatus(String action) throws WxErrorException; | |||
/** | |||
* 小程序版本回退(仅供第三方代小程序调用) | |||
* 小程序版本回退(仅供第三方代小程序调用). | |||
* | |||
* @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | |||
*/ | |||
void revertCodeRelease() throws WxErrorException; | |||
/** | |||
* 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用) | |||
* 查询当前设置的最低基础库版本及各版本用户占比 (仅供第三方代小程序调用). | |||
* | |||
* @return 小程序版本分布信息 | |||
* @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | |||
@@ -127,7 +123,7 @@ public interface WxMaCodeService { | |||
WxMaCodeVersionDistribution getSupportVersion() throws WxErrorException; | |||
/** | |||
* 设置最低基础库版本(仅供第三方代小程序调用) | |||
* 设置最低基础库版本(仅供第三方代小程序调用). | |||
* | |||
* @param version 版本 | |||
* @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | |||
@@ -135,7 +131,7 @@ public interface WxMaCodeService { | |||
void setSupportVersion(String version) throws WxErrorException; | |||
/** | |||
* 小程序审核撤回 | |||
* 小程序审核撤回. | |||
* 单个帐号每天审核撤回次数最多不超过1次,一个月不超过10次 | |||
* | |||
* @throws WxErrorException 失败时抛出,具体错误码请看此接口的注释文档 | |||
@@ -20,7 +20,7 @@ public interface WxMaMsgService { | |||
/** | |||
* <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 | |||
* </pre> | |||
*/ | |||
@@ -29,7 +29,7 @@ public interface WxMaMsgService { | |||
/** | |||
* <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 | |||
* </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 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信息. | |||
@@ -56,6 +60,22 @@ public interface WxMaService { | |||
*/ | |||
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请求. | |||
*/ | |||
@@ -168,18 +188,21 @@ public interface WxMaService { | |||
/** | |||
* 返回分享相关查询服务. | |||
* | |||
* @return WxMaShareService | |||
*/ | |||
WxMaShareService getShareService(); | |||
/** | |||
* 返回微信运动相关接口服务对象. | |||
* | |||
* @return WxMaShareService | |||
*/ | |||
WxMaRunService getRunService(); | |||
/** | |||
* 返回内容安全相关接口服务对象. | |||
* | |||
* @return WxMaShareService | |||
*/ | |||
WxMaSecCheckService getSecCheckService(); | |||
@@ -1,47 +1,33 @@ | |||
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.config.WxMaConfig; | |||
import com.google.common.base.Joiner; | |||
import com.google.gson.Gson; | |||
import com.google.gson.JsonParser; | |||
import lombok.extern.slf4j.Slf4j; | |||
import me.chanjar.weixin.common.WxType; | |||
import me.chanjar.weixin.common.bean.WxAccessToken; | |||
import me.chanjar.weixin.common.error.WxError; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.common.util.DataUtils; | |||
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.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.*; | |||
@@ -50,6 +36,7 @@ import static cn.binarywang.wx.miniapp.constant.WxMaConstants.ErrorCode.*; | |||
*/ | |||
@Slf4j | |||
public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpClient, HttpHost> { | |||
private static final JsonParser JSON_PARSER = new JsonParser(); | |||
private CloseableHttpClient httpClient; | |||
private HttpHost httpProxy; | |||
private WxMaConfig wxMaConfig; | |||
@@ -114,40 +101,68 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
@Override | |||
public String getAccessToken(boolean forceRefresh) throws WxErrorException { | |||
if (!this.getWxMaConfig().isAccessTokenExpired() && !forceRefresh) { | |||
return this.getWxMaConfig().getAccessToken(); | |||
} | |||
Lock lock = this.getWxMaConfig().getAccessTokenLock(); | |||
lock.lock(); | |||
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 { | |||
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 | |||
@@ -168,7 +183,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
try { | |||
return SHA1.gen(this.getWxMaConfig().getToken(), timestamp, nonce).equals(signature); | |||
} 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; | |||
} | |||
} | |||
@@ -246,7 +261,7 @@ public class WxMaServiceImpl implements WxMaService, RequestHttp<CloseableHttpCl | |||
if (error.getErrorCode() == ERR_40001 | |||
|| error.getErrorCode() == ERR_42001 | |||
|| error.getErrorCode() == ERR_40014) { | |||
// 强制设置wxMpConfigStorage它的access token过期了,这样在下一次请求里就会刷新access token | |||
// 强制设置WxMaConfig的access token过期了,这样在下一次请求里就会刷新access token | |||
this.getWxMaConfig().expireAccessToken(); | |||
if (this.getWxMaConfig().autoRefreshToken()) { | |||
return this.execute(executor, uri, data); | |||
@@ -41,20 +41,26 @@ public class WxMaKefuMessage implements Serializable { | |||
@Data | |||
@AllArgsConstructor | |||
public static class KfText { | |||
public static class KfText implements Serializable { | |||
private static final long serialVersionUID = 151122958720941270L; | |||
private String content; | |||
} | |||
@Data | |||
@AllArgsConstructor | |||
public static class KfImage { | |||
public static class KfImage implements Serializable { | |||
private static final long serialVersionUID = -5409342945117300782L; | |||
@SerializedName("media_id") | |||
private String mediaId; | |||
} | |||
@Data | |||
@Builder | |||
public static class KfLink { | |||
public static class KfLink implements Serializable { | |||
private static final long serialVersionUID = -6728776817556127413L; | |||
private String title; | |||
private String description; | |||
private String url; | |||
@@ -65,7 +71,9 @@ public class WxMaKefuMessage implements Serializable { | |||
@Data | |||
@Builder | |||
public static class KfMaPage { | |||
public static class KfMaPage implements Serializable { | |||
private static final long serialVersionUID = -5633492281871634466L; | |||
private String title; | |||
@SerializedName("pagepath") | |||
@@ -5,7 +5,7 @@ import lombok.NoArgsConstructor; | |||
/** | |||
* <pre> | |||
* | |||
* 参考文档 https://developers.weixin.qq.com/miniprogram/dev/api-backend/templateMessage.send.html | |||
* Created by Binary Wang on 2018/9/23. | |||
* </pre> | |||
* | |||
@@ -29,4 +29,5 @@ public class WxMaTemplateData { | |||
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> | |||
*/ | |||
@@ -75,16 +75,6 @@ public class WxMaTemplateMessage implements Serializable { | |||
*/ | |||
private List<WxMaTemplateData> data; | |||
/** | |||
* 模板内容字体的颜色,不填默认黑色. | |||
* <pre> | |||
* 参数:color | |||
* 是否必填: 否 | |||
* 描述: 模板内容字体的颜色,不填默认黑色 | |||
* </pre> | |||
*/ | |||
private String color; | |||
/** | |||
* 模板需要放大的关键词,不填则默认无放大. | |||
* <pre> | |||
@@ -104,5 +104,11 @@ public class WxMaUniformMessage implements Serializable { | |||
* 加入此字段是基于微信官方接口变化多端的考虑 | |||
*/ | |||
private boolean usePath = false; | |||
/** | |||
* 是否使用pagePath,否则使用pagepath. | |||
* 加入此字段是基于微信官方接口变化多端的考虑 | |||
*/ | |||
private boolean usePagePath = false; | |||
} | |||
} |
@@ -18,18 +18,24 @@ import java.io.Serializable; | |||
public class WxMaCodeAuditStatus implements Serializable { | |||
private static final long serialVersionUID = 4655119308692217268L; | |||
/** | |||
* 审核 ID | |||
* 审核 ID. | |||
*/ | |||
@SerializedName(value = "auditId", alternate = {"auditid"}) | |||
private Long auditId; | |||
/** | |||
* 审核状态,其中0为审核成功,1为审核失败,2为审核中 | |||
* 审核状态. | |||
* 其中0为审核成功,1为审核失败,2为审核中 | |||
*/ | |||
private Integer status; | |||
/** | |||
* 当status=1,审核被拒绝时,返回的拒绝原因 | |||
* 当status=1,审核被拒绝时,返回的拒绝原因. | |||
*/ | |||
private String reason; | |||
/** | |||
* 当status=1,审核被拒绝时,会返回审核失败的小程序截图示例。 xxx丨yyy丨zzz是media_id可通过获取永久素材接口 拉取截图内容). | |||
*/ | |||
@SerializedName(value = "screenshot") | |||
private String screenShot; | |||
public static WxMaCodeAuditStatus fromJson(String json) { | |||
return WxMaGsonBuilder.create().fromJson(json, WxMaCodeAuditStatus.class); | |||
@@ -1,19 +1,27 @@ | |||
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.spec.IvParameterSpec; | |||
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> | |||
*/ | |||
public class WxMaCryptUtils extends me.chanjar.weixin.common.util.crypto.WxCryptUtil { | |||
private static final Charset UTF_8 = StandardCharsets.UTF_8; | |||
public WxMaCryptUtils(WxMaConfig config) { | |||
this.appidOrCorpid = config.getAppid(); | |||
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 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.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) { | |||
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()); | |||
} | |||
if (message.getColor() != null) { | |||
messageJson.addProperty("color", message.getColor()); | |||
} | |||
if (message.getEmphasisKeyword() != null) { | |||
messageJson.addProperty("emphasis_keyword", message.getEmphasisKeyword()); | |||
} | |||
@@ -45,9 +41,6 @@ public class WxMaTemplateMessageGsonAdapter implements JsonSerializer<WxMaTempla | |||
for (WxMaTemplateData datum : message.getData()) { | |||
JsonObject dataJson = new JsonObject(); | |||
dataJson.addProperty("value", datum.getValue()); | |||
if (datum.getColor() != null) { | |||
dataJson.addProperty("color", datum.getColor()); | |||
} | |||
data.add(datum.getName(), dataJson); | |||
} | |||
@@ -36,6 +36,8 @@ public class WxMaUniformMessageGsonAdapter implements JsonSerializer<WxMaUniform | |||
miniProgramJson.addProperty("appid", miniProgram.getAppid()); | |||
if (miniProgram.isUsePath()) { | |||
miniProgramJson.addProperty("path", miniProgram.getPagePath()); | |||
} else if (miniProgram.isUsePagePath()) { | |||
miniProgramJson.addProperty("pagePath", miniProgram.getPagePath()); | |||
} else { | |||
miniProgramJson.addProperty("pagepath", miniProgram.getPagePath()); | |||
} | |||
@@ -79,9 +81,6 @@ public class WxMaUniformMessageGsonAdapter implements JsonSerializer<WxMaUniform | |||
for (WxMaTemplateData templateData : message.getData()) { | |||
JsonObject dataJson = new JsonObject(); | |||
dataJson.addProperty("value", templateData.getValue()); | |||
if (templateData.getColor() != null) { | |||
dataJson.addProperty("color", templateData.getColor()); | |||
} | |||
data.add(templateData.getName(), dataJson); | |||
} | |||
} | |||
@@ -10,9 +10,9 @@ import com.google.gson.JsonParseException; | |||
import me.chanjar.weixin.common.util.json.GsonHelper; | |||
import java.lang.reflect.Type; | |||
import java.util.Hashtable; | |||
import java.util.LinkedHashMap; | |||
import java.util.Map; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
/** | |||
* @author <a href="https://github.com/charmingoh">Charming</a> | |||
@@ -36,7 +36,7 @@ public class WxMaVisitDistributionGsonAdapter implements JsonDeserializer<WxMaVi | |||
} | |||
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) { | |||
JsonObject indexObject = indexElement.getAsJsonObject(); | |||
String index = GsonHelper.getString(indexObject, "index"); | |||
@@ -48,10 +48,10 @@ public class WxMaMsgServiceImplTest { | |||
.formId("FORMID") | |||
.page("index") | |||
.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()) | |||
.emphasisKeyword("keyword1.DATA") | |||
.build(); | |||
@@ -1,7 +1,5 @@ | |||
package cn.binarywang.wx.miniapp.api.impl; | |||
import java.io.File; | |||
import org.apache.commons.lang3.StringUtils; | |||
import org.testng.annotations.*; | |||
@@ -11,6 +9,7 @@ import cn.binarywang.wx.miniapp.test.ApiTestModule; | |||
import com.google.inject.Inject; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.testng.Assert.*; | |||
/** | |||
@@ -33,4 +32,9 @@ public class WxMaServiceImplTest { | |||
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; | |||
import org.testng.annotations.*; | |||
import cn.binarywang.wx.miniapp.bean.WxMaTemplateData; | |||
import cn.binarywang.wx.miniapp.bean.WxMaUniformMessage; | |||
import com.google.gson.JsonParser; | |||
import org.testng.annotations.Test; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
@@ -26,7 +25,7 @@ public class WxMaUniformMessageGsonAdapterTest { | |||
.appid("APPID") | |||
.templateId("TEMPLATE_ID") | |||
.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(); | |||
message.addData(new WxMaTemplateData("first", "恭喜你购买成功!", "#173177")) | |||
.addData(new WxMaTemplateData("keyword1", "巧克力", "#173177")) | |||
@@ -72,7 +71,7 @@ public class WxMaUniformMessageGsonAdapterTest { | |||
@Test | |||
public void testSerialize_ma() { | |||
WxMaUniformMessage message = WxMaUniformMessage.builder() | |||
WxMaUniformMessage message = WxMaUniformMessage.builder() | |||
.isMpTemplateMsg(false) | |||
.toUser("OPENID") | |||
.page("page/page/index") | |||
@@ -7,10 +7,11 @@ | |||
<parent> | |||
<groupId>com.github.binarywang</groupId> | |||
<artifactId>wx-java</artifactId> | |||
<version>3.3.0</version> | |||
<version>3.4.0</version> | |||
</parent> | |||
<artifactId>weixin-java-mp</artifactId> | |||
<name>WxJava - MP</name> | |||
<name>WxJava - MP Java SDK</name> | |||
<description>微信公众号Java SDK</description> | |||
<dependencies> | |||
@@ -1,10 +1,10 @@ | |||
package me.chanjar.weixin.mp.api; | |||
import java.io.File; | |||
import me.chanjar.weixin.common.error.WxErrorException; | |||
import me.chanjar.weixin.mp.enums.AiLangType; | |||
import java.io.File; | |||
/** | |||
* <pre> | |||
* 微信AI开放接口(语音识别,微信翻译). | |||
@@ -15,24 +15,15 @@ import java.io.File; | |||
* @author <a href="https://github.com/binarywang">Binary Wang</a> | |||
*/ | |||
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_QUERY_RESULT_URL = "http://api.weixin.qq.com/cgi-bin/media/voice/queryrecoresultfortext"; | |||
/** | |||
* <pre> | |||
* 提交语音. | |||
* 接口调用请求说明 | |||
* | |||
* http请求方式: POST | |||
* 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> | |||
* | |||
* @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 | |||
* 请注意,添加完文件之后10s内调用这个接口 | |||
* | |||
* 参数说明 | |||
* | |||
* 参数 是否必须 说明 | |||
* access_token 是 接口调用凭证 | |||
* voice_id 是 语音唯一标识 | |||
* lang 否 语言,zh_CN 或 en_US,默认中文 | |||
* </pre> | |||
* | |||
* @param lang 语言,zh_CN 或 en_US,默认中文 | |||
@@ -80,18 +64,12 @@ public interface WxMpAiOpenService { | |||
* | |||
* http请求方式: POST | |||
* 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> | |||
* | |||
* @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; | |||
} |
@@ -2,10 +2,8 @@ package me.chanjar.weixin.mp.api; | |||
import me.chanjar.weixin.common.bean.WxCardApiSignature; | |||
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 | |||
*/ | |||
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_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"; | |||
@@ -28,6 +27,11 @@ public interface WxMpCardService { | |||
*/ | |||
String CARD_CODE_UNAVAILABLE = "https://api.weixin.qq.com/card/code/unavailable"; | |||
/** | |||
* 卡券删除 | |||
*/ | |||
String CARD_DELETE = "https://api.weixin.qq.com/card/delete"; | |||
/** | |||
* 得到WxMpService | |||
*/ | |||
@@ -81,8 +85,8 @@ public interface WxMpCardService { | |||
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 code 单张卡券的唯一标准 | |||
* @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同 | |||
@@ -142,6 +146,14 @@ public interface WxMpCardService { | |||
*/ | |||
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; | |||
/** | |||
* 删除卡券接口 | |||
* @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.MemberCardUpdateRequest; | |||
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 | |||
*/ | |||
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_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"; | |||
/** | |||
* 会员卡激活之微信开卡接口(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"; | |||
/** | |||
* 跳转型会员卡开卡字段. | |||
* 获取用户提交资料(wx_activate=true情况调用),开发者根据activate_ticket获取到用户填写的信息 | |||
*/ | |||
String MEMBER_CARD_ACTIVATE_TEMP_INFO = "https://api.weixin.qq.com/card/membercard/activatetempinfo/get"; | |||
/** | |||
* 得到WxMpService. | |||
* | |||
* @return WxMpService | |||
*/ | |||
WxMpService getWxMpService(); | |||
/** | |||
* 会员卡创建接口. | |||
* | |||
* @param createJson 会员卡json字符串 | |||
* @return 返回json字符串 | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
WxMpCardCreateResult createMemberCard(String createJson) throws WxErrorException; | |||
/** | |||
* 会员卡创建接口. | |||
* 会员卡创建接口 | |||
* | |||
* @param createMessageMessage 会员卡创建对象 | |||
* @return 会员卡信息的结果对象 | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
WxMpCardCreateResult createMemberCard(WxMpMemberCardCreateMessage createMessageMessage) throws WxErrorException; | |||
@@ -61,7 +71,7 @@ public interface WxMpMemberCardService { | |||
* 会员卡激活接口. | |||
* | |||
* @param activatedMessage 激活所需参数 | |||
* @return 返回json字符串 | |||
* @return 会员卡激活后的json字符串 | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
String activateMemberCard(WxMpMemberCardActivatedMessage activatedMessage) throws WxErrorException; | |||
@@ -91,16 +101,40 @@ public interface WxMpMemberCardService { | |||
/** | |||
* 设置会员卡激活的字段(会员卡设置:wx_activate=true 时需要). | |||
* | |||
* @param userFormRequest 会员卡激活字段对象 | |||
* @return 会员卡激活后结果对象 | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
MemberCardActivateUserFormResult setActivateUserForm(MemberCardActivateUserFormRequest userFormRequest) throws WxErrorException; | |||
/** | |||
* 获取会员卡开卡插件参数(跳转型开卡组件需要参数). | |||
* | |||
* @param cardId 会员卡的CardId,微信分配 | |||
* @param outStr 会员卡设置商户的渠道 | |||
* @return 会员卡开卡插件参数结果对象 | |||
* @throws WxErrorException 接口调用失败抛出的异常 | |||
*/ | |||
ActivatePluginParam getActivatePluginParam(String cardId, String outStr) throws WxErrorException; | |||
/** | |||
* 更新会员卡信息. | |||
* | |||
* @param memberCardUpdateRequest 会员卡更新对象 | |||
* @return 会员卡更新后结果对象 | |||
* @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.RequestExecutor; | |||
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.result.WxMpCurrentAutoReplyInfo; | |||
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.enums.TicketType; | |||
import java.util.Map; | |||
/** | |||
* 微信公众号API的Service. | |||
* | |||
@@ -302,10 +305,50 @@ public interface WxMpService { | |||
WxMpConfigStorage getWxMpConfigStorage(); | |||
/** | |||
* 注入 {@link WxMpConfigStorage} 的实现. | |||
* 设置 {@link WxMpConfigStorage} 的实现. 兼容老版本 | |||
*/ | |||
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(); | |||
/** | |||
* 返回营销相关接口方法的实现类对象,以方便调用其各个接口. | |||
* | |||
* @return WxMpMarketingService | |||
*/ | |||
WxMpMarketingService getMarketingService(); | |||
/** | |||
* 初始化http请求对象. | |||
*/ | |||
@@ -473,4 +523,6 @@ public interface WxMpService { | |||
void setMassMessageService(WxMpMassMessageService massMessageService); | |||
void setAiOpenService(WxMpAiOpenService aiOpenService); | |||
void setMarketingService(WxMpMarketingService marketingService); | |||
} |
@@ -1,12 +1,7 @@ | |||
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.JsonElement; | |||
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.RandomUtils; | |||
import me.chanjar.weixin.common.util.crypto.SHA1; | |||
import me.chanjar.weixin.common.util.http.RequestExecutor; | |||
import me.chanjar.weixin.common.util.http.RequestHttp; | |||
import me.chanjar.weixin.common.util.http.SimpleGetRequestExecutor; | |||
import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor; | |||
import me.chanjar.weixin.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.result.WxMpCurrentAutoReplyInfo; | |||
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken; | |||
import me.chanjar.weixin.mp.bean.result.WxMpSemanticQueryResult; | |||
import me.chanjar.weixin.mp.bean.result.WxMpUser; | |||
import me.chanjar.weixin.mp.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 WxSessionManager sessionManager = new StandardSessionManager(); | |||
protected WxMpConfigStorage wxMpConfigStorage; | |||
private WxMpKefuService kefuService = new WxMpKefuServiceImpl(this); | |||
private WxMpMaterialService materialService = new WxMpMaterialServiceImpl(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 WxMpAiOpenService aiOpenService = new WxMpAiOpenServiceImpl(this); | |||
private WxMpWifiService wifiService = new WxMpWifiServiceImpl(this); | |||
private WxMpMarketingService marketingService = new WxMpMarketingServiceImpl(this); | |||
private Map<String, WxMpConfigStorage> configStorageMap; | |||
private int retrySleepMillis = 1000; | |||
private int maxRetryTimes = 5; | |||
@Override | |||
public boolean checkSignature(String timestamp, String nonce, String signature) { | |||
try { | |||
@@ -352,15 +333,70 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH | |||
@Override | |||
public WxMpConfigStorage getWxMpConfigStorage() { | |||
return this.wxMpConfigStorage; | |||
if (this.configStorageMap.size() == 1) { | |||
// 只有一个公众号,直接返回其配置即可 | |||
return this.configStorageMap.values().iterator().next(); | |||
} | |||
return this.configStorageMap.get(WxMpConfigStorageHolder.get()); | |||
} | |||
@Override | |||
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(); | |||
} | |||
@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 | |||
public void setRetrySleepMillis(int retrySleepMillis) { | |||
this.retrySleepMillis = retrySleepMillis; | |||
@@ -545,4 +581,14 @@ public abstract class BaseWxMpServiceImpl<H, P> implements WxMpService, RequestH | |||
public WxMpWifiService getWifiService() { | |||
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; | |||
import com.google.gson.JsonObject; | |||
import java.io.File; | |||
import com.google.gson.JsonParser; | |||
import me.chanjar.weixin.common.WxType; | |||
import me.chanjar.weixin.common.error.WxError; | |||
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.WxMpService; | |||
import me.chanjar.weixin.mp.enums.AiLangType; | |||
import me.chanjar.weixin.mp.util.requestexecuter.voice.VoiceUploadRequestExecutor; | |||
import java.io.File; | |||
/** | |||
* <pre> | |||
* 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> | |||
*/ | |||
public class WxMpAiOpenServiceImpl implements WxMpAiOpenService { | |||
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; | |||
public WxMpAiOpenServiceImpl(WxMpService wxMpService) { | |||
@@ -48,14 +45,14 @@ public class WxMpAiOpenServiceImpl implements WxMpAiOpenService { | |||
@Override | |||
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 | |||
@@ -64,13 +61,13 @@ public class WxMpAiOpenServiceImpl implements WxMpAiOpenService { | |||
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())); | |||
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; | |||
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 me.chanjar.weixin.common.bean.WxCardApiSignature; | |||
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.mp.api.WxMpCardService; | |||
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.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. | |||
@@ -150,14 +141,6 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||
return jsonPrimitive.getAsString(); | |||
} | |||
/** | |||
* 卡券Code查询. | |||
* | |||
* @param cardId 卡券ID代表一类卡券 | |||
* @param code 单张卡券的唯一标准 | |||
* @param checkConsume 是否校验code核销状态,填入true和false时的code异常状态返回数据不同 | |||
* @return WxMpCardResult对象 | |||
*/ | |||
@Override | |||
public WxMpCardResult queryCardCode(String cardId, String code, boolean checkConsume) throws WxErrorException { | |||
JsonObject param = new JsonObject(); | |||
@@ -265,6 +248,13 @@ public class WxMpCardServiceImpl implements WxMpCardService { | |||
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); | |||
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.Map; | |||
import me.chanjar.weixin.mp.bean.membercard.*; | |||
import org.apache.commons.lang3.StringUtils; | |||
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.CardColor; | |||
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; | |||
/** | |||
@@ -51,7 +45,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||
private static final Gson GSON = WxMpGsonBuilder.create(); | |||
WxMpMemberCardServiceImpl(WxMpService wxMpService) { | |||
public WxMpMemberCardServiceImpl(WxMpService wxMpService) { | |||
this.wxMpService = wxMpService; | |||
} | |||
@@ -76,7 +70,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||
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); | |||
} | |||
@@ -249,7 +243,7 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||
@Override | |||
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); | |||
} | |||
@@ -284,6 +278,15 @@ public class WxMpMemberCardServiceImpl implements WxMpMemberCardService { | |||
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) { | |||
String strAllParam = null; | |||
String[] arrSplit; | |||