From 88bdd4a36e37c7627cfc271fab5a86bb63b38e10 Mon Sep 17 00:00:00 2001 From: graalvm-samples Date: Tue, 23 Sep 2025 00:25:39 +0800 Subject: [PATCH] =?UTF-8?q?:new:=20#3703=20=E3=80=90=E4=BC=81=E4=B8=9A?= =?UTF-8?q?=E5=BE=AE=E4=BF=A1=E3=80=91=E7=AC=AC=E4=B8=89=E6=96=B9=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E5=9F=BA=E7=A1=80=E6=8E=A5=E5=8F=A3=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 15 + spring-boot-starters/pom.xml | 1 + .../README.md | 97 ++++ .../pom.xml | 60 +++ .../WxCpTpMultiAutoConfiguration.java | 16 + .../WxCpTpMultiServicesAutoConfiguration.java | 27 ++ .../services/AbstractWxCpTpConfiguration.java | 139 ++++++ .../WxCpTpInJedisTpConfiguration.java | 78 +++ .../WxCpTpInMemoryTpConfiguration.java | 39 ++ .../WxCpTpInRedisTemplateTpConfiguration.java | 45 ++ .../WxCpTpInRedissonTpConfiguration.java | 68 +++ .../cp/properties/WxCpTpMultiProperties.java | 129 +++++ .../WxCpTpMultiRedisProperties.java | 48 ++ .../cp/properties/WxCpTpSingleProperties.java | 43 ++ .../cp/service/WxCpTpMultiServices.java | 29 ++ .../cp/service/WxCpTpMultiServicesImpl.java | 44 ++ .../main/resources/META-INF/spring.factories | 2 + ...ot.autoconfigure.AutoConfiguration.imports | 1 + .../cp/bean/message/WxCpTpXmlMessage.java | 2 + .../weixin/cp/config/WxCpTpConfigStorage.java | 2 +- .../impl/AbstractWxCpTpInRedisConfigImpl.java | 431 +++++++++++++++++ .../config/impl/WxCpTpDefaultConfigImpl.java | 62 ++- .../cp/config/impl/WxCpTpJedisConfigImpl.java | 24 + .../impl/WxCpTpRedisTemplateConfigImpl.java | 24 + .../config/impl/WxCpTpRedissonConfigImpl.java | 450 +----------------- .../weixin/cp/constant/WxCpApiPathConsts.java | 4 + .../weixin/cp/tp/service/WxCpTpService.java | 12 +- .../service/impl/BaseWxCpTpServiceImpl.java | 51 +- .../WxCpTpServiceApacheHttpClientImpl.java | 8 +- .../impl/WxCpTpServiceHttpComponentsImpl.java | 8 +- .../impl/WxCpTpServiceJoddHttpImpl.java | 104 ++++ .../service/impl/WxCpTpServiceOkHttpImpl.java | 130 +++++ .../cp/util/crypto/WxCpTpCryptUtil.java | 2 +- .../impl/BaseWxCpTpServiceImplTest.java | 7 +- ...WxCpTpServiceApacheHttpClientImplTest.java | 20 +- 35 files changed, 1730 insertions(+), 492 deletions(-) create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/README.md create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpTpMultiAutoConfiguration.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/WxCpTpMultiServicesAutoConfiguration.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpTpConfiguration.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInJedisTpConfiguration.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInMemoryTpConfiguration.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedisTemplateTpConfiguration.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedissonTpConfiguration.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiProperties.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiRedisProperties.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpSingleProperties.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServices.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServicesImpl.java create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories create mode 100644 spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpJedisConfigImpl.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedisTemplateConfigImpl.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceJoddHttpImpl.java create mode 100644 weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java diff --git a/pom.xml b/pom.xml index a872188ad..6b3075158 100644 --- a/pom.xml +++ b/pom.xml @@ -468,6 +468,21 @@ + + org.apache.maven.plugins + maven-source-plugin + 3.3.0 + + + attach-sources + + verify + + jar-no-fork + + + + diff --git a/spring-boot-starters/pom.xml b/spring-boot-starters/pom.xml index 6035f44a5..57c86d44e 100644 --- a/spring-boot-starters/pom.xml +++ b/spring-boot-starters/pom.xml @@ -26,6 +26,7 @@ wx-java-open-spring-boot-starter wx-java-qidian-spring-boot-starter wx-java-cp-multi-spring-boot-starter + wx-java-cp-tp-multi-spring-boot-starter wx-java-cp-spring-boot-starter wx-java-channel-spring-boot-starter wx-java-channel-multi-spring-boot-starter diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/README.md b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/README.md new file mode 100644 index 000000000..624c6b315 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/README.md @@ -0,0 +1,97 @@ +# wx-java-cp-multi-spring-boot-starter + +企业微信多账号配置 + +- 实现多 WxCpService 初始化。 +- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。 +- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。 + +## 快速开始 + +1. 引入依赖 + ```xml + + com.github.binarywang + wx-java-cp-multi-spring-boot-starter + ${version} + + ``` +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 ... + } +} +``` diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml new file mode 100644 index 000000000..514c3f11d --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/pom.xml @@ -0,0 +1,60 @@ + + + + wx-java-spring-boot-starters + com.github.binarywang + 4.7.7.B + + 4.0.0 + + wx-java-cp-tp-multi-spring-boot-starter + WxJava - Spring Boot Starter for WxCp::支持多账号配置 + 微信企业号开发的 Spring Boot Starter::支持多账号配置 + + + + com.github.binarywang + weixin-java-cp + ${project.version} + + + redis.clients + jedis + provided + + + org.redisson + redisson + provided + + + org.springframework.data + spring-data-redis + provided + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + attach-sources + + jar-no-fork + + + + + + + diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpTpMultiAutoConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpTpMultiAutoConfiguration.java new file mode 100644 index 000000000..1ec07c5c5 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpTpMultiAutoConfiguration.java @@ -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 { +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/WxCpTpMultiServicesAutoConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/WxCpTpMultiServicesAutoConfiguration.java new file mode 100644 index 000000000..1f6e78423 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/WxCpTpMultiServicesAutoConfiguration.java @@ -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 { +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpTpConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpTpConfiguration.java new file mode 100644 index 000000000..2404dee06 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/AbstractWxCpTpConfiguration.java @@ -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 corps = wxCpTpMultiProperties.getCorps(); + if (corps == null || corps.isEmpty()) { + log.warn("企业微信应用参数未配置,通过 WxCpMultiServices#getWxCpTpService(\"tenantId\")获取实例将返回空"); + return new WxCpTpMultiServicesImpl(); + } + + if (services == null) { + services = new WxCpTpMultiServicesImpl(); + } + + Set> entries = corps.entrySet(); + for (Map.Entry 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); + } + } + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInJedisTpConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInJedisTpConfiguration.java new file mode 100644 index 000000000..f3034ac00 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInJedisTpConfiguration.java @@ -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()); + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInMemoryTpConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInMemoryTpConfiguration.java new file mode 100644 index 000000000..5e460abb2 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInMemoryTpConfiguration.java @@ -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(); + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedisTemplateTpConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedisTemplateTpConfiguration.java new file mode 100644 index 000000000..1faa37862 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedisTemplateTpConfiguration.java @@ -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()); + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedissonTpConfiguration.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedissonTpConfiguration.java new file mode 100644 index 000000000..bd16db37e --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/configuration/services/WxCpTpInRedissonTpConfiguration.java @@ -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); + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiProperties.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiProperties.java new file mode 100644 index 000000000..771b1b6de --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiProperties.java @@ -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 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 请求最大重试次数 + *
+     *   {@link me.chanjar.weixin.cp.api.WxCpService#setMaxRetryTimes(int)}
+     *   {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setMaxRetryTimes(int)}
+     * 
+ */ + private int maxRetryTimes = 5; + + /** + * http 请求重试间隔 + *
+     *   {@link me.chanjar.weixin.cp.api.WxCpService#setRetrySleepMillis(int)}
+     *   {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setRetrySleepMillis(int)}
+     * 
+ */ + 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 + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiRedisProperties.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiRedisProperties.java new file mode 100644 index 000000000..b94711216 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpMultiRedisProperties.java @@ -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; +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpSingleProperties.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpSingleProperties.java new file mode 100644 index 000000000..02a52657d --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpTpSingleProperties.java @@ -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; + + +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServices.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServices.java new file mode 100644 index 000000000..c0a9faf51 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServices.java @@ -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); +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServicesImpl.java b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServicesImpl.java new file mode 100644 index 000000000..84b381230 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpTpMultiServicesImpl.java @@ -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 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); + } +} diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..9d1110722 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.binarywang.spring.starter.wxjava.cp.autoconfigure.WxCpTpMultiAutoConfiguration diff --git a/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 000000000..5de0e9f13 --- /dev/null +++ b/spring-boot-starters/wx-java-cp-tp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1 @@ +com.binarywang.spring.starter.wxjava.cp.autoconfigure.WxCpTpMultiAutoConfiguration diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java index e26b152da..122bc2627 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/bean/message/WxCpTpXmlMessage.java @@ -793,4 +793,6 @@ public class WxCpTpXmlMessage implements Serializable { log.debug("解密后的原始xml消息内容:{}", plainText); return fromXml(plainText); } + + } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java index b4316e7e8..2cd37821b 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/WxCpTpConfigStorage.java @@ -130,7 +130,7 @@ public interface WxCpTpConfigStorage { * @return the aes key */ //第三方应用的EncodingAESKey,用来检查签名 - String getAesKey(); + String getEncodingAESKey(); /** * 企微服务商企业ID & 企业secret diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java new file mode 100644 index 000000000..08eed33c1 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/AbstractWxCpTpInRedisConfigImpl.java @@ -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; + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java index 5d0882591..fc124251f 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpDefaultConfigImpl.java @@ -28,20 +28,32 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl private final transient Map accessTokenLocker = new ConcurrentHashMap<>(); private final transient Map authCorpJsapiTicketLocker = new ConcurrentHashMap<>(); private final transient Map authSuiteJsapiTicketLocker = new ConcurrentHashMap<>(); - private volatile String corpId; - private volatile String corpSecret; + /** + * 企微服务商企业ID & 企业secret,来自于企微配置 + */ + protected volatile String corpId; /** * 服务商secret */ - private volatile String providerSecret; + protected volatile String providerSecret; private volatile String providerToken; 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 long suiteAccessTokenExpiresTime; - private volatile String aesKey; + /** + * 第三方应用的EncodingAESKey,用来检查签名 + */ + protected volatile String encodingAESKey; private volatile String suiteTicket; private volatile long suiteTicketExpiresTime; private volatile String oauth2redirectUri; @@ -186,11 +198,10 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl /** * Sets suite id. * - * @param corpId the corp id + * @param suiteId */ - @Deprecated - public void setSuiteId(String corpId) { - this.suiteId = corpId; + public void setSuiteId(String suiteId) { + this.suiteId = suiteId; } @Override @@ -200,10 +211,7 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl /** * Sets suite secret. - * - * @param corpSecret the corp secret */ - @Deprecated public void setSuiteSecret(String corpSecret) { this.suiteSecret = corpSecret; } @@ -218,24 +226,22 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl * * @param token the token */ - @Deprecated public void setToken(String token) { this.token = token; } @Override - public String getAesKey() { - return this.aesKey; + public String getEncodingAESKey() { + return this.encodingAESKey; } /** - * Sets aes key. + * Sets aes key. encodingAESKey * - * @param aesKey the aes key + * @param encodingAESKey the aes key */ - @Deprecated - public void setAesKey(String aesKey) { - this.aesKey = aesKey; + public void setEncodingAESKey(String encodingAESKey) { + this.encodingAESKey = encodingAESKey; } @@ -249,25 +255,15 @@ public class WxCpTpDefaultConfigImpl implements WxCpTpConfigStorage, Serializabl * * @param corpId the corp id */ - @Deprecated public void setCorpId(String corpId) { this.corpId = corpId; } @Override 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 public void setProviderSecret(String providerSecret) { diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpJedisConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpJedisConfigImpl.java new file mode 100644 index 000000000..045761256 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpJedisConfigImpl.java @@ -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 jedisPool) { + this(jedisPool, null); + } + + public WxCpTpJedisConfigImpl(@NonNull Pool jedisPool, String keyPrefix) { + super(new JedisWxRedisOps(jedisPool), keyPrefix); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedisTemplateConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedisTemplateConfigImpl.java new file mode 100644 index 000000000..0a94afc79 --- /dev/null +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedisTemplateConfigImpl.java @@ -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); + } +} diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java index d483bfd53..fe8723a8a 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/config/impl/WxCpTpRedissonConfigImpl.java @@ -1,449 +1,25 @@ package me.chanjar.weixin.cp.config.impl; - import lombok.Builder; +import lombok.Data; import lombok.NonNull; -import lombok.Setter; -import me.chanjar.weixin.common.bean.WxAccessToken; -import me.chanjar.weixin.common.redis.WxRedisOps; -import me.chanjar.weixin.common.util.http.apache.ApacheHttpClientBuilder; -import me.chanjar.weixin.cp.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; +import me.chanjar.weixin.common.redis.RedissonWxRedisOps; +import org.redisson.api.RedissonClient; /** - * 企业微信各种固定、授权配置的Redisson存储实现 + * 基于Redisson的实现 + * + * @author yuanqixun created on 2020 /5/13 + * @author yl */ -@Builder -public class WxCpTpRedissonConfigImpl implements WxCpTpConfigStorage, Serializable { - private static final long serialVersionUID = -5385639031981770319L; +public class WxCpTpRedissonConfigImpl extends AbstractWxCpTpInRedisConfigImpl { + private static final long serialVersionUID = -5674792341070783967L; - /** - * 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; - /** - * 第三方应用的其他配置,来自于企微配置 - */ - 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; + public WxCpTpRedissonConfigImpl(@NonNull RedissonClient redissonClient) { + this(redissonClient, null); } - @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 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; + public WxCpTpRedissonConfigImpl(@NonNull RedissonClient redissonClient, String keyPrefix) { + super(new RedissonWxRedisOps(redissonClient), keyPrefix); } } diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java index 093d386e6..fb6f8ef68 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/constant/WxCpApiPathConsts.java @@ -855,6 +855,10 @@ public interface WxCpApiPathConsts { * The constant 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. */ diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java index 5c433c0b4..b24be535d 100644 --- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java +++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/WxCpTpService.java @@ -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.RequestHttp; import me.chanjar.weixin.cp.bean.*; +import me.chanjar.weixin.cp.bean.message.WxCpTpXmlMessage; import me.chanjar.weixin.cp.config.WxCpTpConfigStorage; import java.util.List; @@ -186,6 +187,8 @@ public interface WxCpTpService { @Deprecated WxCpTpCorp getPermanentCode(String authCode) throws WxErrorException; + WxCpTpCorp getV2PermanentCode(String authCode) throws WxErrorException; + /** * 获取企业永久授权码信息 *
@@ -200,6 +203,8 @@ public interface WxCpTpService {
    */
   WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException;
 
+  WxCpTpPermanentCodeInfo getV2PermanentCodeInfo(String authCode) throws WxErrorException;
+
   /**
    * 
    *   获取预授权链接
@@ -343,9 +348,7 @@ public interface WxCpTpService {
    * 获取WxCpTpConfigStorage 对象.
    *
    * @return WxCpTpConfigStorage wx cp tp config storage
-   * @deprecated storage应该在service内部使用 ,提供这个接口,容易破坏这个封装
    */
-  @Deprecated
   WxCpTpConfigStorage getWxCpTpConfigStorage();
 
   /**
@@ -527,6 +530,11 @@ public interface WxCpTpService {
    */
   WxCpTpLicenseService getWxCpTpLicenseService();
 
+  WxCpTpXmlMessage fromEncryptedXml(String encryptedXml,
+                                    String timestamp, String nonce, String msgSignature);
+
+  String getVerifyDecrypt(String sVerifyEchoStr);
+
   /**
    * 获取应用的管理员列表
    *
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
index 9d620264c..f8f554b81 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImpl.java
@@ -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.WxGsonBuilder;
 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.tp.service.*;
+import me.chanjar.weixin.cp.util.crypto.WxCpTpCryptUtil;
 import org.apache.commons.lang3.StringUtils;
 
 import java.io.File;
@@ -91,6 +93,7 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
 
   private final WxSessionManager sessionManager = new StandardSessionManager();
 
+
   /**
    * 临时文件目录.
    */
@@ -259,6 +262,18 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
     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
   public WxCpTpPermanentCodeInfo getPermanentCodeInfo(String authCode) throws WxErrorException {
     JsonObject jsonObject = new JsonObject();
@@ -267,6 +282,14 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
     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
   @SneakyThrows
   public String getPreAuthUrl(String redirectUri, String state) throws WxErrorException {
@@ -452,7 +475,7 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
       if (error.getErrorCode() == WxCpErrorMsgEnum.CODE_42009.getCode()) {
         // 强制设置wxCpTpConfigStorage它的suite access token过期了,这样在下一次请求里就会刷新suite access token
         this.configStorage.expireSuiteAccessToken();
-        if (this.getWxCpTpConfigStorage().autoRefreshToken()) {
+        if (this.configStorage.autoRefreshToken()) {
           log.warn("即将重新获取新的access_token,错误代码:{},错误信息:{}", error.getErrorCode(), error.getErrorMsg());
           return this.execute(executor, uri, data);
         }
@@ -646,6 +669,27 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
     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
   public WxCpTpAdmin getAdminList(String authCorpId, Integer agentId) throws WxErrorException {
     JsonObject jsonObject = new JsonObject();
@@ -764,4 +808,9 @@ public abstract class BaseWxCpTpServiceImpl implements WxCpTpService, Requ
   public void setWxCpTpOAuth2Service(WxCpTpOAuth2Service wxCpTpOAuth2Service) {
     this.wxCpTpOAuth2Service = wxCpTpOAuth2Service;
   }
+
+  @Override
+  public WxCpTpConfigStorage getWxCpTpConfigStorage() {
+    return this.configStorage;
+  }
 }
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java
index a5948a99c..a28751333 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImpl.java
@@ -101,9 +101,9 @@ public class WxCpTpServiceApacheHttpClientImpl extends BaseWxCpTpServiceImpl {
+  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;
+//  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java
new file mode 100644
index 000000000..63d5e95ba
--- /dev/null
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceOkHttpImpl.java
@@ -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 {
+  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;
+//  }
+}
diff --git a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpTpCryptUtil.java b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpTpCryptUtil.java
index ac6097446..4e9783e3e 100644
--- a/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpTpCryptUtil.java
+++ b/weixin-java-cp/src/main/java/me/chanjar/weixin/cp/util/crypto/WxCpTpCryptUtil.java
@@ -23,7 +23,7 @@ public class WxCpTpCryptUtil extends WxCryptUtil {
      * @param encodingAesKey 公众平台上,开发者设置的EncodingAESKey
      * @param appidOrCorpid          公众平台corpId
      */
-    String encodingAesKey = wxCpTpConfigStorage.getAesKey();
+    String encodingAesKey = wxCpTpConfigStorage.getEncodingAESKey();
     String token = wxCpTpConfigStorage.getToken();
     String corpId = wxCpTpConfigStorage.getCorpId();
 
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java
index 6f2639c89..c0fc2d614 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/BaseWxCpTpServiceImplTest.java
@@ -9,6 +9,8 @@ import me.chanjar.weixin.cp.bean.WxCpTpPermanentCodeInfo;
 import me.chanjar.weixin.cp.bean.WxTpCustomizedAuthUrl;
 import me.chanjar.weixin.cp.config.WxCpTpConfigStorage;
 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.tp.service.WxCpTpService;
 import org.mockito.Mockito;
@@ -69,7 +71,10 @@ public class BaseWxCpTpServiceImplTest {
    * @return the wx cp tp config storage
    */
   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;
   }
 
   /**
diff --git a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImplTest.java b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImplTest.java
index 75927af4d..b46ddf003 100644
--- a/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImplTest.java
+++ b/weixin-java-cp/src/test/java/me/chanjar/weixin/cp/tp/service/impl/WxCpTpServiceApacheHttpClientImplTest.java
@@ -6,6 +6,8 @@ import me.chanjar.weixin.common.redis.RedissonWxRedisOps;
 import me.chanjar.weixin.cp.bean.WxCpProviderToken;
 import me.chanjar.weixin.cp.bean.WxCpTpCorpId2OpenCorpId;
 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.tp.service.WxCpTpService;
 import org.apache.commons.lang3.StringUtils;
@@ -48,14 +50,10 @@ public class WxCpTpServiceApacheHttpClientImplTest {
    * The constant PROVIDER_CORP_ID.
    */
   public static final String PROVIDER_CORP_ID = "xxxxxx";
-  /**
-   * The constant CORP_SECRET.
-   */
-  public static final String CORP_SECRET = "xxxxxx";
   /**
    * The constant PROVIDER_SECRET.
    */
-  public static final String PROVIDER_SECRET = CORP_SECRET;
+  public static final String PROVIDER_SECRET = "xxxxxx";
   /**
    * The constant REDIS_ADDR.
    */
@@ -85,9 +83,15 @@ public class WxCpTpServiceApacheHttpClientImplTest {
    * @return the wx cp tp config storage
    */
   public WxCpTpConfigStorage wxCpTpConfigStorage() {
-    return WxCpTpRedissonConfigImpl.builder().baseApiUrl(API_URL).suiteId(SUITE_ID).suiteSecret(SUITE_SECRET)
-      .token(TOKEN).aesKey(AES_KEY).corpId(PROVIDER_CORP_ID).corpSecret(CORP_SECRET).providerSecret(PROVIDER_SECRET)
-      .wxRedisOps(new RedissonWxRedisOps(redissonClient())).build();
+    WxCpTpRedissonConfigImpl wxCpTpRedissonConfig=new WxCpTpRedissonConfigImpl(redissonClient(),"");
+    wxCpTpRedissonConfig.setBaseApiUrl(API_URL);
+    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;
   }
 
   /**