mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2026-01-24 05:42:06 +08:00
🆕 #3703 【企业微信】第三方应用基础接口实现
This commit is contained in:
15
pom.xml
15
pom.xml
@@ -468,6 +468,21 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>3.3.0</version> <!-- 建议使用最新版本 -->
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<!-- 绑定到 verify 阶段,在 install 之前执行 -->
|
||||||
|
<phase>verify</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>jar-no-fork</goal> <!-- 使用 jar-no-fork 效率更高 -->
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
<module>wx-java-open-spring-boot-starter</module>
|
<module>wx-java-open-spring-boot-starter</module>
|
||||||
<module>wx-java-qidian-spring-boot-starter</module>
|
<module>wx-java-qidian-spring-boot-starter</module>
|
||||||
<module>wx-java-cp-multi-spring-boot-starter</module>
|
<module>wx-java-cp-multi-spring-boot-starter</module>
|
||||||
|
<module>wx-java-cp-tp-multi-spring-boot-starter</module>
|
||||||
<module>wx-java-cp-spring-boot-starter</module>
|
<module>wx-java-cp-spring-boot-starter</module>
|
||||||
<module>wx-java-channel-spring-boot-starter</module>
|
<module>wx-java-channel-spring-boot-starter</module>
|
||||||
<module>wx-java-channel-multi-spring-boot-starter</module>
|
<module>wx-java-channel-multi-spring-boot-starter</module>
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
# wx-java-cp-multi-spring-boot-starter
|
||||||
|
|
||||||
|
企业微信多账号配置
|
||||||
|
|
||||||
|
- 实现多 WxCpService 初始化。
|
||||||
|
- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
|
||||||
|
- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
1. 引入依赖
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<artifactId>wx-java-cp-multi-spring-boot-starter</artifactId>
|
||||||
|
<version>${version}</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
2. 添加配置(application.properties)
|
||||||
|
```properties
|
||||||
|
# 应用 1 配置
|
||||||
|
wx.cp.corps.tenantId1.corp-id = @corp-id
|
||||||
|
wx.cp.corps.tenantId1.corp-secret = @corp-secret
|
||||||
|
## 选填
|
||||||
|
wx.cp.corps.tenantId1.agent-id = @agent-id
|
||||||
|
wx.cp.corps.tenantId1.token = @token
|
||||||
|
wx.cp.corps.tenantId1.aes-key = @aes-key
|
||||||
|
wx.cp.corps.tenantId1.msg-audit-priKey = @msg-audit-priKey
|
||||||
|
wx.cp.corps.tenantId1.msg-audit-lib-path = @msg-audit-lib-path
|
||||||
|
|
||||||
|
# 应用 2 配置
|
||||||
|
wx.cp.corps.tenantId2.corp-id = @corp-id
|
||||||
|
wx.cp.corps.tenantId2.corp-secret = @corp-secret
|
||||||
|
## 选填
|
||||||
|
wx.cp.corps.tenantId2.agent-id = @agent-id
|
||||||
|
wx.cp.corps.tenantId2.token = @token
|
||||||
|
wx.cp.corps.tenantId2.aes-key = @aes-key
|
||||||
|
wx.cp.corps.tenantId2.msg-audit-priKey = @msg-audit-priKey
|
||||||
|
wx.cp.corps.tenantId2.msg-audit-lib-path = @msg-audit-lib-path
|
||||||
|
|
||||||
|
# 公共配置
|
||||||
|
## ConfigStorage 配置(选填)
|
||||||
|
wx.cp.config-storage.type=memory # 配置类型: memory(默认), jedis, redisson, redistemplate
|
||||||
|
## http 客户端配置(选填)
|
||||||
|
## # http客户端类型: http_client(默认), ok_http, jodd_http
|
||||||
|
wx.cp.config-storage.http-client-type=http_client
|
||||||
|
wx.cp.config-storage.http-proxy-host=
|
||||||
|
wx.cp.config-storage.http-proxy-port=
|
||||||
|
wx.cp.config-storage.http-proxy-username=
|
||||||
|
wx.cp.config-storage.http-proxy-password=
|
||||||
|
## 最大重试次数,默认:5 次,如果小于 0,则为 0
|
||||||
|
wx.cp.config-storage.max-retry-times=5
|
||||||
|
## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000
|
||||||
|
wx.cp.config-storage.retry-sleep-millis=1000
|
||||||
|
```
|
||||||
|
3. 支持自动注入的类型: `WxCpMultiServices`
|
||||||
|
|
||||||
|
4. 使用样例
|
||||||
|
|
||||||
|
```java
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpService;
|
||||||
|
import me.chanjar.weixin.cp.api.WxCpUserService;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class DemoService {
|
||||||
|
@Autowired
|
||||||
|
private WxCpTpMultiServices wxCpTpMultiServices;
|
||||||
|
|
||||||
|
public void test() {
|
||||||
|
// 应用 1 的 WxCpService
|
||||||
|
WxCpService wxCpService1 = wxCpMultiServices.getWxCpService("tenantId1");
|
||||||
|
WxCpUserService userService1 = wxCpService1.getUserService();
|
||||||
|
userService1.getUserId("xxx");
|
||||||
|
// todo ...
|
||||||
|
|
||||||
|
// 应用 2 的 WxCpService
|
||||||
|
WxCpService wxCpService2 = wxCpMultiServices.getWxCpService("tenantId2");
|
||||||
|
WxCpUserService userService2 = wxCpService2.getUserService();
|
||||||
|
userService2.getUserId("xxx");
|
||||||
|
// todo ...
|
||||||
|
|
||||||
|
// 应用 3 的 WxCpService
|
||||||
|
WxCpService wxCpService3 = wxCpMultiServices.getWxCpService("tenantId3");
|
||||||
|
// 判断是否为空
|
||||||
|
if (wxCpService3 == null) {
|
||||||
|
// todo wxCpService3 为空,请先配置 tenantId3 企业微信应用参数
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WxCpUserService userService3 = wxCpService3.getUserService();
|
||||||
|
userService3.getUserId("xxx");
|
||||||
|
// todo ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?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-spring-boot-starters</artifactId>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<version>4.7.7.B</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>wx-java-cp-tp-multi-spring-boot-starter</artifactId>
|
||||||
|
<name>WxJava - Spring Boot Starter for WxCp::支持多账号配置</name>
|
||||||
|
<description>微信企业号开发的 Spring Boot Starter::支持多账号配置</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.binarywang</groupId>
|
||||||
|
<artifactId>weixin-java-cp</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>redis.clients</groupId>
|
||||||
|
<artifactId>jedis</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.redisson</groupId>
|
||||||
|
<artifactId>redisson</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.data</groupId>
|
||||||
|
<artifactId>spring-data-redis</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,16 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.autoconfigure;
|
||||||
|
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.configuration.WxCpTpMultiServicesAutoConfiguration;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信自动注册
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@Import(WxCpTpMultiServicesAutoConfiguration.class)
|
||||||
|
public class WxCpTpMultiAutoConfiguration {
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.configuration;
|
||||||
|
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInJedisTpConfiguration;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInMemoryTpConfiguration;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInRedisTemplateTpConfiguration;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.configuration.services.WxCpTpInRedissonTpConfiguration;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信平台相关服务自动注册
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableConfigurationProperties(WxCpTpMultiProperties.class)
|
||||||
|
@Import({
|
||||||
|
WxCpTpInJedisTpConfiguration.class,
|
||||||
|
WxCpTpInMemoryTpConfiguration.class,
|
||||||
|
WxCpTpInRedissonTpConfiguration.class,
|
||||||
|
WxCpTpInRedisTemplateTpConfiguration.class
|
||||||
|
})
|
||||||
|
public class WxCpTpMultiServicesAutoConfiguration {
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.configuration.services;
|
||||||
|
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpSingleProperties;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServicesImpl;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
|
||||||
|
import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceApacheHttpClientImpl;
|
||||||
|
import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceImpl;
|
||||||
|
import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceJoddHttpImpl;
|
||||||
|
import me.chanjar.weixin.cp.tp.service.impl.WxCpTpServiceOkHttpImpl;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WxCpConfigStorage 抽象配置类
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractWxCpTpConfiguration {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param wxCpTpMultiProperties 应用列表配置
|
||||||
|
* @param services 用于支持,应用启动之后,可以调用这个接口添加新服务对象。主要是配置是从数据库中读取的
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public WxCpTpMultiServices wxCpMultiServices(WxCpTpMultiProperties wxCpTpMultiProperties,WxCpTpMultiServices services) {
|
||||||
|
Map<String, WxCpTpSingleProperties> corps = wxCpTpMultiProperties.getCorps();
|
||||||
|
if (corps == null || corps.isEmpty()) {
|
||||||
|
log.warn("企业微信应用参数未配置,通过 WxCpMultiServices#getWxCpTpService(\"tenantId\")获取实例将返回空");
|
||||||
|
return new WxCpTpMultiServicesImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (services == null) {
|
||||||
|
services = new WxCpTpMultiServicesImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Map.Entry<String, WxCpTpSingleProperties>> entries = corps.entrySet();
|
||||||
|
for (Map.Entry<String, WxCpTpSingleProperties> entry : entries) {
|
||||||
|
String tenantId = entry.getKey();
|
||||||
|
WxCpTpSingleProperties wxCpTpSingleProperties = entry.getValue();
|
||||||
|
WxCpTpDefaultConfigImpl storage = this.wxCpTpConfigStorage(wxCpTpMultiProperties);
|
||||||
|
this.configCorp(storage, wxCpTpSingleProperties);
|
||||||
|
this.configHttp(storage, wxCpTpMultiProperties.getConfigStorage());
|
||||||
|
WxCpTpService wxCpTpService = this.wxCpTpService(storage, wxCpTpMultiProperties.getConfigStorage());
|
||||||
|
if (services.getWxCpTpService(tenantId) == null) {
|
||||||
|
// 不存在的才会添加到服务列表中
|
||||||
|
services.addWxCpTpService(tenantId, wxCpTpService);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置 WxCpDefaultConfigImpl
|
||||||
|
*
|
||||||
|
* @param wxCpTpMultiProperties 参数
|
||||||
|
* @return WxCpDefaultConfigImpl
|
||||||
|
*/
|
||||||
|
protected abstract WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties);
|
||||||
|
|
||||||
|
private WxCpTpService wxCpTpService(WxCpTpConfigStorage wxCpTpConfigStorage, WxCpTpMultiProperties.ConfigStorage storage) {
|
||||||
|
WxCpTpMultiProperties.HttpClientType httpClientType = storage.getHttpClientType();
|
||||||
|
WxCpTpService cpTpService;
|
||||||
|
switch (httpClientType) {
|
||||||
|
case OK_HTTP:
|
||||||
|
cpTpService = new WxCpTpServiceOkHttpImpl();
|
||||||
|
break;
|
||||||
|
case JODD_HTTP:
|
||||||
|
cpTpService = new WxCpTpServiceJoddHttpImpl();
|
||||||
|
break;
|
||||||
|
case HTTP_CLIENT:
|
||||||
|
cpTpService = new WxCpTpServiceApacheHttpClientImpl();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cpTpService = new WxCpTpServiceImpl();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
cpTpService.setWxCpTpConfigStorage(wxCpTpConfigStorage);
|
||||||
|
int maxRetryTimes = storage.getMaxRetryTimes();
|
||||||
|
if (maxRetryTimes < 0) {
|
||||||
|
maxRetryTimes = 0;
|
||||||
|
}
|
||||||
|
int retrySleepMillis = storage.getRetrySleepMillis();
|
||||||
|
if (retrySleepMillis < 0) {
|
||||||
|
retrySleepMillis = 1000;
|
||||||
|
}
|
||||||
|
cpTpService.setRetrySleepMillis(retrySleepMillis);
|
||||||
|
cpTpService.setMaxRetryTimes(maxRetryTimes);
|
||||||
|
return cpTpService;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configCorp(WxCpTpDefaultConfigImpl config, WxCpTpSingleProperties wxCpTpSingleProperties) {
|
||||||
|
String corpId = wxCpTpSingleProperties.getCorpId();
|
||||||
|
String providerSecret = wxCpTpSingleProperties.getProviderSecret();
|
||||||
|
String suiteId = wxCpTpSingleProperties.getSuiteId();
|
||||||
|
String token = wxCpTpSingleProperties.getToken();
|
||||||
|
String suiteSecret = wxCpTpSingleProperties.getSuiteSecret();
|
||||||
|
// 企业微信,私钥,会话存档路径
|
||||||
|
config.setCorpId(corpId);
|
||||||
|
config.setProviderSecret(providerSecret);
|
||||||
|
config.setEncodingAESKey(wxCpTpSingleProperties.getEncodingAESKey());
|
||||||
|
config.setSuiteId(suiteId);
|
||||||
|
config.setToken(token);
|
||||||
|
config.setSuiteSecret(suiteSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configHttp(WxCpTpDefaultConfigImpl config, WxCpTpMultiProperties.ConfigStorage storage) {
|
||||||
|
String httpProxyHost = storage.getHttpProxyHost();
|
||||||
|
Integer httpProxyPort = storage.getHttpProxyPort();
|
||||||
|
String httpProxyUsername = storage.getHttpProxyUsername();
|
||||||
|
String httpProxyPassword = storage.getHttpProxyPassword();
|
||||||
|
if (StringUtils.isNotBlank(httpProxyHost)) {
|
||||||
|
config.setHttpProxyHost(httpProxyHost);
|
||||||
|
if (httpProxyPort != null) {
|
||||||
|
config.setHttpProxyPort(httpProxyPort);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(httpProxyUsername)) {
|
||||||
|
config.setHttpProxyUsername(httpProxyUsername);
|
||||||
|
}
|
||||||
|
if (StringUtils.isNotBlank(httpProxyPassword)) {
|
||||||
|
config.setHttpProxyPassword(httpProxyPassword);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.configuration.services;
|
||||||
|
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiRedisProperties;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpJedisConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpTpJedisConfigImpl;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import redis.clients.jedis.JedisPool;
|
||||||
|
import redis.clients.jedis.JedisPoolConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动装配基于 jedis 策略配置
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis"
|
||||||
|
)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WxCpTpInJedisTpConfiguration extends AbstractWxCpTpConfiguration {
|
||||||
|
private final WxCpTpMultiProperties wxCpTpMultiProperties;
|
||||||
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WxCpTpMultiServices wxCpMultiServices() {
|
||||||
|
return this.wxCpMultiServices(wxCpTpMultiProperties,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) {
|
||||||
|
return this.configRedis(wxCpTpMultiProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WxCpTpDefaultConfigImpl configRedis(WxCpTpMultiProperties wxCpTpMultiProperties) {
|
||||||
|
WxCpTpMultiRedisProperties wxCpTpMultiRedisProperties = wxCpTpMultiProperties.getConfigStorage().getRedis();
|
||||||
|
JedisPool jedisPool;
|
||||||
|
if (wxCpTpMultiRedisProperties != null && StringUtils.isNotEmpty(wxCpTpMultiRedisProperties.getHost())) {
|
||||||
|
jedisPool = getJedisPool(wxCpTpMultiProperties);
|
||||||
|
} else {
|
||||||
|
jedisPool = applicationContext.getBean(JedisPool.class);
|
||||||
|
}
|
||||||
|
return new WxCpTpJedisConfigImpl(jedisPool, wxCpTpMultiProperties.getConfigStorage().getKeyPrefix());
|
||||||
|
}
|
||||||
|
|
||||||
|
private JedisPool getJedisPool(WxCpTpMultiProperties wxCpTpMultiProperties) {
|
||||||
|
WxCpTpMultiProperties.ConfigStorage storage = wxCpTpMultiProperties.getConfigStorage();
|
||||||
|
WxCpTpMultiRedisProperties redis = storage.getRedis();
|
||||||
|
|
||||||
|
JedisPoolConfig config = new JedisPoolConfig();
|
||||||
|
if (redis.getMaxActive() != null) {
|
||||||
|
config.setMaxTotal(redis.getMaxActive());
|
||||||
|
}
|
||||||
|
if (redis.getMaxIdle() != null) {
|
||||||
|
config.setMaxIdle(redis.getMaxIdle());
|
||||||
|
}
|
||||||
|
if (redis.getMaxWaitMillis() != null) {
|
||||||
|
config.setMaxWaitMillis(redis.getMaxWaitMillis());
|
||||||
|
}
|
||||||
|
if (redis.getMinIdle() != null) {
|
||||||
|
config.setMinIdle(redis.getMinIdle());
|
||||||
|
}
|
||||||
|
config.setTestOnBorrow(true);
|
||||||
|
config.setTestWhileIdle(true);
|
||||||
|
|
||||||
|
return new JedisPool(config, redis.getHost(), redis.getPort(),
|
||||||
|
redis.getTimeout(), redis.getPassword(), redis.getDatabase());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.configuration.services;
|
||||||
|
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动装配基于内存策略配置
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "memory", matchIfMissing = true
|
||||||
|
)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WxCpTpInMemoryTpConfiguration extends AbstractWxCpTpConfiguration {
|
||||||
|
private final WxCpTpMultiProperties wxCpTpMultiProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WxCpTpMultiServices wxCpMultiServices() {
|
||||||
|
return this.wxCpMultiServices(wxCpTpMultiProperties,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) {
|
||||||
|
return this.configInMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
private WxCpTpDefaultConfigImpl configInMemory() {
|
||||||
|
return new WxCpTpDefaultConfigImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.configuration.services;
|
||||||
|
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpRedisTemplateConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpTpRedisTemplateConfigImpl;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动装配基于 redisTemplate 策略配置
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate"
|
||||||
|
)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WxCpTpInRedisTemplateTpConfiguration extends AbstractWxCpTpConfiguration {
|
||||||
|
private final WxCpTpMultiProperties wxCpTpMultiProperties;
|
||||||
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WxCpTpMultiServices wxCpMultiServices() {
|
||||||
|
return this.wxCpMultiServices(wxCpTpMultiProperties,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) {
|
||||||
|
return this.configRedisTemplate(wxCpTpMultiProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WxCpTpDefaultConfigImpl configRedisTemplate(WxCpTpMultiProperties wxCpTpMultiProperties) {
|
||||||
|
StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
|
||||||
|
return new WxCpTpRedisTemplateConfigImpl(redisTemplate, wxCpTpMultiProperties.getConfigStorage().getKeyPrefix());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.configuration.services;
|
||||||
|
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiProperties;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpTpMultiRedisProperties;
|
||||||
|
import com.binarywang.spring.starter.wxjava.cp.service.WxCpTpMultiServices;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.AbstractWxCpTpInRedisConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.redisson.Redisson;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
import org.redisson.config.Config;
|
||||||
|
import org.redisson.config.TransportMode;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动装配基于 redisson 策略配置
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
prefix = WxCpTpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson"
|
||||||
|
)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WxCpTpInRedissonTpConfiguration extends AbstractWxCpTpConfiguration {
|
||||||
|
private final WxCpTpMultiProperties wxCpTpMultiProperties;
|
||||||
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public WxCpTpMultiServices wxCpMultiServices() {
|
||||||
|
return this.wxCpMultiServices(wxCpTpMultiProperties,null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected WxCpTpDefaultConfigImpl wxCpTpConfigStorage(WxCpTpMultiProperties wxCpTpMultiProperties) {
|
||||||
|
return this.configRedisson(wxCpTpMultiProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
private WxCpTpDefaultConfigImpl configRedisson(WxCpTpMultiProperties wxCpTpMultiProperties) {
|
||||||
|
WxCpTpMultiRedisProperties redisProperties = wxCpTpMultiProperties.getConfigStorage().getRedis();
|
||||||
|
RedissonClient redissonClient;
|
||||||
|
if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) {
|
||||||
|
redissonClient = getRedissonClient(wxCpTpMultiProperties);
|
||||||
|
} else {
|
||||||
|
redissonClient = applicationContext.getBean(RedissonClient.class);
|
||||||
|
}
|
||||||
|
return new WxCpTpRedissonConfigImpl(redissonClient, wxCpTpMultiProperties.getConfigStorage().getKeyPrefix());
|
||||||
|
}
|
||||||
|
|
||||||
|
private RedissonClient getRedissonClient(WxCpTpMultiProperties wxCpTpMultiProperties) {
|
||||||
|
WxCpTpMultiProperties.ConfigStorage storage = wxCpTpMultiProperties.getConfigStorage();
|
||||||
|
WxCpTpMultiRedisProperties redis = storage.getRedis();
|
||||||
|
|
||||||
|
Config config = new Config();
|
||||||
|
config.useSingleServer()
|
||||||
|
.setAddress("redis://" + redis.getHost() + ":" + redis.getPort())
|
||||||
|
.setDatabase(redis.getDatabase())
|
||||||
|
.setPassword(redis.getPassword());
|
||||||
|
config.setTransportMode(TransportMode.NIO);
|
||||||
|
return Redisson.create(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.properties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信多企业接入相关配置属性
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@ConfigurationProperties(prefix = WxCpTpMultiProperties.PREFIX)
|
||||||
|
public class WxCpTpMultiProperties implements Serializable {
|
||||||
|
private static final long serialVersionUID = -1569510477055668503L;
|
||||||
|
public static final String PREFIX = "wx.cp.tp";
|
||||||
|
|
||||||
|
private Map<String, WxCpTpSingleProperties> corps = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置存储策略,默认内存
|
||||||
|
*/
|
||||||
|
private ConfigStorage configStorage = new ConfigStorage();
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public static class ConfigStorage implements Serializable {
|
||||||
|
private static final long serialVersionUID = 4815731027000065434L;
|
||||||
|
/**
|
||||||
|
* 存储类型
|
||||||
|
*/
|
||||||
|
private StorageType type = StorageType.memory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定key前缀
|
||||||
|
*/
|
||||||
|
private String keyPrefix = "wx:cp:tp";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redis连接配置
|
||||||
|
*/
|
||||||
|
@NestedConfigurationProperty
|
||||||
|
private WxCpTpMultiRedisProperties redis = new WxCpTpMultiRedisProperties();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http客户端类型.
|
||||||
|
*/
|
||||||
|
private HttpClientType httpClientType = HttpClientType.HTTP_CLIENT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http代理主机
|
||||||
|
*/
|
||||||
|
private String httpProxyHost;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http代理端口
|
||||||
|
*/
|
||||||
|
private Integer httpProxyPort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http代理用户名
|
||||||
|
*/
|
||||||
|
private String httpProxyUsername;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http代理密码
|
||||||
|
*/
|
||||||
|
private String httpProxyPassword;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http 请求最大重试次数
|
||||||
|
* <pre>
|
||||||
|
* {@link me.chanjar.weixin.cp.api.WxCpService#setMaxRetryTimes(int)}
|
||||||
|
* {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setMaxRetryTimes(int)}
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
private int maxRetryTimes = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http 请求重试间隔
|
||||||
|
* <pre>
|
||||||
|
* {@link me.chanjar.weixin.cp.api.WxCpService#setRetrySleepMillis(int)}
|
||||||
|
* {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setRetrySleepMillis(int)}
|
||||||
|
* </pre>
|
||||||
|
*/
|
||||||
|
private int retrySleepMillis = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum StorageType {
|
||||||
|
/**
|
||||||
|
* 内存
|
||||||
|
*/
|
||||||
|
memory,
|
||||||
|
/**
|
||||||
|
* jedis
|
||||||
|
*/
|
||||||
|
jedis,
|
||||||
|
/**
|
||||||
|
* redisson
|
||||||
|
*/
|
||||||
|
redisson,
|
||||||
|
/**
|
||||||
|
* redistemplate
|
||||||
|
*/
|
||||||
|
redistemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum HttpClientType {
|
||||||
|
/**
|
||||||
|
* HttpClient
|
||||||
|
*/
|
||||||
|
HTTP_CLIENT,
|
||||||
|
/**
|
||||||
|
* OkHttp
|
||||||
|
*/
|
||||||
|
OK_HTTP,
|
||||||
|
/**
|
||||||
|
* JoddHttp
|
||||||
|
*/
|
||||||
|
JODD_HTTP
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.properties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redis配置.
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class WxCpTpMultiRedisProperties implements Serializable {
|
||||||
|
private static final long serialVersionUID = -5924815351660074401L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主机地址.
|
||||||
|
*/
|
||||||
|
private String host;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 端口号.
|
||||||
|
*/
|
||||||
|
private int port = 6379;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 密码.
|
||||||
|
*/
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超时.
|
||||||
|
*/
|
||||||
|
private int timeout = 2000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据库.
|
||||||
|
*/
|
||||||
|
private int database = 0;
|
||||||
|
|
||||||
|
private Integer maxActive;
|
||||||
|
private Integer maxIdle;
|
||||||
|
private Integer maxWaitMillis;
|
||||||
|
private Integer minIdle;
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.properties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信企业相关配置属性
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class WxCpTpSingleProperties implements Serializable {
|
||||||
|
private static final long serialVersionUID = -7502823825007859418L;
|
||||||
|
/**
|
||||||
|
* 微信企业号 corpId
|
||||||
|
*/
|
||||||
|
private String corpId;
|
||||||
|
/**
|
||||||
|
* 微信企业号 服务商 providerSecret
|
||||||
|
*/
|
||||||
|
private String providerSecret;
|
||||||
|
/**
|
||||||
|
* 微信企业号应用 token
|
||||||
|
*/
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
private String encodingAESKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信企业号 第三方 应用 ID
|
||||||
|
*/
|
||||||
|
private String suiteId;
|
||||||
|
/**
|
||||||
|
* 微信企业号应用
|
||||||
|
*/
|
||||||
|
private String suiteSecret;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.service;
|
||||||
|
|
||||||
|
|
||||||
|
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信 {@link WxCpTpService} 所有实例存放类.
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
public interface WxCpTpMultiServices {
|
||||||
|
/**
|
||||||
|
* 通过租户 Id 获取 WxCpTpService
|
||||||
|
*
|
||||||
|
* @param tenantId 租户 Id
|
||||||
|
* @return WxCpTpService
|
||||||
|
*/
|
||||||
|
WxCpTpService getWxCpTpService(String tenantId);
|
||||||
|
|
||||||
|
void addWxCpTpService(String tenantId, WxCpTpService wxCpService);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据租户 Id,从列表中移除一个 WxCpTpService 实例
|
||||||
|
*
|
||||||
|
* @param tenantId 租户 Id
|
||||||
|
*/
|
||||||
|
void removeWxCpTpService(String tenantId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.cp.service;
|
||||||
|
|
||||||
|
|
||||||
|
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信 {@link WxCpTpMultiServices} 默认实现
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/10/16
|
||||||
|
*/
|
||||||
|
public class WxCpTpMultiServicesImpl implements WxCpTpMultiServices {
|
||||||
|
private final Map<String, WxCpTpService> services = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过租户 Id 获取 WxCpTpService
|
||||||
|
*
|
||||||
|
* @param tenantId 租户 Id
|
||||||
|
* @return WxCpTpService
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public WxCpTpService getWxCpTpService(String tenantId) {
|
||||||
|
return this.services.get(tenantId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据租户 Id,添加一个 WxCpTpService 到列表
|
||||||
|
*
|
||||||
|
* @param tenantId 租户 Id
|
||||||
|
* @param wxCpService WxCpTpService 实例
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void addWxCpTpService(String tenantId, WxCpTpService wxCpService) {
|
||||||
|
this.services.put(tenantId, wxCpService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeWxCpTpService(String tenantId) {
|
||||||
|
this.services.remove(tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||||
|
com.binarywang.spring.starter.wxjava.cp.autoconfigure.WxCpTpMultiAutoConfiguration
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
com.binarywang.spring.starter.wxjava.cp.autoconfigure.WxCpTpMultiAutoConfiguration
|
||||||
@@ -793,4 +793,6 @@ public class WxCpTpXmlMessage implements Serializable {
|
|||||||
log.debug("解密后的原始xml消息内容:{}", plainText);
|
log.debug("解密后的原始xml消息内容:{}", plainText);
|
||||||
return fromXml(plainText);
|
return fromXml(plainText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ public interface WxCpTpConfigStorage {
|
|||||||
* @return the aes key
|
* @return the aes key
|
||||||
*/
|
*/
|
||||||
//第三方应用的EncodingAESKey,用来检查签名
|
//第三方应用的EncodingAESKey,用来检查签名
|
||||||
String getAesKey();
|
String getEncodingAESKey();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 企微服务商企业ID & 企业secret
|
* 企微服务商企业ID & 企业secret
|
||||||
|
|||||||
@@ -0,0 +1,431 @@
|
|||||||
|
package me.chanjar.weixin.cp.config.impl;
|
||||||
|
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Setter;
|
||||||
|
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||||
|
import me.chanjar.weixin.common.redis.RedissonWxRedisOps;
|
||||||
|
import me.chanjar.weixin.common.redis.WxRedisOps;
|
||||||
|
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
||||||
|
import me.chanjar.weixin.cp.bean.WxCpProviderToken;
|
||||||
|
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.redisson.api.RedissonClient;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企业微信各种固定、授权配置的Redisson存储实现
|
||||||
|
*/
|
||||||
|
public abstract class AbstractWxCpTpInRedisConfigImpl extends WxCpTpDefaultConfigImpl implements Serializable {
|
||||||
|
private static final long serialVersionUID = -5385639031981770319L;
|
||||||
|
|
||||||
|
public AbstractWxCpTpInRedisConfigImpl(@NonNull WxRedisOps wxRedisOps) {
|
||||||
|
this(wxRedisOps, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractWxCpTpInRedisConfigImpl(@NonNull WxRedisOps wxRedisOps, String keyPrefix) {
|
||||||
|
this.wxRedisOps = wxRedisOps;
|
||||||
|
this.keyPrefix = keyPrefix;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* The constant LOCK_KEY.
|
||||||
|
*/
|
||||||
|
// lock key
|
||||||
|
protected static final String LOCK_KEY = "wechat_tp_lock:";
|
||||||
|
/**
|
||||||
|
* The constant LOCKER_PROVIDER_ACCESS_TOKEN.
|
||||||
|
*/
|
||||||
|
protected static final String LOCKER_PROVIDER_ACCESS_TOKEN = "providerAccessTokenLock";
|
||||||
|
/**
|
||||||
|
* The constant LOCKER_SUITE_ACCESS_TOKEN.
|
||||||
|
*/
|
||||||
|
protected static final String LOCKER_SUITE_ACCESS_TOKEN = "suiteAccessTokenLock";
|
||||||
|
/**
|
||||||
|
* The constant LOCKER_ACCESS_TOKEN.
|
||||||
|
*/
|
||||||
|
protected static final String LOCKER_ACCESS_TOKEN = "accessTokenLock";
|
||||||
|
/**
|
||||||
|
* The constant LOCKER_CORP_JSAPI_TICKET.
|
||||||
|
*/
|
||||||
|
protected static final String LOCKER_CORP_JSAPI_TICKET = "corpJsapiTicketLock";
|
||||||
|
/**
|
||||||
|
* The constant LOCKER_SUITE_JSAPI_TICKET.
|
||||||
|
*/
|
||||||
|
protected static final String LOCKER_SUITE_JSAPI_TICKET = "suiteJsapiTicketLock";
|
||||||
|
@NonNull
|
||||||
|
private final WxRedisOps wxRedisOps;
|
||||||
|
private final String suiteAccessTokenKey = ":suiteAccessTokenKey:";
|
||||||
|
private final String suiteTicketKey = ":suiteTicketKey:";
|
||||||
|
private final String accessTokenKey = ":accessTokenKey:";
|
||||||
|
private final String authCorpJsApiTicketKey = ":authCorpJsApiTicketKey:";
|
||||||
|
private final String authSuiteJsApiTicketKey = ":authSuiteJsApiTicketKey:";
|
||||||
|
private final String providerTokenKey = ":providerTokenKey:";
|
||||||
|
/**
|
||||||
|
* redis里面key的统一前缀
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private String keyPrefix = "";
|
||||||
|
private volatile String baseApiUrl;
|
||||||
|
private volatile String httpProxyHost;
|
||||||
|
private volatile int httpProxyPort;
|
||||||
|
private volatile String httpProxyUsername;
|
||||||
|
private volatile String httpProxyPassword;
|
||||||
|
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
|
||||||
|
private volatile File tmpDirFile;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBaseApiUrl(String baseUrl) {
|
||||||
|
this.baseApiUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getApiUrl(String path) {
|
||||||
|
if (baseApiUrl == null) {
|
||||||
|
baseApiUrl = "https://qyapi.weixin.qq.com";
|
||||||
|
}
|
||||||
|
return baseApiUrl + path;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方应用的suite access token相关
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSuiteAccessToken() {
|
||||||
|
return wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxAccessToken getSuiteAccessTokenEntity() {
|
||||||
|
String suiteAccessToken = wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
|
||||||
|
Long expireIn = wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey));
|
||||||
|
if (StringUtils.isBlank(suiteAccessToken) || expireIn == null || expireIn == 0 || expireIn == -2) {
|
||||||
|
return new WxAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
WxAccessToken suiteAccessTokenEntity = new WxAccessToken();
|
||||||
|
suiteAccessTokenEntity.setAccessToken(suiteAccessToken);
|
||||||
|
suiteAccessTokenEntity.setExpiresIn(Math.max(Math.toIntExact(expireIn), 0));
|
||||||
|
return suiteAccessTokenEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuiteAccessTokenExpired() {
|
||||||
|
//remain time to live in seconds, or key not exist
|
||||||
|
return wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireSuiteAccessToken() {
|
||||||
|
wxRedisOps.expire(keyWithPrefix(suiteAccessTokenKey), 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSuiteAccessToken(WxAccessToken suiteAccessToken) {
|
||||||
|
updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) {
|
||||||
|
wxRedisOps.setValue(keyWithPrefix(suiteAccessTokenKey), suiteAccessToken, expiresInSeconds, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方应用的suite ticket相关
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSuiteTicket() {
|
||||||
|
return wxRedisOps.getValue(keyWithPrefix(suiteTicketKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSuiteTicketExpired() {
|
||||||
|
//remain time to live in seconds, or key not exist
|
||||||
|
return wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireSuiteTicket() {
|
||||||
|
wxRedisOps.expire(keyWithPrefix(suiteTicketKey), 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateSuiteTicket(String suiteTicket, int expiresInSeconds) {
|
||||||
|
wxRedisOps.setValue(keyWithPrefix(suiteTicketKey), suiteTicket, expiresInSeconds, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方应用的其他配置,来自于企微配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getSuiteId() {
|
||||||
|
return suiteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSuiteSecret() {
|
||||||
|
return suiteSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第三方应用的token,用来检查应用的签名
|
||||||
|
@Override
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
//第三方应用的EncodingAESKey,用来检查签名
|
||||||
|
@Override
|
||||||
|
public String getEncodingAESKey() {
|
||||||
|
return encodingAESKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 企微服务商企业ID & 企业secret, 来自于企微配置
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getCorpId() {
|
||||||
|
return corpId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProviderSecret() {
|
||||||
|
return providerSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setProviderSecret(String providerSecret) {
|
||||||
|
this.providerSecret = providerSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权企业的access token相关
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getAccessToken(String authCorpId) {
|
||||||
|
return wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxAccessToken getAccessTokenEntity(String authCorpId) {
|
||||||
|
String accessToken = wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
|
||||||
|
Long expire = wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey);
|
||||||
|
if (StringUtils.isBlank(accessToken) || expire == null || expire == 0 || expire == -2) {
|
||||||
|
return new WxAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
WxAccessToken accessTokenEntity = new WxAccessToken();
|
||||||
|
accessTokenEntity.setAccessToken(accessToken);
|
||||||
|
accessTokenEntity.setExpiresIn((int) ((expire - System.currentTimeMillis()) / 1000 + 200));
|
||||||
|
return accessTokenEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAccessTokenExpired(String authCorpId) {
|
||||||
|
//没有设置或者TTL为0,都是过期
|
||||||
|
return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == 0L
|
||||||
|
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireAccessToken(String authCorpId) {
|
||||||
|
wxRedisOps.expire(keyWithPrefix(authCorpId) + accessTokenKey, 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
|
||||||
|
wxRedisOps.setValue(keyWithPrefix(authCorpId) + accessTokenKey, accessToken, expiredInSeconds, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权企业的js api ticket相关
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getAuthCorpJsApiTicket(String authCorpId) {
|
||||||
|
return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAuthCorpJsApiTicketExpired(String authCorpId) {
|
||||||
|
//没有设置或TTL为0,都是过期
|
||||||
|
return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == 0L
|
||||||
|
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireAuthCorpJsApiTicket(String authCorpId) {
|
||||||
|
wxRedisOps.expire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
||||||
|
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权企业的第三方应用js api ticket相关
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getAuthSuiteJsApiTicket(String authCorpId) {
|
||||||
|
return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAuthSuiteJsApiTicketExpired(String authCorpId) {
|
||||||
|
//没有设置或者TTL为0,都是过期
|
||||||
|
return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == 0L
|
||||||
|
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireAuthSuiteJsApiTicket(String authCorpId) {
|
||||||
|
wxRedisOps.expire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
||||||
|
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isProviderTokenExpired() {
|
||||||
|
//remain time to live in seconds, or key not exist
|
||||||
|
return wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey)) == 0L || wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey)) == -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateProviderToken(String providerToken, int expiredInSeconds) {
|
||||||
|
wxRedisOps.setValue(providerKeyWithPrefix(providerTokenKey), providerToken, expiredInSeconds, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getProviderToken() {
|
||||||
|
return wxRedisOps.getValue(providerKeyWithPrefix(providerTokenKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpProviderToken getProviderTokenEntity() {
|
||||||
|
String providerToken = wxRedisOps.getValue(providerKeyWithPrefix(providerTokenKey));
|
||||||
|
Long expire = wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey));
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(providerToken) || expire == null || expire == 0 || expire == -2) {
|
||||||
|
return new WxCpProviderToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
WxCpProviderToken wxCpProviderToken = new WxCpProviderToken();
|
||||||
|
wxCpProviderToken.setProviderAccessToken(providerToken);
|
||||||
|
wxCpProviderToken.setExpiresIn(Math.max(Math.toIntExact(expire), 0));
|
||||||
|
return wxCpProviderToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expireProviderToken() {
|
||||||
|
wxRedisOps.expire(providerKeyWithPrefix(providerTokenKey), 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网络代理相关
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getHttpProxyHost() {
|
||||||
|
return this.httpProxyHost;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getHttpProxyPort() {
|
||||||
|
return this.httpProxyPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHttpProxyUsername() {
|
||||||
|
return this.httpProxyUsername;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHttpProxyPassword() {
|
||||||
|
return this.httpProxyPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getTmpDirFile() {
|
||||||
|
return tmpDirFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Lock getProviderAccessTokenLock() {
|
||||||
|
return getProviderLockByKey(String.join(":", this.corpId, LOCKER_PROVIDER_ACCESS_TOKEN));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Lock getSuiteAccessTokenLock() {
|
||||||
|
return getLockByKey(LOCKER_SUITE_ACCESS_TOKEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Lock getAccessTokenLock(String authCorpId) {
|
||||||
|
return getLockByKey(String.join(":", authCorpId, LOCKER_ACCESS_TOKEN));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Lock getAuthCorpJsapiTicketLock(String authCorpId) {
|
||||||
|
return getLockByKey(String.join(":", authCorpId, LOCKER_CORP_JSAPI_TICKET));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Lock getSuiteJsapiTicketLock(String authCorpId) {
|
||||||
|
return getLockByKey(String.join(":", authCorpId, LOCKER_SUITE_JSAPI_TICKET));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Lock getLockByKey(String key) {
|
||||||
|
// 最终key的模式:(keyPrefix:)wechat_tp_lock:suiteId:(authCorpId):lockKey
|
||||||
|
// 其中keyPrefix目前不支持外部配置,authCorpId只有涉及到corpAccessToken, suiteJsapiTicket, authCorpJsapiTicket时才会拼上
|
||||||
|
return this.wxRedisOps.getLock(String.join(":", keyWithPrefix(LOCK_KEY + this.suiteId), key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 单独处理provider,且不应和suite 有关系
|
||||||
|
*/
|
||||||
|
private Lock getProviderLockByKey(String key) {
|
||||||
|
return this.wxRedisOps.getLock(String.join(":", providerKeyWithPrefix(LOCK_KEY), key));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
|
||||||
|
return this.apacheHttpClientBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean autoRefreshToken() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return WxCpGsonBuilder.create().toJson(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一个provider 会有多个suite,需要唯一标识作为前缀
|
||||||
|
*/
|
||||||
|
private String keyWithPrefix(String key) {
|
||||||
|
return keyPrefix + ":" + suiteId + ":" + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* provider 应该独享一个key,且不和任何suite关联
|
||||||
|
* 一个provider 会有多个suite,不同的suite 都应该指向同一个provider 的数据
|
||||||
|
*/
|
||||||
|
private String providerKeyWithPrefix(String key) {
|
||||||
|
return keyPrefix + ":" + corpId + ":" + key;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,20 +28,32 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
|
|||||||
private final transient Map<String, Lock> accessTokenLocker = new ConcurrentHashMap<>();
|
private final transient Map<String, Lock> accessTokenLocker = new ConcurrentHashMap<>();
|
||||||
private final transient Map<String, Lock> authCorpJsapiTicketLocker = new ConcurrentHashMap<>();
|
private final transient Map<String, Lock> authCorpJsapiTicketLocker = new ConcurrentHashMap<>();
|
||||||
private final transient Map<String, Lock> authSuiteJsapiTicketLocker = new ConcurrentHashMap<>();
|
private final transient Map<String, Lock> authSuiteJsapiTicketLocker = new ConcurrentHashMap<>();
|
||||||
private volatile String corpId;
|
/**
|
||||||
private volatile String corpSecret;
|
* 企微服务商企业ID & 企业secret,来自于企微配置
|
||||||
|
*/
|
||||||
|
protected volatile String corpId;
|
||||||
/**
|
/**
|
||||||
* 服务商secret
|
* 服务商secret
|
||||||
*/
|
*/
|
||||||
private volatile String providerSecret;
|
protected volatile String providerSecret;
|
||||||
private volatile String providerToken;
|
private volatile String providerToken;
|
||||||
private volatile long providerTokenExpiresTime;
|
private volatile long providerTokenExpiresTime;
|
||||||
private volatile String suiteId;
|
/**
|
||||||
private volatile String suiteSecret;
|
* 第三方应用的其他配置,来自于企微配置
|
||||||
private volatile String token;
|
*/
|
||||||
|
protected volatile String suiteId;
|
||||||
|
|
||||||
|
protected volatile String suiteSecret;
|
||||||
|
/**
|
||||||
|
* 第三方应用的token,用来检查应用的签名
|
||||||
|
*/
|
||||||
|
protected volatile String token;
|
||||||
private volatile String suiteAccessToken;
|
private volatile String suiteAccessToken;
|
||||||
private volatile long suiteAccessTokenExpiresTime;
|
private volatile long suiteAccessTokenExpiresTime;
|
||||||
private volatile String aesKey;
|
/**
|
||||||
|
* 第三方应用的EncodingAESKey,用来检查签名
|
||||||
|
*/
|
||||||
|
protected volatile String encodingAESKey;
|
||||||
private volatile String suiteTicket;
|
private volatile String suiteTicket;
|
||||||
private volatile long suiteTicketExpiresTime;
|
private volatile long suiteTicketExpiresTime;
|
||||||
private volatile String oauth2redirectUri;
|
private volatile String oauth2redirectUri;
|
||||||
@@ -186,11 +198,10 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
|
|||||||
/**
|
/**
|
||||||
* Sets suite id.
|
* Sets suite id.
|
||||||
*
|
*
|
||||||
* @param corpId the corp id
|
* @param suiteId
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
public void setSuiteId(String suiteId) {
|
||||||
public void setSuiteId(String corpId) {
|
this.suiteId = suiteId;
|
||||||
this.suiteId = corpId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -200,10 +211,7 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets suite secret.
|
* Sets suite secret.
|
||||||
*
|
|
||||||
* @param corpSecret the corp secret
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public void setSuiteSecret(String corpSecret) {
|
public void setSuiteSecret(String corpSecret) {
|
||||||
this.suiteSecret = corpSecret;
|
this.suiteSecret = corpSecret;
|
||||||
}
|
}
|
||||||
@@ -218,24 +226,22 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
|
|||||||
*
|
*
|
||||||
* @param token the token
|
* @param token the token
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public void setToken(String token) {
|
public void setToken(String token) {
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAesKey() {
|
public String getEncodingAESKey() {
|
||||||
return this.aesKey;
|
return this.encodingAESKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets aes key.
|
* Sets aes key. encodingAESKey
|
||||||
*
|
*
|
||||||
* @param aesKey the aes key
|
* @param encodingAESKey the aes key
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
public void setEncodingAESKey(String encodingAESKey) {
|
||||||
public void setAesKey(String aesKey) {
|
this.encodingAESKey = encodingAESKey;
|
||||||
this.aesKey = aesKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -249,25 +255,15 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl
|
|||||||
*
|
*
|
||||||
* @param corpId the corp id
|
* @param corpId the corp id
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public void setCorpId(String corpId) {
|
public void setCorpId(String corpId) {
|
||||||
this.corpId = corpId;
|
this.corpId = corpId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getCorpSecret() {
|
public String getCorpSecret() {
|
||||||
return this.corpSecret;
|
return this.providerSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets corp secret.
|
|
||||||
*
|
|
||||||
* @param corpSecret the corp secret
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public void setCorpSecret(String corpSecret) {
|
|
||||||
this.corpSecret = corpSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setProviderSecret(String providerSecret) {
|
public void setProviderSecret(String providerSecret) {
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package me.chanjar.weixin.cp.config.impl;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import me.chanjar.weixin.common.redis.JedisWxRedisOps;
|
||||||
|
import redis.clients.jedis.Jedis;
|
||||||
|
import redis.clients.jedis.util.Pool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 jedis 的实现
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/04/23
|
||||||
|
*/
|
||||||
|
public class WxCpTpJedisConfigImpl extends AbstractWxCpTpInRedisConfigImpl {
|
||||||
|
private static final long serialVersionUID = -1869372247414407433L;
|
||||||
|
|
||||||
|
public WxCpTpJedisConfigImpl(Pool<Jedis> jedisPool) {
|
||||||
|
this(jedisPool, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WxCpTpJedisConfigImpl(@NonNull Pool<Jedis> jedisPool, String keyPrefix) {
|
||||||
|
super(new JedisWxRedisOps(jedisPool), keyPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package me.chanjar.weixin.cp.config.impl;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
|
||||||
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于 RedisTemplate 的实现
|
||||||
|
*
|
||||||
|
* @author yl
|
||||||
|
* created on 2023/04/23
|
||||||
|
*/
|
||||||
|
public class WxCpTpRedisTemplateConfigImpl extends AbstractWxCpTpInRedisConfigImpl {
|
||||||
|
private static final long serialVersionUID = -1660004125413310620L;
|
||||||
|
|
||||||
|
public WxCpTpRedisTemplateConfigImpl(@NonNull StringRedisTemplate stringRedisTemplate) {
|
||||||
|
this(stringRedisTemplate, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WxCpTpRedisTemplateConfigImpl(@NonNull StringRedisTemplate stringRedisTemplate, String keyPrefix) {
|
||||||
|
super(new RedisTemplateWxRedisOps(stringRedisTemplate), keyPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,449 +1,25 @@
|
|||||||
package me.chanjar.weixin.cp.config.impl;
|
package me.chanjar.weixin.cp.config.impl;
|
||||||
|
|
||||||
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.Setter;
|
import me.chanjar.weixin.common.redis.RedissonWxRedisOps;
|
||||||
import me.chanjar.weixin.common.bean.WxAccessToken;
|
import org.redisson.api.RedissonClient;
|
||||||
import me.chanjar.weixin.common.redis.WxRedisOps;
|
|
||||||
import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder;
|
|
||||||
import me.chanjar.weixin.cp.bean.WxCpProviderToken;
|
|
||||||
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
|
||||||
import me.chanjar.weixin.cp.util.json.WxCpGsonBuilder;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 企业微信各种固定、授权配置的Redisson存储实现
|
* 基于Redisson的实现
|
||||||
|
*
|
||||||
|
* @author yuanqixun created on 2020 /5/13
|
||||||
|
* @author yl
|
||||||
*/
|
*/
|
||||||
@Builder
|
public class WxCpTpRedissonConfigImpl extends AbstractWxCpTpInRedisConfigImpl {
|
||||||
public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializable {
|
private static final long serialVersionUID = -5674792341070783967L;
|
||||||
private static final long serialVersionUID = -5385639031981770319L;
|
|
||||||
|
|
||||||
/**
|
public WxCpTpRedissonConfigImpl(@NonNull RedissonClient redissonClient) {
|
||||||
* The constant LOCK_KEY.
|
this(redissonClient, null);
|
||||||
*/
|
|
||||||
// lock key
|
|
||||||
protected static final String LOCK_KEY = "wechat_tp_lock:";
|
|
||||||
/**
|
|
||||||
* The constant LOCKER_PROVIDER_ACCESS_TOKEN.
|
|
||||||
*/
|
|
||||||
protected static final String LOCKER_PROVIDER_ACCESS_TOKEN = "providerAccessTokenLock";
|
|
||||||
/**
|
|
||||||
* The constant LOCKER_SUITE_ACCESS_TOKEN.
|
|
||||||
*/
|
|
||||||
protected static final String LOCKER_SUITE_ACCESS_TOKEN = "suiteAccessTokenLock";
|
|
||||||
/**
|
|
||||||
* The constant LOCKER_ACCESS_TOKEN.
|
|
||||||
*/
|
|
||||||
protected static final String LOCKER_ACCESS_TOKEN = "accessTokenLock";
|
|
||||||
/**
|
|
||||||
* The constant LOCKER_CORP_JSAPI_TICKET.
|
|
||||||
*/
|
|
||||||
protected static final String LOCKER_CORP_JSAPI_TICKET = "corpJsapiTicketLock";
|
|
||||||
/**
|
|
||||||
* The constant LOCKER_SUITE_JSAPI_TICKET.
|
|
||||||
*/
|
|
||||||
protected static final String LOCKER_SUITE_JSAPI_TICKET = "suiteJsapiTicketLock";
|
|
||||||
@NonNull
|
|
||||||
private final WxRedisOps wxRedisOps;
|
|
||||||
private final String suiteAccessTokenKey = ":suiteAccessTokenKey:";
|
|
||||||
private final String suiteTicketKey = ":suiteTicketKey:";
|
|
||||||
private final String accessTokenKey = ":accessTokenKey:";
|
|
||||||
private final String authCorpJsApiTicketKey = ":authCorpJsApiTicketKey:";
|
|
||||||
private final String authSuiteJsApiTicketKey = ":authSuiteJsApiTicketKey:";
|
|
||||||
private final String providerTokenKey = ":providerTokenKey:";
|
|
||||||
/**
|
|
||||||
* redis里面key的统一前缀
|
|
||||||
*/
|
|
||||||
@Setter
|
|
||||||
private String keyPrefix = "";
|
|
||||||
private volatile String baseApiUrl;
|
|
||||||
private volatile String httpProxyHost;
|
|
||||||
private volatile int httpProxyPort;
|
|
||||||
private volatile String httpProxyUsername;
|
|
||||||
private volatile String httpProxyPassword;
|
|
||||||
private volatile ApacheHttpClientBuilder apacheHttpClientBuilder;
|
|
||||||
private volatile File tmpDirFile;
|
|
||||||
/**
|
|
||||||
* 第三方应用的其他配置,来自于企微配置
|
|
||||||
*/
|
|
||||||
private volatile String suiteId;
|
|
||||||
private volatile String suiteSecret;
|
|
||||||
/**
|
|
||||||
* 第三方应用的token,用来检查应用的签名
|
|
||||||
*/
|
|
||||||
private volatile String token;
|
|
||||||
/**
|
|
||||||
* 第三方应用的EncodingAESKey,用来检查签名
|
|
||||||
*/
|
|
||||||
private volatile String aesKey;
|
|
||||||
/**
|
|
||||||
* 企微服务商企业ID & 企业secret,来自于企微配置
|
|
||||||
*/
|
|
||||||
private volatile String corpId;
|
|
||||||
private volatile String corpSecret;
|
|
||||||
/**
|
|
||||||
* 服务商secret
|
|
||||||
*/
|
|
||||||
private volatile String providerSecret;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setBaseApiUrl(String baseUrl) {
|
|
||||||
this.baseApiUrl = baseUrl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public WxCpTpRedissonConfigImpl(@NonNull RedissonClient redissonClient, String keyPrefix) {
|
||||||
public String getApiUrl(String path) {
|
super(new RedissonWxRedisOps(redissonClient), keyPrefix);
|
||||||
if (baseApiUrl == null) {
|
|
||||||
baseApiUrl = "https://qyapi.weixin.qq.com";
|
|
||||||
}
|
|
||||||
return baseApiUrl + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 第三方应用的suite access token相关
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getSuiteAccessToken() {
|
|
||||||
return wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WxAccessToken getSuiteAccessTokenEntity() {
|
|
||||||
String suiteAccessToken = wxRedisOps.getValue(keyWithPrefix(suiteAccessTokenKey));
|
|
||||||
Long expireIn = wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey));
|
|
||||||
if (StringUtils.isBlank(suiteAccessToken) || expireIn == null || expireIn == 0 || expireIn == -2) {
|
|
||||||
return new WxAccessToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
WxAccessToken suiteAccessTokenEntity = new WxAccessToken();
|
|
||||||
suiteAccessTokenEntity.setAccessToken(suiteAccessToken);
|
|
||||||
suiteAccessTokenEntity.setExpiresIn(Math.max(Math.toIntExact(expireIn), 0));
|
|
||||||
return suiteAccessTokenEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSuiteAccessTokenExpired() {
|
|
||||||
//remain time to live in seconds, or key not exist
|
|
||||||
return wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteAccessTokenKey)) == -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void expireSuiteAccessToken() {
|
|
||||||
wxRedisOps.expire(keyWithPrefix(suiteAccessTokenKey), 0, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateSuiteAccessToken(WxAccessToken suiteAccessToken) {
|
|
||||||
updateSuiteAccessToken(suiteAccessToken.getAccessToken(), suiteAccessToken.getExpiresIn());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateSuiteAccessToken(String suiteAccessToken, int expiresInSeconds) {
|
|
||||||
wxRedisOps.setValue(keyWithPrefix(suiteAccessTokenKey), suiteAccessToken, expiresInSeconds, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 第三方应用的suite ticket相关
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getSuiteTicket() {
|
|
||||||
return wxRedisOps.getValue(keyWithPrefix(suiteTicketKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isSuiteTicketExpired() {
|
|
||||||
//remain time to live in seconds, or key not exist
|
|
||||||
return wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == 0L || wxRedisOps.getExpire(keyWithPrefix(suiteTicketKey)) == -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void expireSuiteTicket() {
|
|
||||||
wxRedisOps.expire(keyWithPrefix(suiteTicketKey), 0, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateSuiteTicket(String suiteTicket, int expiresInSeconds) {
|
|
||||||
wxRedisOps.setValue(keyWithPrefix(suiteTicketKey), suiteTicket, expiresInSeconds, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 第三方应用的其他配置,来自于企微配置
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getSuiteId() {
|
|
||||||
return suiteId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getSuiteSecret() {
|
|
||||||
return suiteSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第三方应用的token,用来检查应用的签名
|
|
||||||
@Override
|
|
||||||
public String getToken() {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
|
|
||||||
//第三方应用的EncodingAESKey,用来检查签名
|
|
||||||
@Override
|
|
||||||
public String getAesKey() {
|
|
||||||
return aesKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 企微服务商企业ID & 企业secret, 来自于企微配置
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getCorpId() {
|
|
||||||
return corpId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCorpSecret() {
|
|
||||||
return corpSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setProviderSecret(String providerSecret) {
|
|
||||||
this.providerSecret = providerSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProviderSecret() {
|
|
||||||
return providerSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 授权企业的access token相关
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getAccessToken(String authCorpId) {
|
|
||||||
return wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WxAccessToken getAccessTokenEntity(String authCorpId) {
|
|
||||||
String accessToken = wxRedisOps.getValue(keyWithPrefix(authCorpId) + accessTokenKey);
|
|
||||||
Long expire = wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey);
|
|
||||||
if (StringUtils.isBlank(accessToken) || expire == null || expire == 0 || expire == -2) {
|
|
||||||
return new WxAccessToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
WxAccessToken accessTokenEntity = new WxAccessToken();
|
|
||||||
accessTokenEntity.setAccessToken(accessToken);
|
|
||||||
accessTokenEntity.setExpiresIn((int) ((expire - System.currentTimeMillis()) / 1000 + 200));
|
|
||||||
return accessTokenEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAccessTokenExpired(String authCorpId) {
|
|
||||||
//没有设置或者TTL为0,都是过期
|
|
||||||
return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == 0L
|
|
||||||
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + accessTokenKey) == -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void expireAccessToken(String authCorpId) {
|
|
||||||
wxRedisOps.expire(keyWithPrefix(authCorpId) + accessTokenKey, 0, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateAccessToken(String authCorpId, String accessToken, int expiredInSeconds) {
|
|
||||||
wxRedisOps.setValue(keyWithPrefix(authCorpId) + accessTokenKey, accessToken, expiredInSeconds, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 授权企业的js api ticket相关
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getAuthCorpJsApiTicket(String authCorpId) {
|
|
||||||
return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAuthCorpJsApiTicketExpired(String authCorpId) {
|
|
||||||
//没有设置或TTL为0,都是过期
|
|
||||||
return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == 0L
|
|
||||||
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey) == -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void expireAuthCorpJsApiTicket(String authCorpId) {
|
|
||||||
wxRedisOps.expire(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, 0, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateAuthCorpJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
|
||||||
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authCorpJsApiTicketKey, jsApiTicket, expiredInSeconds,
|
|
||||||
TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 授权企业的第三方应用js api ticket相关
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getAuthSuiteJsApiTicket(String authCorpId) {
|
|
||||||
return wxRedisOps.getValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isAuthSuiteJsApiTicketExpired(String authCorpId) {
|
|
||||||
//没有设置或者TTL为0,都是过期
|
|
||||||
return wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == 0L
|
|
||||||
|| wxRedisOps.getExpire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey) == -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void expireAuthSuiteJsApiTicket(String authCorpId) {
|
|
||||||
wxRedisOps.expire(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, 0, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateAuthSuiteJsApiTicket(String authCorpId, String jsApiTicket, int expiredInSeconds) {
|
|
||||||
wxRedisOps.setValue(keyWithPrefix(authCorpId) + authSuiteJsApiTicketKey, jsApiTicket, expiredInSeconds,
|
|
||||||
TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isProviderTokenExpired() {
|
|
||||||
//remain time to live in seconds, or key not exist
|
|
||||||
return wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey)) == 0L || wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey)) == -2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void updateProviderToken(String providerToken, int expiredInSeconds) {
|
|
||||||
wxRedisOps.setValue(providerKeyWithPrefix(providerTokenKey), providerToken, expiredInSeconds, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getProviderToken() {
|
|
||||||
return wxRedisOps.getValue(providerKeyWithPrefix(providerTokenKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WxCpProviderToken getProviderTokenEntity() {
|
|
||||||
String providerToken = wxRedisOps.getValue(providerKeyWithPrefix(providerTokenKey));
|
|
||||||
Long expire = wxRedisOps.getExpire(providerKeyWithPrefix(providerTokenKey));
|
|
||||||
|
|
||||||
if (StringUtils.isBlank(providerToken) || expire == null || expire == 0 || expire == -2) {
|
|
||||||
return new WxCpProviderToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
WxCpProviderToken wxCpProviderToken = new WxCpProviderToken();
|
|
||||||
wxCpProviderToken.setProviderAccessToken(providerToken);
|
|
||||||
wxCpProviderToken.setExpiresIn(Math.max(Math.toIntExact(expire), 0));
|
|
||||||
return wxCpProviderToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void expireProviderToken() {
|
|
||||||
wxRedisOps.expire(providerKeyWithPrefix(providerTokenKey), 0, TimeUnit.SECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 网络代理相关
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public String getHttpProxyHost() {
|
|
||||||
return this.httpProxyHost;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getHttpProxyPort() {
|
|
||||||
return this.httpProxyPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHttpProxyUsername() {
|
|
||||||
return this.httpProxyUsername;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHttpProxyPassword() {
|
|
||||||
return this.httpProxyPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public File getTmpDirFile() {
|
|
||||||
return tmpDirFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Lock getProviderAccessTokenLock() {
|
|
||||||
return getProviderLockByKey(String.join(":", this.corpId, LOCKER_PROVIDER_ACCESS_TOKEN));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Lock getSuiteAccessTokenLock() {
|
|
||||||
return getLockByKey(LOCKER_SUITE_ACCESS_TOKEN);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Lock getAccessTokenLock(String authCorpId) {
|
|
||||||
return getLockByKey(String.join(":", authCorpId, LOCKER_ACCESS_TOKEN));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Lock getAuthCorpJsapiTicketLock(String authCorpId) {
|
|
||||||
return getLockByKey(String.join(":", authCorpId, LOCKER_CORP_JSAPI_TICKET));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Lock getSuiteJsapiTicketLock(String authCorpId) {
|
|
||||||
return getLockByKey(String.join(":", authCorpId, LOCKER_SUITE_JSAPI_TICKET));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Lock getLockByKey(String key) {
|
|
||||||
// 最终key的模式:(keyPrefix:)wechat_tp_lock:suiteId:(authCorpId):lockKey
|
|
||||||
// 其中keyPrefix目前不支持外部配置,authCorpId只有涉及到corpAccessToken, suiteJsapiTicket, authCorpJsapiTicket时才会拼上
|
|
||||||
return this.wxRedisOps.getLock(String.join(":", keyWithPrefix(LOCK_KEY + this.suiteId), key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单独处理provider,且不应和suite 有关系
|
|
||||||
*/
|
|
||||||
private Lock getProviderLockByKey(String key) {
|
|
||||||
return this.wxRedisOps.getLock(String.join(":", providerKeyWithPrefix(LOCK_KEY), key));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ApacheHttpClientBuilder getApacheHttpClientBuilder() {
|
|
||||||
return this.apacheHttpClientBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean autoRefreshToken() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return WxCpGsonBuilder.create().toJson(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 一个provider 会有多个suite,需要唯一标识作为前缀
|
|
||||||
*/
|
|
||||||
private String keyWithPrefix(String key) {
|
|
||||||
return keyPrefix + ":" + suiteId + ":" + key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* provider 应该独享一个key,且不和任何suite关联
|
|
||||||
* 一个provider 会有多个suite,不同的suite 都应该指向同一个provider 的数据
|
|
||||||
*/
|
|
||||||
private String providerKeyWithPrefix(String key) {
|
|
||||||
return keyPrefix + ":" + corpId + ":" + key;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -855,6 +855,10 @@ public interface WxCpApiPathConsts {
|
|||||||
* The constant GET_PERMANENT_CODE.
|
* The constant GET_PERMANENT_CODE.
|
||||||
*/
|
*/
|
||||||
String GET_PERMANENT_CODE = "/cgi-bin/service/get_permanent_code";
|
String GET_PERMANENT_CODE = "/cgi-bin/service/get_permanent_code";
|
||||||
|
/**
|
||||||
|
* The constant GET_V2_PERMANENT_CODE.
|
||||||
|
*/
|
||||||
|
String GET_V2_PERMANENT_CODE = "/cgi-bin/service/v2/get_permanent_code";
|
||||||
/**
|
/**
|
||||||
* The constant GET_SUITE_TOKEN.
|
* The constant GET_SUITE_TOKEN.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import me.chanjar.weixin.common.util.http.MediaUploadRequestExecutor;
|
|||||||
import me.chanjar.weixin.common.util.http.RequestExecutor;
|
import me.chanjar.weixin.common.util.http.RequestExecutor;
|
||||||
import me.chanjar.weixin.common.util.http.RequestHttp;
|
import me.chanjar.weixin.common.util.http.RequestHttp;
|
||||||
import me.chanjar.weixin.cp.bean.*;
|
import me.chanjar.weixin.cp.bean.*;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
|
||||||
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -186,6 +187,8 @@ public interface WxCpTpService {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException;
|
WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException;
|
||||||
|
|
||||||
|
WxCpTpCorp getV2PermanentCode(String authCode) throws WxErrorException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取企业永久授权码信息
|
* 获取企业永久授权码信息
|
||||||
* <pre>
|
* <pre>
|
||||||
@@ -200,6 +203,8 @@ public interface WxCpTpService {
|
|||||||
*/
|
*/
|
||||||
WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException;
|
WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException;
|
||||||
|
|
||||||
|
WxCpTpPermanentCodeInfo getV2PermanentCodeInfo(String authCode) throws WxErrorException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
* 获取预授权链接
|
* 获取预授权链接
|
||||||
@@ -343,9 +348,7 @@ public interface WxCpTpService {
|
|||||||
* 获取WxCpTpConfigStorage 对象.
|
* 获取WxCpTpConfigStorage 对象.
|
||||||
*
|
*
|
||||||
* @return WxCpTpConfigStorage wx cp tp config storage
|
* @return WxCpTpConfigStorage wx cp tp config storage
|
||||||
* @deprecated storage应该在service内部使用 ,提供这个接口,容易破坏这个封装
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
WxCpTpConfigStorage getWxCpTpConfigStorage();
|
WxCpTpConfigStorage getWxCpTpConfigStorage();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -527,6 +530,11 @@ public interface WxCpTpService {
|
|||||||
*/
|
*/
|
||||||
WxCpTpLicenseService getWxCpTpLicenseService();
|
WxCpTpLicenseService getWxCpTpLicenseService();
|
||||||
|
|
||||||
|
WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
|
||||||
|
String timestamp, String nonce, String msgSignature);
|
||||||
|
|
||||||
|
String getVerifyDecrypt(String sVerifyEchoStr);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取应用的管理员列表
|
* 获取应用的管理员列表
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ import me.chanjar.weixin.common.util.http.SimplePostRequestExecutor;
|
|||||||
import me.chanjar.weixin.common.util.json.GsonParser;
|
import me.chanjar.weixin.common.util.json.GsonParser;
|
||||||
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
|
import me.chanjar.weixin.common.util.json.WxGsonBuilder;
|
||||||
import me.chanjar.weixin.cp.bean.*;
|
import me.chanjar.weixin.cp.bean.*;
|
||||||
|
import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage;
|
||||||
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
||||||
import me.chanjar.weixin.cp.tp.service.*;
|
import me.chanjar.weixin.cp.tp.service.*;
|
||||||
|
import me.chanjar.weixin.cp.util.crypto.WxCpTpCryptUtil;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -91,6 +93,7 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
|
|
||||||
private final WxSessionManager sessionManager = new StandardSessionManager();
|
private final WxSessionManager sessionManager = new StandardSessionManager();
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 临时文件目录.
|
* 临时文件目录.
|
||||||
*/
|
*/
|
||||||
@@ -259,6 +262,18 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
return wxCpTpCorp;
|
return wxCpTpCorp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpTpCorp getV2PermanentCode(String authCode) throws WxErrorException {
|
||||||
|
JsonObject jsonObject = new JsonObject();
|
||||||
|
jsonObject.addProperty("auth_code", authCode);
|
||||||
|
|
||||||
|
String result = post(configStorage.getApiUrl(GET_V2_PERMANENT_CODE), jsonObject.toString());
|
||||||
|
jsonObject = GsonParser.parse(result);
|
||||||
|
WxCpTpCorp wxCpTpCorp = WxCpTpCorp.fromJson(jsonObject.get("auth_corp_info").getAsJsonObject().toString());
|
||||||
|
wxCpTpCorp.setPermanentCode(jsonObject.get("permanent_code").getAsString());
|
||||||
|
return wxCpTpCorp;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException {
|
public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException {
|
||||||
JsonObject jsonObject = new JsonObject();
|
JsonObject jsonObject = new JsonObject();
|
||||||
@@ -267,6 +282,14 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
return WxCpTpPermanentCodeInfo.fromJson(result);
|
return WxCpTpPermanentCodeInfo.fromJson(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpTpPermanentCodeInfo getV2PermanentCodeInfo(String authCode) throws WxErrorException {
|
||||||
|
JsonObject jsonObject = new JsonObject();
|
||||||
|
jsonObject.addProperty("auth_code", authCode);
|
||||||
|
String result = post(configStorage.getApiUrl(GET_V2_PERMANENT_CODE), jsonObject.toString());
|
||||||
|
return WxCpTpPermanentCodeInfo.fromJson(result);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public String getPreAuthUrl(String redirectUri, String state) throws WxErrorException {
|
public String getPreAuthUrl(String redirectUri, String state) throws WxErrorException {
|
||||||
@@ -452,7 +475,7 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
if (error.getErrorCode() == WxCpErrorMsgEnum.CODE_42009.getCode()) {
|
if (error.getErrorCode() == WxCpErrorMsgEnum.CODE_42009.getCode()) {
|
||||||
// 强制设置wxCpTpConfigStorage它的suite access token过期了,这样在下一次请求里就会刷新suite access token
|
// 强制设置wxCpTpConfigStorage它的suite access token过期了,这样在下一次请求里就会刷新suite access token
|
||||||
this.configStorage.expireSuiteAccessToken();
|
this.configStorage.expireSuiteAccessToken();
|
||||||
if (this.getWxCpTpConfigStorage().autoRefreshToken()) {
|
if (this.configStorage.autoRefreshToken()) {
|
||||||
log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
|
log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
|
||||||
return this.execute(executor, uri, data);
|
return this.execute(executor, uri, data);
|
||||||
}
|
}
|
||||||
@@ -646,6 +669,27 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
this.wxCpTpUserService = wxCpTpUserService;
|
this.wxCpTpUserService = wxCpTpUserService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param encryptedXml the encrypted xml
|
||||||
|
* @param timestamp the timestamp
|
||||||
|
* @param nonce the nonce
|
||||||
|
* @param msgSignature the msg signature
|
||||||
|
* @return the wx cp tp xml message
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
|
||||||
|
String timestamp, String nonce, String msgSignature) {
|
||||||
|
return WxCpTpXmlMessage.fromEncryptedXml(encryptedXml,this.configStorage,timestamp,nonce,msgSignature);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getVerifyDecrypt(String sVerifyEchoStr) {
|
||||||
|
WxCpTpCryptUtil cryptUtil = new WxCpTpCryptUtil(this.configStorage);
|
||||||
|
return cryptUtil.decrypt(sVerifyEchoStr);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public WxCpTpAdmin getAdminList(String authCorpId, Integer agentId) throws WxErrorException {
|
public WxCpTpAdmin getAdminList(String authCorpId, Integer agentId) throws WxErrorException {
|
||||||
JsonObject jsonObject = new JsonObject();
|
JsonObject jsonObject = new JsonObject();
|
||||||
@@ -764,4 +808,9 @@ public abstract class BaseWxCpTpServiceImpl<H, P> implements WxCpTpService, Requ
|
|||||||
public void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service) {
|
public void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service) {
|
||||||
this.wxCpTpOAuth2Service = wxCpTpOAuth2Service;
|
this.wxCpTpOAuth2Service = wxCpTpOAuth2Service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxCpTpConfigStorage getWxCpTpConfigStorage() {
|
||||||
|
return this.configStorage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,9 +101,9 @@ public class WxCpTpServiceApacheHttpClientImpl extends BaseWxCpTpServiceImpl<Clo
|
|||||||
this.httpClient = apacheHttpClientBuilder.build();
|
this.httpClient = apacheHttpClientBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// @Override
|
||||||
public WxCpTpConfigStorage getWxCpTpConfigStorage() {
|
// public WxCpTpConfigStorage getWxCpTpConfigStorage() {
|
||||||
return this.configStorage;
|
// return this.configStorage;
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,9 +98,9 @@ public class WxCpTpServiceHttpComponentsImpl extends BaseWxCpTpServiceImpl<Close
|
|||||||
this.httpClient = apacheHttpClientBuilder.build();
|
this.httpClient = apacheHttpClientBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
// @Override
|
||||||
public WxCpTpConfigStorage getWxCpTpConfigStorage() {
|
// public WxCpTpConfigStorage getWxCpTpConfigStorage() {
|
||||||
return this.configStorage;
|
// return this.configStorage;
|
||||||
}
|
// }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
package me.chanjar.weixin.cp.tp.service.impl;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import jodd.http.HttpConnectionProvider;
|
||||||
|
import jodd.http.HttpRequest;
|
||||||
|
import jodd.http.HttpResponse;
|
||||||
|
import jodd.http.ProxyInfo;
|
||||||
|
import jodd.http.net.SocketHttpConnectionProvider;
|
||||||
|
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||||
|
import me.chanjar.weixin.common.enums.WxType;
|
||||||
|
import me.chanjar.weixin.common.error.WxError;
|
||||||
|
import me.chanjar.weixin.common.error.WxErrorException;
|
||||||
|
import me.chanjar.weixin.common.util.http.HttpClientType;
|
||||||
|
import me.chanjar.weixin.common.util.json.GsonParser;
|
||||||
|
import me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
|
||||||
|
import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type Wx cp service jodd http.
|
||||||
|
*
|
||||||
|
* @author someone
|
||||||
|
*/
|
||||||
|
public class WxCpTpServiceJoddHttpImpl extends BaseWxCpTpServiceImpl<HttpConnectionProvider, ProxyInfo> {
|
||||||
|
private HttpConnectionProvider httpClient;
|
||||||
|
private ProxyInfo httpProxy;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpConnectionProvider getRequestHttpClient() {
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProxyInfo getRequestHttpProxy() {
|
||||||
|
return httpProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpClientType getRequestType() {
|
||||||
|
return HttpClientType.JODD_HTTP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException {
|
||||||
|
if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
|
||||||
|
return this.configStorage.getSuiteAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this.globalSuiteAccessTokenRefreshLock) {
|
||||||
|
// 构建请求 URL
|
||||||
|
String url = this.configStorage.getApiUrl(WxCpApiPathConsts.Tp.GET_SUITE_TOKEN);
|
||||||
|
|
||||||
|
JsonObject jsonObject = new JsonObject();
|
||||||
|
jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
|
||||||
|
jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
|
||||||
|
jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
|
||||||
|
String jsonBody = jsonObject.toString();
|
||||||
|
|
||||||
|
if (this.httpProxy != null) {
|
||||||
|
httpClient.useProxy(this.httpProxy);
|
||||||
|
}
|
||||||
|
// 创建 POST 请求
|
||||||
|
HttpRequest request = HttpRequest
|
||||||
|
.post(url)
|
||||||
|
.contentType("application/json")
|
||||||
|
.body(jsonBody); // 使用 .body() 设置请求体
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新 access token
|
||||||
|
jsonObject = GsonParser.parse(resultContent);
|
||||||
|
String suiteAccussToken = jsonObject.get("suite_access_token").getAsString();
|
||||||
|
int expiresIn = jsonObject.get("expires_in").getAsInt();
|
||||||
|
this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.configStorage.getSuiteAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initHttp() {
|
||||||
|
if (this.configStorage.getHttpProxyHost() != null && this.configStorage.getHttpProxyPort() > 0) {
|
||||||
|
httpProxy = new ProxyInfo(ProxyInfo.ProxyType.HTTP, configStorage.getHttpProxyHost(),
|
||||||
|
configStorage.getHttpProxyPort(), configStorage.getHttpProxyUsername(), configStorage.getHttpProxyPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient = new SocketHttpConnectionProvider();
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// @Override
|
||||||
|
// public WxCpConfigStorage getWxCpConfigStorage() {
|
||||||
|
// return this.configStorage;
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package me.chanjar.weixin.cp.tp.service.impl;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import me.chanjar.weixin.common.bean.WxAccessToken;
|
||||||
|
import me.chanjar.weixin.common.enums.WxType;
|
||||||
|
import me.chanjar.weixin.common.error.WxError;
|
||||||
|
import me.chanjar.weixin.common.error.WxErrorException;
|
||||||
|
import me.chanjar.weixin.common.error.WxRuntimeException;
|
||||||
|
import me.chanjar.weixin.common.util.http.HttpClientType;
|
||||||
|
import me.chanjar.weixin.common.util.http.okhttp.DefaultOkHttpClientBuilder;
|
||||||
|
import me.chanjar.weixin.common.util.http.okhttp.OkHttpProxyInfo;
|
||||||
|
import me.chanjar.weixin.common.util.json.GsonParser;
|
||||||
|
import me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
|
||||||
|
import me.chanjar.weixin.cp.constant.WxCpApiPathConsts;
|
||||||
|
import okhttp3.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static me.chanjar.weixin.cp.constant.WxCpApiPathConsts.GET_TOKEN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type Wx cp service ok http.
|
||||||
|
*
|
||||||
|
* @author someone
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class WxCpTpServiceOkHttpImpl extends BaseWxCpTpServiceImpl<OkHttpClient, OkHttpProxyInfo> {
|
||||||
|
private OkHttpClient httpClient;
|
||||||
|
private OkHttpProxyInfo httpProxy;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OkHttpClient getRequestHttpClient() {
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OkHttpProxyInfo getRequestHttpProxy() {
|
||||||
|
return httpProxy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpClientType getRequestType() {
|
||||||
|
return HttpClientType.OK_HTTP;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSuiteAccessToken(boolean forceRefresh) throws WxErrorException {
|
||||||
|
if (!this.configStorage.isSuiteAccessTokenExpired() && !forceRefresh) {
|
||||||
|
return this.configStorage.getSuiteAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (this.globalSuiteAccessTokenRefreshLock) {
|
||||||
|
// 得到 httpClient
|
||||||
|
OkHttpClient client = getRequestHttpClient();
|
||||||
|
|
||||||
|
JsonObject jsonObject = new JsonObject();
|
||||||
|
jsonObject.addProperty("suite_id", this.configStorage.getSuiteId());
|
||||||
|
jsonObject.addProperty("suite_secret", this.configStorage.getSuiteSecret());
|
||||||
|
jsonObject.addProperty("suite_ticket", this.getSuiteTicket());
|
||||||
|
String jsonBody = jsonObject.toString();
|
||||||
|
|
||||||
|
RequestBody requestBody = RequestBody.create(
|
||||||
|
MediaType.get("application/json; charset=utf-8"),
|
||||||
|
jsonBody
|
||||||
|
);
|
||||||
|
|
||||||
|
// 构建 POST 请求
|
||||||
|
Request request = new Request.Builder()
|
||||||
|
.url(this.configStorage.getApiUrl(WxCpApiPathConsts.Tp.GET_SUITE_TOKEN)) // URL 不包含查询参数
|
||||||
|
.post(requestBody) // 使用 POST 方法
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String resultContent = null;
|
||||||
|
try (Response response = client.newCall(request).execute()) {
|
||||||
|
if (!response.isSuccessful()) {
|
||||||
|
throw new IOException("Unexpected response code: " + response);
|
||||||
|
}
|
||||||
|
resultContent = response.body().string();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("获取 suite token 失败: {}", e.getMessage(), e);
|
||||||
|
throw new WxRuntimeException("获取 suite token 失败", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
WxError error = WxError.fromJson(resultContent, WxType.CP);
|
||||||
|
if (error.getErrorCode() != 0) {
|
||||||
|
throw new WxErrorException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonObject = GsonParser.parse(resultContent);
|
||||||
|
String suiteAccussToken = jsonObject.get("suite_access_token").getAsString();
|
||||||
|
int expiresIn = jsonObject.get("expires_in").getAsInt();
|
||||||
|
this.configStorage.updateSuiteAccessToken(suiteAccussToken, expiresIn);
|
||||||
|
}
|
||||||
|
return this.configStorage.getSuiteAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initHttp() {
|
||||||
|
log.debug("WxCpServiceOkHttpImpl initHttp");
|
||||||
|
//设置代理
|
||||||
|
if (configStorage.getHttpProxyHost() != null && configStorage.getHttpProxyPort() > 0) {
|
||||||
|
httpProxy = OkHttpProxyInfo.httpProxy(configStorage.getHttpProxyHost(),
|
||||||
|
configStorage.getHttpProxyPort(),
|
||||||
|
configStorage.getHttpProxyUsername(),
|
||||||
|
configStorage.getHttpProxyPassword());
|
||||||
|
OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder();
|
||||||
|
clientBuilder.proxy(getRequestHttpProxy().getProxy());
|
||||||
|
//设置授权
|
||||||
|
clientBuilder.authenticator(new Authenticator() {
|
||||||
|
@Override
|
||||||
|
public Request authenticate(Route route, Response response) throws IOException {
|
||||||
|
String credential = Credentials.basic(httpProxy.getProxyUsername(), httpProxy.getProxyPassword());
|
||||||
|
return response.request().newBuilder()
|
||||||
|
.header("Authorization", credential)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
httpClient = clientBuilder.build();
|
||||||
|
} else {
|
||||||
|
httpClient = DefaultOkHttpClientBuilder.get().build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public WxCpConfigStorage getWxCpConfigStorage() {
|
||||||
|
// return this.configStorage;
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ public class WxCpTpCryptUtil extends WxCryptUtil {
|
|||||||
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
|
* @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
|
||||||
* @param appidOrCorpid 公众平台corpId
|
* @param appidOrCorpid 公众平台corpId
|
||||||
*/
|
*/
|
||||||
String encodingAesKey = wxCpTpConfigStorage.getAesKey();
|
String encodingAesKey = wxCpTpConfigStorage.getEncodingAESKey();
|
||||||
String token = wxCpTpConfigStorage.getToken();
|
String token = wxCpTpConfigStorage.getToken();
|
||||||
String corpId = wxCpTpConfigStorage.getCorpId();
|
String corpId = wxCpTpConfigStorage.getCorpId();
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import me.chanjar.weixin.cp.bean.WxCpTpPermanentCodeInfo;
|
|||||||
import me.chanjar.weixin.cp.bean.WxTpCustomizedAuthUrl;
|
import me.chanjar.weixin.cp.bean.WxTpCustomizedAuthUrl;
|
||||||
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
||||||
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
|
import me.chanjar.weixin.cp.config.impl.WxCpTpDefaultConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.AbstractWxCpTpInRedisConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpTpRedisTemplateConfigImpl;
|
||||||
import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl;
|
import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl;
|
||||||
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
|
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
@@ -69,7 +71,10 @@ public class BaseWxCpTpServiceImplTest {
|
|||||||
* @return the wx cp tp config storage
|
* @return the wx cp tp config storage
|
||||||
*/
|
*/
|
||||||
public WxCpTpConfigStorage wxCpTpConfigStorage() {
|
public WxCpTpConfigStorage wxCpTpConfigStorage() {
|
||||||
return WxCpTpRedissonConfigImpl.builder().corpId(PROVIDER_CORP_ID).providerSecret(PROVIDER_SECRET).wxRedisOps(new RedissonWxRedisOps(redissonClient())).build();
|
WxCpTpRedissonConfigImpl wxCpTpRedissonConfig=new WxCpTpRedissonConfigImpl(redissonClient(),"");
|
||||||
|
wxCpTpRedissonConfig.setCorpId(PROVIDER_CORP_ID);
|
||||||
|
wxCpTpRedissonConfig.setProviderSecret(PROVIDER_SECRET);
|
||||||
|
return wxCpTpRedissonConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import me.chanjar.weixin.common.redis.RedissonWxRedisOps;
|
|||||||
import me.chanjar.weixin.cp.bean.WxCpProviderToken;
|
import me.chanjar.weixin.cp.bean.WxCpProviderToken;
|
||||||
import me.chanjar.weixin.cp.bean.WxCpTpCorpId2OpenCorpId;
|
import me.chanjar.weixin.cp.bean.WxCpTpCorpId2OpenCorpId;
|
||||||
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.AbstractWxCpTpInRedisConfigImpl;
|
||||||
|
import me.chanjar.weixin.cp.config.impl.WxCpTpRedisTemplateConfigImpl;
|
||||||
import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl;
|
import me.chanjar.weixin.cp.config.impl.WxCpTpRedissonConfigImpl;
|
||||||
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
|
import me.chanjar.weixin.cp.tp.service.WxCpTpService;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@@ -48,14 +50,10 @@ public class WxCpTpServiceApacheHttpClientImplTest {
|
|||||||
* The constant PROVIDER_CORP_ID.
|
* The constant PROVIDER_CORP_ID.
|
||||||
*/
|
*/
|
||||||
public static final String PROVIDER_CORP_ID = "xxxxxx";
|
public static final String PROVIDER_CORP_ID = "xxxxxx";
|
||||||
/**
|
|
||||||
* The constant CORP_SECRET.
|
|
||||||
*/
|
|
||||||
public static final String CORP_SECRET = "xxxxxx";
|
|
||||||
/**
|
/**
|
||||||
* The constant PROVIDER_SECRET.
|
* The constant PROVIDER_SECRET.
|
||||||
*/
|
*/
|
||||||
public static final String PROVIDER_SECRET = CORP_SECRET;
|
public static final String PROVIDER_SECRET = "xxxxxx";
|
||||||
/**
|
/**
|
||||||
* The constant REDIS_ADDR.
|
* The constant REDIS_ADDR.
|
||||||
*/
|
*/
|
||||||
@@ -85,9 +83,15 @@ public class WxCpTpServiceApacheHttpClientImplTest {
|
|||||||
* @return the wx cp tp config storage
|
* @return the wx cp tp config storage
|
||||||
*/
|
*/
|
||||||
public WxCpTpConfigStorage wxCpTpConfigStorage() {
|
public WxCpTpConfigStorage wxCpTpConfigStorage() {
|
||||||
return WxCpTpRedissonConfigImpl.builder().baseApiUrl(API_URL).suiteId(SUITE_ID).suiteSecret(SUITE_SECRET)
|
WxCpTpRedissonConfigImpl wxCpTpRedissonConfig=new WxCpTpRedissonConfigImpl(redissonClient(),"");
|
||||||
.token(TOKEN).aesKey(AES_KEY).corpId(PROVIDER_CORP_ID).corpSecret(CORP_SECRET).providerSecret(PROVIDER_SECRET)
|
wxCpTpRedissonConfig.setBaseApiUrl(API_URL);
|
||||||
.wxRedisOps(new RedissonWxRedisOps(redissonClient())).build();
|
wxCpTpRedissonConfig.setSuiteId(SUITE_ID);
|
||||||
|
wxCpTpRedissonConfig.setSuiteSecret(SUITE_SECRET);
|
||||||
|
wxCpTpRedissonConfig.setToken(TOKEN);
|
||||||
|
wxCpTpRedissonConfig.setEncodingAESKey(AES_KEY);
|
||||||
|
wxCpTpRedissonConfig.setCorpId(PROVIDER_CORP_ID);
|
||||||
|
wxCpTpRedissonConfig.setProviderSecret(PROVIDER_SECRET);
|
||||||
|
return wxCpTpRedissonConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user