Sfoglia il codice sorgente

:new: #3149 [企业微信] 增加支持企业微信多账号配置的starter

Forever杨 1 anno fa
parent
commit
82c1e9a120
18 ha cambiato i file con 830 aggiunte e 3 eliminazioni
  1. 4 1
      spring-boot-starters/pom.xml
  2. 85 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/README.md
  3. 60 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml
  4. 20 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpMultiAutoConfiguration.java
  5. 26 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpMultiServicesAutoConfiguration.java
  6. 142 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/services/AbstractWxCpConfiguration.java
  7. 76 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/services/WxCpInJedisConfiguration.java
  8. 38 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/services/WxCpInMemoryConfiguration.java
  9. 43 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/services/WxCpInRedisTemplateConfiguration.java
  10. 67 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/services/WxCpInRedissonConfiguration.java
  11. 43 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/CorpProperties.java
  12. 108 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpMultiProperties.java
  13. 46 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpMultiRedisProperties.java
  14. 26 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpMultiServices.java
  15. 42 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpMultiServicesImpl.java
  16. 2 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/resources/META-INF/spring.factories
  17. 1 0
      spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  18. 1 2
      weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java

+ 4 - 1
spring-boot-starters/pom.xml

@@ -1,5 +1,7 @@
 <?xml version="1.0"?>
-<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0">
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
+         xmlns="http://maven.apache.org/POM/4.0.0">
   <modelVersion>4.0.0</modelVersion>
   <parent>
     <groupId>com.github.binarywang</groupId>
@@ -21,6 +23,7 @@
     <module>wx-java-pay-spring-boot-starter</module>
     <module>wx-java-open-spring-boot-starter</module>
     <module>wx-java-qidian-spring-boot-starter</module>
+    <module>wx-java-cp-multi-spring-boot-starter</module>
     <module>wx-java-cp-spring-boot-starter</module>
     <module>wx-java-channel-spring-boot-starter</module>
   </modules>

+ 85 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/README.md

@@ -0,0 +1,85 @@
+# wx-java-cp-multi-spring-boot-starter
+
+企业微信多账号配置
+
+- 实现多 WxCpService 初始化。
+- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
+- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
+
+## 快速开始
+
+1. 引入依赖
+    ```xml
+    <dependency>
+        <groupId>com.github.binarywang</groupId>
+        <artifactId>wx-java-cp-multi-spring-boot-starter</artifactId>
+        <version>${version}</version>
+    </dependency>
+    ```
+2. 添加配置(application.properties)
+    ```properties
+    # 应用 1 配置
+    wx.cp.corps.tenantId1.corp-id = @corp-id
+    wx.cp.corps.tenantId1.corp-secret = @corp-secret
+    ## 选填
+    wx.cp.corps.tenantId1.agent-id = @agent-id
+    wx.cp.corps.tenantId1.token = @token
+    wx.cp.corps.tenantId1.aes-key = @aes-key
+    wx.cp.corps.tenantId1.msg-audit-priKey = @msg-audit-priKey
+    wx.cp.corps.tenantId1.msg-audit-lib-path = @msg-audit-lib-path
+
+    # 应用 2 配置
+    wx.cp.corps.tenantId2.corp-id = @corp-id
+    wx.cp.corps.tenantId2.corp-secret = @corp-secret
+    ## 选填
+    wx.cp.corps.tenantId2.agent-id = @agent-id
+    wx.cp.corps.tenantId2.token = @token
+    wx.cp.corps.tenantId2.aes-key = @aes-key
+    wx.cp.corps.tenantId2.msg-audit-priKey = @msg-audit-priKey
+    wx.cp.corps.tenantId2.msg-audit-lib-path = @msg-audit-lib-path
+
+    # 公共配置
+    ## ConfigStorage 配置(选填)
+    wx.cp.config-storage.type=memory # 配置类型: memory(默认), jedis, redisson, redistemplate
+    ## http 客户端配置(选填)
+    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.WxCpMultiServices;
+import com.binarywang.spring.starter.wxjava.cp.service.WxCpServices;
+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 WxCpMultiServices wxCpMultiServices;
+
+  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 ...
+  }
+}
+```

+ 60 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>wx-java-spring-boot-starters</artifactId>
+    <groupId>com.github.binarywang</groupId>
+    <version>4.5.5.B</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>wx-java-cp-multi-spring-boot-starter</artifactId>
+  <name>WxJava - Spring Boot Starter for WxCp::支持多账号配置</name>
+  <description>微信企业号开发的 Spring Boot Starter::支持多账号配置</description>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.github.binarywang</groupId>
+      <artifactId>weixin-java-cp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>redis.clients</groupId>
+      <artifactId>jedis</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.redisson</groupId>
+      <artifactId>redisson</artifactId>
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.data</groupId>
+      <artifactId>spring-data-redis</artifactId>
+      <scope>provided</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-maven-plugin</artifactId>
+        <version>${spring.boot.version}</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <version>2.2.1</version>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <goals>
+              <goal>jar-no-fork</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>

+ 20 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpMultiAutoConfiguration.java

@@ -0,0 +1,20 @@
+package com.binarywang.spring.starter.wxjava.cp.autoconfigure;
+
+import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties;
+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(WxCpMultiProperties.class)
+@Import({
+  WxCpMultiServicesAutoConfiguration.class
+})
+public class WxCpMultiAutoConfiguration {
+}

+ 26 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/WxCpMultiServicesAutoConfiguration.java

@@ -0,0 +1,26 @@
+package com.binarywang.spring.starter.wxjava.cp.autoconfigure;
+
+import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInJedisConfiguration;
+import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInMemoryConfiguration;
+import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInRedisTemplateConfiguration;
+import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInRedissonConfiguration;
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+
+/**
+ * 企业微信平台相关服务自动注册
+ *
+ * @author yl
+ * created on 2023/10/16
+ */
+@Configuration
+@RequiredArgsConstructor
+@Import({
+  WxCpInJedisConfiguration.class,
+  WxCpInMemoryConfiguration.class,
+  WxCpInRedissonConfiguration.class,
+  WxCpInRedisTemplateConfiguration.class
+})
+public class WxCpMultiServicesAutoConfiguration {
+}

+ 142 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/services/AbstractWxCpConfiguration.java

@@ -0,0 +1,142 @@
+package com.binarywang.spring.starter.wxjava.cp.autoconfigure.services;
+
+import com.binarywang.spring.starter.wxjava.cp.properties.CorpProperties;
+import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties;
+import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices;
+import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServicesImpl;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.cp.api.WxCpService;
+import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
+import me.chanjar.weixin.cp.config.WxCpConfigStorage;
+import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
+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
+public abstract class AbstractWxCpConfiguration {
+
+  protected WxCpMultiServices configWxCpServices(WxCpMultiProperties wxCpMultiProperties) {
+    WxCpMultiServicesImpl wxCpServices = new WxCpMultiServicesImpl();
+    Map<String, CorpProperties> corps = wxCpMultiProperties.getCorps();
+    if (corps == null || corps.isEmpty()) {
+      throw new RuntimeException("企业微信配置为null");
+    }
+    /**
+     * 校验同一个企业下,agentId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
+     *
+     * 查看 {@link me.chanjar.weixin.cp.config.impl.AbstractWxCpInRedisConfigImpl#setAgentId(Integer)}
+     */
+    Collection<CorpProperties> corpList = corps.values();
+    if (corpList.size() > 1) {
+      // 先按 corpId 分组统计
+      Map<String, List<CorpProperties>> corpsMap = corpList.stream()
+        .collect(Collectors.groupingBy(CorpProperties::getCorpId));
+      Set<Map.Entry<String, List<CorpProperties>>> entries = corpsMap.entrySet();
+      for (Map.Entry<String, List<CorpProperties>> entry : entries) {
+        String corpId = entry.getKey();
+        // 校验每个企业下,agentId 是否唯一
+        boolean multi = entry.getValue().stream()
+          // 通讯录没有 agentId,如果不判断是否为空,这里会报 NPE 异常
+          .collect(Collectors.groupingBy(c -> c.getAgentId() == null ? 0 : c.getAgentId(), Collectors.counting()))
+          .entrySet().stream().anyMatch(e -> e.getValue() > 1);
+        if (multi) {
+          throw new RuntimeException("请确保企业微信配置唯一性[" + corpId + "]");
+        }
+      }
+    }
+
+    Set<Map.Entry<String, CorpProperties>> entries = corps.entrySet();
+    for (Map.Entry<String, CorpProperties> entry : entries) {
+      String tenantId = entry.getKey();
+      CorpProperties corpProperties = entry.getValue();
+      WxCpDefaultConfigImpl storage = this.configWxCpDefaultConfigImpl(wxCpMultiProperties);
+      this.configCorp(storage, corpProperties);
+      this.configHttp(storage, wxCpMultiProperties.getConfigStorage());
+      WxCpService wxCpService = this.configWxCpService(storage, wxCpMultiProperties.getConfigStorage());
+      wxCpServices.addWxCpService(tenantId, wxCpService);
+    }
+    return wxCpServices;
+  }
+
+  /**
+   * 配置 WxCpDefaultConfigImpl
+   *
+   * @param wxCpMultiProperties 参数
+   * @return WxCpDefaultConfigImpl
+   */
+  protected abstract WxCpDefaultConfigImpl configWxCpDefaultConfigImpl(WxCpMultiProperties wxCpMultiProperties);
+
+  private WxCpService configWxCpService(WxCpConfigStorage wxCpConfigStorage, WxCpMultiProperties.ConfigStorage storage) {
+    WxCpService wxCpService = new WxCpServiceImpl();
+    wxCpService.setWxCpConfigStorage(wxCpConfigStorage);
+
+    int maxRetryTimes = storage.getMaxRetryTimes();
+    if (maxRetryTimes < 0) {
+      maxRetryTimes = 0;
+    }
+    int retrySleepMillis = storage.getRetrySleepMillis();
+    if (retrySleepMillis < 0) {
+      retrySleepMillis = 1000;
+    }
+    wxCpService.setRetrySleepMillis(retrySleepMillis);
+    wxCpService.setMaxRetryTimes(maxRetryTimes);
+    return wxCpService;
+  }
+
+  private void configCorp(WxCpDefaultConfigImpl config, CorpProperties corpProperties) {
+    String corpId = corpProperties.getCorpId();
+    String corpSecret = corpProperties.getCorpSecret();
+    Integer agentId = corpProperties.getAgentId();
+    String token = corpProperties.getToken();
+    String aesKey = corpProperties.getAesKey();
+    // 企业微信,私钥,会话存档路径
+    String msgAuditPriKey = corpProperties.getMsgAuditPriKey();
+    String msgAuditLibPath = corpProperties.getMsgAuditLibPath();
+
+    config.setCorpId(corpId);
+    config.setCorpSecret(corpSecret);
+    config.setAgentId(agentId);
+    if (StringUtils.isNotBlank(token)) {
+      config.setToken(token);
+    }
+    if (StringUtils.isNotBlank(aesKey)) {
+      config.setAesKey(aesKey);
+    }
+    if (StringUtils.isNotBlank(msgAuditPriKey)) {
+      config.setMsgAuditPriKey(msgAuditPriKey);
+    }
+    if (StringUtils.isNotBlank(msgAuditLibPath)) {
+      config.setMsgAuditLibPath(msgAuditLibPath);
+    }
+  }
+
+  private void configHttp(WxCpDefaultConfigImpl config, WxCpMultiProperties.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);
+      }
+    }
+  }
+}

+ 76 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/services/WxCpInJedisConfiguration.java

@@ -0,0 +1,76 @@
+package com.binarywang.spring.starter.wxjava.cp.autoconfigure.services;
+
+import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties;
+import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiRedisProperties;
+import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
+import me.chanjar.weixin.cp.config.impl.WxCpJedisConfigImpl;
+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 = WxCpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "jedis"
+)
+@RequiredArgsConstructor
+public class WxCpInJedisConfiguration extends AbstractWxCpConfiguration {
+  private final WxCpMultiProperties wxCpMultiProperties;
+  private final ApplicationContext applicationContext;
+
+  @Bean
+  public WxCpMultiServices wxCpServices() {
+    return this.configWxCpServices(wxCpMultiProperties);
+  }
+
+  @Override
+  protected WxCpDefaultConfigImpl configWxCpDefaultConfigImpl(WxCpMultiProperties wxCpMultiProperties) {
+    return this.configRedis(wxCpMultiProperties);
+  }
+
+  private WxCpDefaultConfigImpl configRedis(WxCpMultiProperties wxCpMultiProperties) {
+    WxCpMultiRedisProperties wxCpMultiRedisProperties = wxCpMultiProperties.getConfigStorage().getRedis();
+    JedisPool jedisPool;
+    if (wxCpMultiRedisProperties != null && StringUtils.isNotEmpty(wxCpMultiRedisProperties.getHost())) {
+      jedisPool = getJedisPool(wxCpMultiProperties);
+    } else {
+      jedisPool = applicationContext.getBean(JedisPool.class);
+    }
+    return new WxCpJedisConfigImpl(jedisPool, wxCpMultiProperties.getConfigStorage().getKeyPrefix());
+  }
+
+  private JedisPool getJedisPool(WxCpMultiProperties wxCpMultiProperties) {
+    WxCpMultiProperties.ConfigStorage storage = wxCpMultiProperties.getConfigStorage();
+    WxCpMultiRedisProperties 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());
+  }
+}

+ 38 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/services/WxCpInMemoryConfiguration.java

@@ -0,0 +1,38 @@
+package com.binarywang.spring.starter.wxjava.cp.autoconfigure.services;
+
+import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties;
+import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
+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 = WxCpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "memory", matchIfMissing = true
+)
+@RequiredArgsConstructor
+public class WxCpInMemoryConfiguration extends AbstractWxCpConfiguration {
+  private final WxCpMultiProperties wxCpMultiProperties;
+
+  @Bean
+  public WxCpMultiServices wxCpServices() {
+    return this.configWxCpServices(wxCpMultiProperties);
+  }
+
+  @Override
+  protected WxCpDefaultConfigImpl configWxCpDefaultConfigImpl(WxCpMultiProperties wxCpMultiProperties) {
+    return this.configInMemory();
+  }
+
+  private WxCpDefaultConfigImpl configInMemory() {
+    return new WxCpDefaultConfigImpl();
+  }
+}

+ 43 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/services/WxCpInRedisTemplateConfiguration.java

@@ -0,0 +1,43 @@
+package com.binarywang.spring.starter.wxjava.cp.autoconfigure.services;
+
+import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties;
+import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
+import me.chanjar.weixin.cp.config.impl.WxCpRedisTemplateConfigImpl;
+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 = WxCpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redistemplate"
+)
+@RequiredArgsConstructor
+public class WxCpInRedisTemplateConfiguration extends AbstractWxCpConfiguration {
+  private final WxCpMultiProperties wxCpMultiProperties;
+  private final ApplicationContext applicationContext;
+
+  @Bean
+  public WxCpMultiServices wxCpServices() {
+    return this.configWxCpServices(wxCpMultiProperties);
+  }
+
+  @Override
+  protected WxCpDefaultConfigImpl configWxCpDefaultConfigImpl(WxCpMultiProperties wxCpMultiProperties) {
+    return this.configRedisTemplate(wxCpMultiProperties);
+  }
+
+  private WxCpDefaultConfigImpl configRedisTemplate(WxCpMultiProperties wxCpMultiProperties) {
+    StringRedisTemplate redisTemplate = applicationContext.getBean(StringRedisTemplate.class);
+    return new WxCpRedisTemplateConfigImpl(redisTemplate, wxCpMultiProperties.getConfigStorage().getKeyPrefix());
+  }
+}

+ 67 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/autoconfigure/services/WxCpInRedissonConfiguration.java

@@ -0,0 +1,67 @@
+package com.binarywang.spring.starter.wxjava.cp.autoconfigure.services;
+
+import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties;
+import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiRedisProperties;
+import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices;
+import lombok.RequiredArgsConstructor;
+import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
+import me.chanjar.weixin.cp.config.impl.WxCpRedissonConfigImpl;
+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 = WxCpMultiProperties.PREFIX + ".config-storage", name = "type", havingValue = "redisson"
+)
+@RequiredArgsConstructor
+public class WxCpInRedissonConfiguration extends AbstractWxCpConfiguration {
+  private final WxCpMultiProperties wxCpMultiProperties;
+  private final ApplicationContext applicationContext;
+
+  @Bean
+  public WxCpMultiServices wxCpServices() {
+    return this.configWxCpServices(wxCpMultiProperties);
+  }
+
+  @Override
+  protected WxCpDefaultConfigImpl configWxCpDefaultConfigImpl(WxCpMultiProperties wxCpMultiProperties) {
+    return this.configRedisson(wxCpMultiProperties);
+  }
+
+  private WxCpDefaultConfigImpl configRedisson(WxCpMultiProperties wxCpMultiProperties) {
+    WxCpMultiRedisProperties redisProperties = wxCpMultiProperties.getConfigStorage().getRedis();
+    RedissonClient redissonClient;
+    if (redisProperties != null && StringUtils.isNotEmpty(redisProperties.getHost())) {
+      redissonClient = getRedissonClient(wxCpMultiProperties);
+    } else {
+      redissonClient = applicationContext.getBean(RedissonClient.class);
+    }
+    return new WxCpRedissonConfigImpl(redissonClient, wxCpMultiProperties.getConfigStorage().getKeyPrefix());
+  }
+
+  private RedissonClient getRedissonClient(WxCpMultiProperties wxCpMultiProperties) {
+    WxCpMultiProperties.ConfigStorage storage = wxCpMultiProperties.getConfigStorage();
+    WxCpMultiRedisProperties 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);
+  }
+}

+ 43 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/CorpProperties.java

@@ -0,0 +1,43 @@
+package com.binarywang.spring.starter.wxjava.cp.properties;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 企业微信企业相关配置属性
+ *
+ * @author yl
+ * created on 2023/10/16
+ */
+@Data
+@NoArgsConstructor
+public class CorpProperties {
+  /**
+   * 微信企业号 corpId
+   */
+  private String corpId;
+  /**
+   * 微信企业号 corpSecret
+   */
+  private String corpSecret;
+  /**
+   * 微信企业号应用 token
+   */
+  private String token;
+  /**
+   * 微信企业号应用 ID
+   */
+  private Integer agentId;
+  /**
+   * 微信企业号应用 EncodingAESKey
+   */
+  private String aesKey;
+  /**
+   * 微信企业号应用 会话存档私钥
+   */
+  private String msgAuditPriKey;
+  /**
+   * 微信企业号应用 会话存档类库路径
+   */
+  private String msgAuditLibPath;
+}

+ 108 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpMultiProperties.java

@@ -0,0 +1,108 @@
+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 = WxCpMultiProperties.PREFIX)
+public class WxCpMultiProperties {
+  public static final String PREFIX = "wx.cp";
+
+  private Map<String, CorpProperties> 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";
+
+    /**
+     * redis连接配置
+     */
+    @NestedConfigurationProperty
+    private WxCpMultiRedisProperties redis = new WxCpMultiRedisProperties();
+
+    /**
+     * http代理主机
+     */
+    private String httpProxyHost;
+
+    /**
+     * http代理端口
+     */
+    private Integer httpProxyPort;
+
+    /**
+     * http代理用户名
+     */
+    private String httpProxyUsername;
+
+    /**
+     * http代理密码
+     */
+    private String httpProxyPassword;
+
+    /**
+     * http 请求最大重试次数
+     * <pre>
+     *   {@link me.chanjar.weixin.cp.api.WxCpService#setMaxRetryTimes(int)}
+     *   {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setMaxRetryTimes(int)}
+     * </pre>
+     */
+    private int maxRetryTimes = 5;
+
+    /**
+     * http 请求重试间隔
+     * <pre>
+     *   {@link me.chanjar.weixin.cp.api.WxCpService#setRetrySleepMillis(int)}
+     *   {@link me.chanjar.weixin.cp.api.impl.BaseWxCpServiceImpl#setRetrySleepMillis(int)}
+     * </pre>
+     */
+    private int retrySleepMillis = 1000;
+  }
+
+  public enum StorageType {
+    /**
+     * 内存
+     */
+    memory,
+    /**
+     * jedis
+     */
+    jedis,
+    /**
+     * redisson
+     */
+    redisson,
+    /**
+     * redistemplate
+     */
+    redistemplate
+  }
+}

+ 46 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/properties/WxCpMultiRedisProperties.java

@@ -0,0 +1,46 @@
+package com.binarywang.spring.starter.wxjava.cp.properties;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * Redis配置.
+ *
+ * @author yl
+ * created on 2023/10/16
+ */
+@Data
+public class WxCpMultiRedisProperties 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;
+}

+ 26 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpMultiServices.java

@@ -0,0 +1,26 @@
+package com.binarywang.spring.starter.wxjava.cp.service;
+
+import me.chanjar.weixin.cp.api.WxCpService;
+
+/**
+ * 企业微信 {@link WxCpService} 所有实例存放类.
+ *
+ * @author yl
+ * created on 2023/10/16
+ */
+public interface WxCpMultiServices {
+  /**
+   * 通过租户 Id 获取 WxCpService
+   *
+   * @param tenantId 租户 Id
+   * @return WxCpService
+   */
+  WxCpService getWxCpService(String tenantId);
+
+  /**
+   * 根据租户 Id,从列表中移除一个 WxCpService 实例
+   *
+   * @param tenantId 租户 Id
+   */
+  void removeWxCpService(String tenantId);
+}

+ 42 - 0
spring-boot-starters/wx-java-cp-multi-spring-boot-starter/src/main/java/com/binarywang/spring/starter/wxjava/cp/service/WxCpMultiServicesImpl.java

@@ -0,0 +1,42 @@
+package com.binarywang.spring.starter.wxjava.cp.service;
+
+import me.chanjar.weixin.cp.api.WxCpService;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 企业微信 {@link WxCpMultiServices} 默认实现
+ *
+ * @author yl
+ * created on 2023/10/16
+ */
+public class WxCpMultiServicesImpl implements WxCpMultiServices {
+  private final Map<String, WxCpService> services = new ConcurrentHashMap<>();
+
+  /**
+   * 通过租户 Id 获取 WxCpService
+   *
+   * @param tenantId 租户 Id
+   * @return WxCpService
+   */
+  @Override
+  public WxCpService getWxCpService(String tenantId) {
+    return this.services.get(tenantId);
+  }
+
+  /**
+   * 根据租户 Id,添加一个 WxCpService 到列表
+   *
+   * @param tenantId    租户 Id
+   * @param wxCpService WxCpService 实例
+   */
+  public void addWxCpService(String tenantId, WxCpService wxCpService) {
+    this.services.put(tenantId, wxCpService);
+  }
+
+  @Override
+  public void removeWxCpService(String tenantId) {
+    this.services.remove(tenantId);
+  }
+}

+ 2 - 0
spring-boot-starters/wx-java-cp-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.WxCpMultiAutoConfiguration

+ 1 - 0
spring-boot-starters/wx-java-cp-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.WxCpMultiAutoConfiguration

+ 1 - 2
weixin-java-cp/src/main/java/me/chanjar/weixin/cp/api/WxCpService.java

@@ -12,7 +12,6 @@ import me.chanjar.weixin.cp.bean.WxCpAgentJsapiSignature;
 import me.chanjar.weixin.cp.bean.WxCpMaJsCode2SessionResult;
 import me.chanjar.weixin.cp.bean.WxCpProviderToken;
 import me.chanjar.weixin.cp.config.WxCpConfigStorage;
-import me.chanjar.weixin.cp.corpgroup.service.WxCpLinkedCorpService;
 
 /**
  * 微信API的Service.
@@ -567,7 +566,7 @@ public interface WxCpService extends WxService {
   /**
    * 相关接口的服务类对象
    *
-   * @return  the meeting service
+   * @return the meeting service
    */
   WxCpMeetingService getMeetingService();