Skip to content

Commit 373d9fa

Browse files
authored
🎨 #3840 小程序和公众号的多租户starter添加多租户共享模式以优化资源使用
1 parent 12db287 commit 373d9fa

File tree

8 files changed

+690
-43
lines changed

8 files changed

+690
-43
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# 多租户模式配置改进说明
2+
3+
## 问题背景
4+
5+
用户在 issue #3835 中提出了一个架构设计问题:
6+
7+
> 基础 Wx 实现类中已经有 configMap 了,可以用 configMap 来存储不同的小程序配置。不同的配置,都是复用同一个 http 客户端。为什么在各个 spring-boot-starter 中又单独创建类来存储不同的配置?从 spring 的配置来看,http 客户端只有一个,不同小程序配置可以实现多租户,所以似乎没必要单独再建新类存放?重复创建,增加了 http 客户端的成本?直接使用 Wx 实现类中已经有 configMap 不是更好吗?
8+
9+
## 解决方案
10+
11+
从 4.8.0 版本开始,我们为多租户 Spring Boot Starter 提供了**两种实现模式**供用户选择:
12+
13+
### 1. 隔离模式(ISOLATED,默认)
14+
15+
**实现方式**:为每个租户创建独立的 WxService 实例,每个实例拥有独立的 HTTP 客户端。
16+
17+
**优点**
18+
- ✅ 线程安全,无需担心并发问题
19+
- ✅ 不依赖 ThreadLocal,适合异步/响应式编程
20+
- ✅ 租户间完全隔离,互不影响
21+
22+
**缺点**
23+
- ❌ 每个租户创建独立的 HTTP 客户端,资源占用较多
24+
- ❌ 适合租户数量不多的场景(建议 < 50 个租户)
25+
26+
**代码实现**`WxMaMultiServicesImpl`, `WxMpMultiServicesImpl`
27+
28+
### 2. 共享模式(SHARED,新增)
29+
30+
**实现方式**:使用单个 WxService 实例管理所有租户配置,通过 ThreadLocal 切换租户,所有租户共享同一个 HTTP 客户端。
31+
32+
**优点**
33+
- ✅ 共享 HTTP 客户端,大幅节省资源
34+
- ✅ 适合租户数量较多的场景(支持 100+ 租户)
35+
- ✅ 内存占用更小
36+
37+
**缺点**
38+
- ❌ 依赖 ThreadLocal 切换配置,在异步场景需要特别注意
39+
- ❌ 需要注意线程上下文传递
40+
41+
**代码实现**`WxMaMultiServicesSharedImpl`, `WxMpMultiServicesSharedImpl`
42+
43+
## 使用方式
44+
45+
### 配置示例
46+
47+
```yaml
48+
wx:
49+
ma: # 或 mp, cp, channel
50+
apps:
51+
tenant1:
52+
app-id: wxd898fcb01713c555
53+
app-secret: 47a2422a5d04a27e2b3ed1f1f0b0dbad
54+
tenant2:
55+
app-id: wx1234567890abcdef
56+
app-secret: 1234567890abcdef1234567890abcdef
57+
58+
config-storage:
59+
type: memory
60+
http-client-type: http_client
61+
# 多租户模式配置(新增)
62+
multi-tenant-mode: shared # isolated(默认)或 shared
63+
```
64+
65+
### 代码使用(两种模式代码完全相同)
66+
67+
```java
68+
@RestController
69+
public class WxController {
70+
@Autowired
71+
private WxMaMultiServices wxMaMultiServices; // 或 WxMpMultiServices
72+
73+
@GetMapping("/api/{tenantId}")
74+
public String handle(@PathVariable String tenantId) {
75+
WxMaService wxService = wxMaMultiServices.getWxMaService(tenantId);
76+
// 使用 wxService 调用微信 API
77+
return wxService.getAccessToken();
78+
}
79+
}
80+
```
81+
82+
## 性能对比
83+
84+
以 100 个租户为例:
85+
86+
| 指标 | 隔离模式 | 共享模式 |
87+
|------|---------|---------|
88+
| HTTP 客户端数量 | 100 个 | 1 个 |
89+
| 内存占用(估算) | ~500MB | ~50MB |
90+
| 线程安全 | ✅ 完全安全 | ⚠️ 需注意异步场景 |
91+
| 性能 | 略高(无 ThreadLocal 切换) | 略低(有 ThreadLocal 切换) |
92+
| 适用场景 | 中小规模 | 大规模 |
93+
94+
## 支持的模块
95+
96+
目前已实现共享模式支持的模块:
97+
98+
-**小程序(MiniApp)**`wx-java-miniapp-multi-spring-boot-starter`
99+
-**公众号(MP)**`wx-java-mp-multi-spring-boot-starter`
100+
101+
后续版本将支持:
102+
- ⏳ 企业微信(CP)
103+
- ⏳ 视频号(Channel)
104+
- ⏳ 企业微信第三方应用(CP-TP)
105+
106+
## 迁移指南
107+
108+
### 从旧版本升级
109+
110+
升级到 4.8.0+ 后:
111+
112+
1. **默认行为不变**:如果不配置 `multi-tenant-mode`,将继续使用隔离模式(与旧版本行为一致)
113+
2. **向后兼容**:所有现有代码无需修改
114+
3. **可选升级**:如需节省资源,可配置 `multi-tenant-mode: shared` 启用共享模式
115+
116+
### 选择建议
117+
118+
**使用隔离模式(ISOLATED)的场景**
119+
- 租户数量较少(< 50 个)
120+
- 使用异步编程、响应式编程
121+
- 对线程安全有严格要求
122+
- 对资源占用不敏感
123+
124+
**使用共享模式(SHARED)的场景**
125+
- 租户数量较多(> 50 个)
126+
- 同步编程场景
127+
- 对资源占用敏感
128+
- 可以接受 ThreadLocal 的约束
129+
130+
## 注意事项
131+
132+
### 共享模式下的异步编程
133+
134+
如果使用共享模式,在异步编程时需要注意 ThreadLocal 的传递:
135+
136+
```java
137+
// ❌ 错误:异步线程无法获取到正确的配置
138+
CompletableFuture.runAsync(() -> {
139+
wxService.getUserService().getUserInfo(...); // 可能使用错误的租户配置
140+
});
141+
142+
// ✅ 正确:在主线程获取必要信息,传递给异步线程
143+
String appId = wxService.getWxMaConfig().getAppid();
144+
CompletableFuture.runAsync(() -> {
145+
log.info("AppId: {}", appId); // 使用已获取的配置信息
146+
});
147+
```
148+
149+
## 详细文档
150+
151+
- 小程序模块详细说明:[spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/MULTI_TENANT_MODE.md](spring-boot-starters/wx-java-miniapp-multi-spring-boot-starter/MULTI_TENANT_MODE.md)
152+
153+
## 相关链接
154+
155+
- Issue: [#3835](https://github.com/binarywang/WxJava/issues/3835)
156+
- Pull Request: [#3840](https://github.com/binarywang/WxJava/pull/3840)
157+
158+
## 致谢
159+
160+
感谢 issue 提出者对项目架构的深入思考和建议,这帮助我们提供了更灵活、更高效的多租户解决方案。
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# 微信小程序多租户配置说明
2+
3+
## 多租户模式对比
4+
5+
从 4.8.0 版本开始,wx-java-miniapp-multi-spring-boot-starter 支持两种多租户实现模式:
6+
7+
### 1. 隔离模式(ISOLATED,默认)
8+
9+
每个租户创建独立的 `WxMaService` 实例,各自拥有独立的 HTTP 客户端。
10+
11+
**优点:**
12+
- 线程安全,无需担心并发问题
13+
- 不依赖 ThreadLocal,适合异步/响应式编程
14+
- 租户间完全隔离,互不影响
15+
16+
**缺点:**
17+
- 每个租户创建独立的 HTTP 客户端,资源占用较多
18+
- 适合租户数量不多的场景(建议 < 50 个租户)
19+
20+
**适用场景:**
21+
- SaaS 应用,租户数量较少
22+
- 异步编程、响应式编程场景
23+
- 对线程安全有严格要求
24+
25+
### 2. 共享模式(SHARED)
26+
27+
使用单个 `WxMaService` 实例管理所有租户配置,所有租户共享同一个 HTTP 客户端。
28+
29+
**优点:**
30+
- 共享 HTTP 客户端,大幅节省资源
31+
- 适合租户数量较多的场景(支持 100+ 租户)
32+
- 内存占用更小
33+
34+
**缺点:**
35+
- 依赖 ThreadLocal 切换配置,在异步场景需要特别注意
36+
- 需要注意线程上下文传递
37+
38+
**适用场景:**
39+
- 租户数量较多(> 50 个)
40+
- 同步编程场景
41+
- 对资源占用有严格要求
42+
43+
## 配置方式
44+
45+
### 使用隔离模式(默认)
46+
47+
```yaml
48+
wx:
49+
ma:
50+
# 多租户配置
51+
apps:
52+
tenant1:
53+
app-id: wxd898fcb01713c555
54+
app-secret: 47a2422a5d04a27e2b3ed1f1f0b0dbad
55+
token: aBcDeFg123456
56+
aes-key: abcdefgh123456abcdefgh123456abc
57+
tenant2:
58+
app-id: wx1234567890abcdef
59+
app-secret: 1234567890abcdef1234567890abcdef
60+
token: token123
61+
aes-key: aeskey123aeskey123aeskey123aes
62+
63+
# 配置存储(可选)
64+
config-storage:
65+
type: memory # memory, jedis, redisson, redis_template
66+
http-client-type: http_client # http_client, ok_http, jodd_http
67+
# multi-tenant-mode: isolated # 默认值,可以不配置
68+
```
69+
70+
### 使用共享模式
71+
72+
```yaml
73+
wx:
74+
ma:
75+
# 多租户配置
76+
apps:
77+
tenant1:
78+
app-id: wxd898fcb01713c555
79+
app-secret: 47a2422a5d04a27e2b3ed1f1f0b0dbad
80+
tenant2:
81+
app-id: wx1234567890abcdef
82+
app-secret: 1234567890abcdef1234567890abcdef
83+
# ... 可配置更多租户
84+
85+
# 配置存储
86+
config-storage:
87+
type: memory
88+
http-client-type: http_client
89+
multi-tenant-mode: shared # 启用共享模式
90+
```
91+
92+
## 代码使用
93+
94+
两种模式下的代码使用方式**完全相同**:
95+
96+
```java
97+
@RestController
98+
@RequestMapping("/ma")
99+
public class MiniAppController {
100+
101+
@Autowired
102+
private WxMaMultiServices wxMaMultiServices;
103+
104+
@GetMapping("/userInfo/{tenantId}")
105+
public String getUserInfo(@PathVariable String tenantId, @RequestParam String code) {
106+
// 获取指定租户的 WxMaService
107+
WxMaService wxMaService = wxMaMultiServices.getWxMaService(tenantId);
108+
109+
try {
110+
WxMaJscode2SessionResult session = wxMaService.jsCode2SessionInfo(code);
111+
return "OpenId: " + session.getOpenid();
112+
} catch (WxErrorException e) {
113+
return "错误: " + e.getMessage();
114+
}
115+
}
116+
}
117+
```
118+
119+
## 性能对比
120+
121+
以 100 个租户为例:
122+
123+
| 指标 | 隔离模式 | 共享模式 |
124+
|------|---------|---------|
125+
| HTTP 客户端数量 | 100 个 | 1 个 |
126+
| 内存占用(估算) | ~500MB | ~50MB |
127+
| 线程安全 | ✅ 完全安全 | ⚠️ 需注意异步场景 |
128+
| 性能 | 略高(无 ThreadLocal 切换) | 略低(有 ThreadLocal 切换) |
129+
| 适用场景 | 中小规模 | 大规模 |
130+
131+
## 注意事项
132+
133+
### 共享模式下的异步编程
134+
135+
如果使用共享模式,在异步编程时需要注意 ThreadLocal 的传递:
136+
137+
```java
138+
@Service
139+
public class MiniAppService {
140+
141+
@Autowired
142+
private WxMaMultiServices wxMaMultiServices;
143+
144+
public void asyncOperation(String tenantId) {
145+
WxMaService wxMaService = wxMaMultiServices.getWxMaService(tenantId);
146+
147+
// ❌ 错误:异步线程无法获取到正确的配置
148+
CompletableFuture.runAsync(() -> {
149+
// 这里 wxMaService.getWxMaConfig() 可能返回错误的配置
150+
wxMaService.getUserService().getUserInfo(...);
151+
});
152+
153+
// ✅ 正确:在主线程获取配置,传递给异步线程
154+
WxMaConfig config = wxMaService.getWxMaConfig();
155+
String appId = config.getAppid();
156+
CompletableFuture.runAsync(() -> {
157+
// 使用已获取的配置信息
158+
log.info("AppId: {}", appId);
159+
});
160+
}
161+
}
162+
```
163+
164+
### 动态添加/删除租户
165+
166+
两种模式都支持运行时动态添加或删除租户配置。
167+
168+
## 迁移指南
169+
170+
如果您正在使用旧版本,升级到 4.8.0+ 后:
171+
172+
1. **默认行为不变**:如果不配置 `multi-tenant-mode`,将继续使用隔离模式(与旧版本行为一致)
173+
2. **向后兼容**:所有现有代码无需修改
174+
3. **可选升级**:如需节省资源,可配置 `multi-tenant-mode: shared` 启用共享模式
175+
176+
## 源码分析
177+
178+
issue讨论地址:[#3835](https://github.com/binarywang/WxJava/issues/3835)
179+
180+
### 为什么有两种设计?
181+
182+
1. **基础实现类的 `configMap`**
183+
- 位置:`BaseWxMaServiceImpl`
184+
- 特点:单个 Service 实例 + 多个配置 + ThreadLocal 切换
185+
- 设计目的:支持在一个应用中管理多个小程序账号
186+
187+
2. **Spring Boot Starter 的 `services` Map**
188+
- 位置:`WxMaMultiServicesImpl`
189+
- 特点:多个 Service 实例 + 每个实例一个配置
190+
- 设计目的:为 Spring Boot 提供更符合依赖注入风格的多租户支持
191+
192+
### 新版本改进
193+
194+
新版本通过配置项让用户自主选择实现方式:
195+
196+
```
197+
用户 → WxMaMultiServices 接口
198+
199+
┌────┴────┐
200+
↓ ↓
201+
隔离模式 共享模式
202+
(多Service) (单Service+configMap)
203+
```
204+
205+
这样既保留了线程安全的优势(隔离模式),又提供了资源节省的选项(共享模式)。

0 commit comments

Comments
 (0)