Skip to content

Commit d265e8b

Browse files
🆕 #3149 [企业微信] 增加支持企业微信多账号配置的starter
1 parent 97c3621 commit d265e8b

File tree

18 files changed

+830
-3
lines changed

18 files changed

+830
-3
lines changed

spring-boot-starters/pom.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?xml version="1.0"?>
2-
<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">
2+
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
4+
xmlns="http://maven.apache.org/POM/4.0.0">
35
<modelVersion>4.0.0</modelVersion>
46
<parent>
57
<groupId>com.github.binarywang</groupId>
@@ -21,6 +23,7 @@
2123
<module>wx-java-pay-spring-boot-starter</module>
2224
<module>wx-java-open-spring-boot-starter</module>
2325
<module>wx-java-qidian-spring-boot-starter</module>
26+
<module>wx-java-cp-multi-spring-boot-starter</module>
2427
<module>wx-java-cp-spring-boot-starter</module>
2528
<module>wx-java-channel-spring-boot-starter</module>
2629
</modules>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# wx-java-cp-multi-spring-boot-starter
2+
3+
企业微信多账号配置
4+
5+
- 实现多 WxCpService 初始化。
6+
- 未实现 WxCpTpService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
7+
- 未实现 WxCpCgService 初始化,需要的小伙伴可以参考多 WxCpService 配置的实现。
8+
9+
## 快速开始
10+
11+
1. 引入依赖
12+
```xml
13+
<dependency>
14+
<groupId>com.github.binarywang</groupId>
15+
<artifactId>wx-java-cp-multi-spring-boot-starter</artifactId>
16+
<version>${version}</version>
17+
</dependency>
18+
```
19+
2. 添加配置(application.properties)
20+
```properties
21+
# 应用 1 配置
22+
wx.cp.corps.tenantId1.corp-id = @corp-id
23+
wx.cp.corps.tenantId1.corp-secret = @corp-secret
24+
## 选填
25+
wx.cp.corps.tenantId1.agent-id = @agent-id
26+
wx.cp.corps.tenantId1.token = @token
27+
wx.cp.corps.tenantId1.aes-key = @aes-key
28+
wx.cp.corps.tenantId1.msg-audit-priKey = @msg-audit-priKey
29+
wx.cp.corps.tenantId1.msg-audit-lib-path = @msg-audit-lib-path
30+
31+
# 应用 2 配置
32+
wx.cp.corps.tenantId2.corp-id = @corp-id
33+
wx.cp.corps.tenantId2.corp-secret = @corp-secret
34+
## 选填
35+
wx.cp.corps.tenantId2.agent-id = @agent-id
36+
wx.cp.corps.tenantId2.token = @token
37+
wx.cp.corps.tenantId2.aes-key = @aes-key
38+
wx.cp.corps.tenantId2.msg-audit-priKey = @msg-audit-priKey
39+
wx.cp.corps.tenantId2.msg-audit-lib-path = @msg-audit-lib-path
40+
41+
# 公共配置
42+
## ConfigStorage 配置(选填)
43+
wx.cp.config-storage.type=memory # 配置类型: memory(默认), jedis, redisson, redistemplate
44+
## http 客户端配置(选填)
45+
wx.cp.config-storage.http-proxy-host=
46+
wx.cp.config-storage.http-proxy-port=
47+
wx.cp.config-storage.http-proxy-username=
48+
wx.cp.config-storage.http-proxy-password=
49+
## 最大重试次数,默认:5 次,如果小于 0,则为 0
50+
wx.cp.config-storage.max-retry-times=5
51+
## 重试时间间隔步进,默认:1000 毫秒,如果小于 0,则为 1000
52+
wx.cp.config-storage.retry-sleep-millis=1000
53+
```
54+
3. 支持自动注入的类型: `WxCpMultiServices`
55+
56+
4. 使用样例
57+
58+
```java
59+
import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices;
60+
import com.binarywang.spring.starter.wxjava.cp.service.WxCpServices;
61+
import me.chanjar.weixin.cp.api.WxCpService;
62+
import me.chanjar.weixin.cp.api.WxCpUserService;
63+
import org.springframework.beans.factory.annotation.Autowired;
64+
import org.springframework.stereotype.Service;
65+
66+
@Service
67+
public class DemoService {
68+
@Autowired
69+
private WxCpMultiServices wxCpMultiServices;
70+
71+
public void test() {
72+
// 应用 1 的 WxCpService
73+
WxCpService wxCpService1 = wxCpMultiServices.getWxCpService("tenantId1");
74+
WxCpUserService userService1 = wxCpService1.getUserService();
75+
userService1.getUserId("xxx");
76+
// todo ...
77+
78+
// 应用 2 的 WxCpService
79+
WxCpService wxCpService2 = wxCpMultiServices.getWxCpService("tenantId2");
80+
WxCpUserService userService2 = wxCpService2.getUserService();
81+
userService2.getUserId("xxx");
82+
// todo ...
83+
}
84+
}
85+
```
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<parent>
5+
<artifactId>wx-java-spring-boot-starters</artifactId>
6+
<groupId>com.github.binarywang</groupId>
7+
<version>4.5.5.B</version>
8+
</parent>
9+
<modelVersion>4.0.0</modelVersion>
10+
11+
<artifactId>wx-java-cp-multi-spring-boot-starter</artifactId>
12+
<name>WxJava - Spring Boot Starter for WxCp::支持多账号配置</name>
13+
<description>微信企业号开发的 Spring Boot Starter::支持多账号配置</description>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>com.github.binarywang</groupId>
18+
<artifactId>weixin-java-cp</artifactId>
19+
<version>${project.version}</version>
20+
</dependency>
21+
<dependency>
22+
<groupId>redis.clients</groupId>
23+
<artifactId>jedis</artifactId>
24+
<scope>provided</scope>
25+
</dependency>
26+
<dependency>
27+
<groupId>org.redisson</groupId>
28+
<artifactId>redisson</artifactId>
29+
<scope>provided</scope>
30+
</dependency>
31+
<dependency>
32+
<groupId>org.springframework.data</groupId>
33+
<artifactId>spring-data-redis</artifactId>
34+
<scope>provided</scope>
35+
</dependency>
36+
</dependencies>
37+
38+
<build>
39+
<plugins>
40+
<plugin>
41+
<groupId>org.springframework.boot</groupId>
42+
<artifactId>spring-boot-maven-plugin</artifactId>
43+
<version>${spring.boot.version}</version>
44+
</plugin>
45+
<plugin>
46+
<groupId>org.apache.maven.plugins</groupId>
47+
<artifactId>maven-source-plugin</artifactId>
48+
<version>2.2.1</version>
49+
<executions>
50+
<execution>
51+
<id>attach-sources</id>
52+
<goals>
53+
<goal>jar-no-fork</goal>
54+
</goals>
55+
</execution>
56+
</executions>
57+
</plugin>
58+
</plugins>
59+
</build>
60+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.binarywang.spring.starter.wxjava.cp.autoconfigure;
2+
3+
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties;
4+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.context.annotation.Import;
7+
8+
/**
9+
* 企业微信自动注册
10+
*
11+
* @author yl
12+
* created on 2023/10/16
13+
*/
14+
@Configuration
15+
@EnableConfigurationProperties(WxCpMultiProperties.class)
16+
@Import({
17+
WxCpMultiServicesAutoConfiguration.class
18+
})
19+
public class WxCpMultiAutoConfiguration {
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.binarywang.spring.starter.wxjava.cp.autoconfigure;
2+
3+
import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInJedisConfiguration;
4+
import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInMemoryConfiguration;
5+
import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInRedisTemplateConfiguration;
6+
import com.binarywang.spring.starter.wxjava.cp.autoconfigure.services.WxCpInRedissonConfiguration;
7+
import lombok.RequiredArgsConstructor;
8+
import org.springframework.context.annotation.Configuration;
9+
import org.springframework.context.annotation.Import;
10+
11+
/**
12+
* 企业微信平台相关服务自动注册
13+
*
14+
* @author yl
15+
* created on 2023/10/16
16+
*/
17+
@Configuration
18+
@RequiredArgsConstructor
19+
@Import({
20+
WxCpInJedisConfiguration.class,
21+
WxCpInMemoryConfiguration.class,
22+
WxCpInRedissonConfiguration.class,
23+
WxCpInRedisTemplateConfiguration.class
24+
})
25+
public class WxCpMultiServicesAutoConfiguration {
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package com.binarywang.spring.starter.wxjava.cp.autoconfigure.services;
2+
3+
import com.binarywang.spring.starter.wxjava.cp.properties.CorpProperties;
4+
import com.binarywang.spring.starter.wxjava.cp.properties.WxCpMultiProperties;
5+
import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServices;
6+
import com.binarywang.spring.starter.wxjava.cp.service.WxCpMultiServicesImpl;
7+
import lombok.RequiredArgsConstructor;
8+
import me.chanjar.weixin.cp.api.WxCpService;
9+
import me.chanjar.weixin.cp.api.impl.WxCpServiceImpl;
10+
import me.chanjar.weixin.cp.config.WxCpConfigStorage;
11+
import me.chanjar.weixin.cp.config.impl.WxCpDefaultConfigImpl;
12+
import org.apache.commons.lang3.StringUtils;
13+
14+
import java.util.Collection;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.Set;
18+
import java.util.stream.Collectors;
19+
20+
/**
21+
* WxCpConfigStorage 抽象配置类
22+
*
23+
* @author yl
24+
* created on 2023/10/16
25+
*/
26+
@RequiredArgsConstructor
27+
public abstract class AbstractWxCpConfiguration {
28+
29+
protected WxCpMultiServices configWxCpServices(WxCpMultiProperties wxCpMultiProperties) {
30+
WxCpMultiServicesImpl wxCpServices = new WxCpMultiServicesImpl();
31+
Map<String, CorpProperties> corps = wxCpMultiProperties.getCorps();
32+
if (corps == null || corps.isEmpty()) {
33+
throw new RuntimeException("企业微信配置为null");
34+
}
35+
/**
36+
* 校验同一个企业下,agentId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
37+
*
38+
* 查看 {@link me.chanjar.weixin.cp.config.impl.AbstractWxCpInRedisConfigImpl#setAgentId(Integer)}
39+
*/
40+
Collection<CorpProperties> corpList = corps.values();
41+
if (corpList.size() > 1) {
42+
// 先按 corpId 分组统计
43+
Map<String, List<CorpProperties>> corpsMap = corpList.stream()
44+
.collect(Collectors.groupingBy(CorpProperties::getCorpId));
45+
Set<Map.Entry<String, List<CorpProperties>>> entries = corpsMap.entrySet();
46+
for (Map.Entry<String, List<CorpProperties>> entry : entries) {
47+
String corpId = entry.getKey();
48+
// 校验每个企业下,agentId 是否唯一
49+
boolean multi = entry.getValue().stream()
50+
// 通讯录没有 agentId,如果不判断是否为空,这里会报 NPE 异常
51+
.collect(Collectors.groupingBy(c -> c.getAgentId() == null ? 0 : c.getAgentId(), Collectors.counting()))
52+
.entrySet().stream().anyMatch(e -> e.getValue() > 1);
53+
if (multi) {
54+
throw new RuntimeException("请确保企业微信配置唯一性[" + corpId + "]");
55+
}
56+
}
57+
}
58+
59+
Set<Map.Entry<String, CorpProperties>> entries = corps.entrySet();
60+
for (Map.Entry<String, CorpProperties> entry : entries) {
61+
String tenantId = entry.getKey();
62+
CorpProperties corpProperties = entry.getValue();
63+
WxCpDefaultConfigImpl storage = this.configWxCpDefaultConfigImpl(wxCpMultiProperties);
64+
this.configCorp(storage, corpProperties);
65+
this.configHttp(storage, wxCpMultiProperties.getConfigStorage());
66+
WxCpService wxCpService = this.configWxCpService(storage, wxCpMultiProperties.getConfigStorage());
67+
wxCpServices.addWxCpService(tenantId, wxCpService);
68+
}
69+
return wxCpServices;
70+
}
71+
72+
/**
73+
* 配置 WxCpDefaultConfigImpl
74+
*
75+
* @param wxCpMultiProperties 参数
76+
* @return WxCpDefaultConfigImpl
77+
*/
78+
protected abstract WxCpDefaultConfigImpl configWxCpDefaultConfigImpl(WxCpMultiProperties wxCpMultiProperties);
79+
80+
private WxCpService configWxCpService(WxCpConfigStorage wxCpConfigStorage, WxCpMultiProperties.ConfigStorage storage) {
81+
WxCpService wxCpService = new WxCpServiceImpl();
82+
wxCpService.setWxCpConfigStorage(wxCpConfigStorage);
83+
84+
int maxRetryTimes = storage.getMaxRetryTimes();
85+
if (maxRetryTimes < 0) {
86+
maxRetryTimes = 0;
87+
}
88+
int retrySleepMillis = storage.getRetrySleepMillis();
89+
if (retrySleepMillis < 0) {
90+
retrySleepMillis = 1000;
91+
}
92+
wxCpService.setRetrySleepMillis(retrySleepMillis);
93+
wxCpService.setMaxRetryTimes(maxRetryTimes);
94+
return wxCpService;
95+
}
96+
97+
private void configCorp(WxCpDefaultConfigImpl config, CorpProperties corpProperties) {
98+
String corpId = corpProperties.getCorpId();
99+
String corpSecret = corpProperties.getCorpSecret();
100+
Integer agentId = corpProperties.getAgentId();
101+
String token = corpProperties.getToken();
102+
String aesKey = corpProperties.getAesKey();
103+
// 企业微信,私钥,会话存档路径
104+
String msgAuditPriKey = corpProperties.getMsgAuditPriKey();
105+
String msgAuditLibPath = corpProperties.getMsgAuditLibPath();
106+
107+
config.setCorpId(corpId);
108+
config.setCorpSecret(corpSecret);
109+
config.setAgentId(agentId);
110+
if (StringUtils.isNotBlank(token)) {
111+
config.setToken(token);
112+
}
113+
if (StringUtils.isNotBlank(aesKey)) {
114+
config.setAesKey(aesKey);
115+
}
116+
if (StringUtils.isNotBlank(msgAuditPriKey)) {
117+
config.setMsgAuditPriKey(msgAuditPriKey);
118+
}
119+
if (StringUtils.isNotBlank(msgAuditLibPath)) {
120+
config.setMsgAuditLibPath(msgAuditLibPath);
121+
}
122+
}
123+
124+
private void configHttp(WxCpDefaultConfigImpl config, WxCpMultiProperties.ConfigStorage storage) {
125+
String httpProxyHost = storage.getHttpProxyHost();
126+
Integer httpProxyPort = storage.getHttpProxyPort();
127+
String httpProxyUsername = storage.getHttpProxyUsername();
128+
String httpProxyPassword = storage.getHttpProxyPassword();
129+
if (StringUtils.isNotBlank(httpProxyHost)) {
130+
config.setHttpProxyHost(httpProxyHost);
131+
if (httpProxyPort != null) {
132+
config.setHttpProxyPort(httpProxyPort);
133+
}
134+
if (StringUtils.isNotBlank(httpProxyUsername)) {
135+
config.setHttpProxyUsername(httpProxyUsername);
136+
}
137+
if (StringUtils.isNotBlank(httpProxyPassword)) {
138+
config.setHttpProxyPassword(httpProxyPassword);
139+
}
140+
}
141+
}
142+
}

0 commit comments

Comments
 (0)