|
| 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