Skip to content

Commit

Permalink
🆕 #3149 [企业微信] 增加支持企业微信多账号配置的starter
Browse files Browse the repository at this point in the history
  • Loading branch information
foreveryang321 authored Oct 18, 2023
1 parent 97c3621 commit d265e8b
Show file tree
Hide file tree
Showing 18 changed files with 830 additions and 3 deletions.
5 changes: 4 additions & 1 deletion spring-boot-starters/pom.xml
Original file line number Diff line number Diff line change
@@ -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>
Expand All @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 changes: 60 additions & 0 deletions spring-boot-starters/wx-java-cp-multi-spring-boot-starter/pom.xml
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
Loading

0 comments on commit d265e8b

Please sign in to comment.