mirror of
https://gitee.com/binary/weixin-java-tools.git
synced 2026-03-10 00:13:40 +08:00
🎨 #3840 小程序和公众号的多租户starter添加多租户共享模式以优化资源使用
This commit is contained in:
@@ -0,0 +1,160 @@
|
|||||||
|
# 多租户模式配置改进说明
|
||||||
|
|
||||||
|
## 问题背景
|
||||||
|
|
||||||
|
用户在 issue #3835 中提出了一个架构设计问题:
|
||||||
|
|
||||||
|
> 基础 Wx 实现类中已经有 configMap 了,可以用 configMap 来存储不同的小程序配置。不同的配置,都是复用同一个 http 客户端。为什么在各个 spring-boot-starter 中又单独创建类来存储不同的配置?从 spring 的配置来看,http 客户端只有一个,不同小程序配置可以实现多租户,所以似乎没必要单独再建新类存放?重复创建,增加了 http 客户端的成本?直接使用 Wx 实现类中已经有 configMap 不是更好吗?
|
||||||
|
|
||||||
|
## 解决方案
|
||||||
|
|
||||||
|
从 4.8.0 版本开始,我们为多租户 Spring Boot Starter 提供了**两种实现模式**供用户选择:
|
||||||
|
|
||||||
|
### 1. 隔离模式(ISOLATED,默认)
|
||||||
|
|
||||||
|
**实现方式**:为每个租户创建独立的 WxService 实例,每个实例拥有独立的 HTTP 客户端。
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- ✅ 线程安全,无需担心并发问题
|
||||||
|
- ✅ 不依赖 ThreadLocal,适合异步/响应式编程
|
||||||
|
- ✅ 租户间完全隔离,互不影响
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- ❌ 每个租户创建独立的 HTTP 客户端,资源占用较多
|
||||||
|
- ❌ 适合租户数量不多的场景(建议 < 50 个租户)
|
||||||
|
|
||||||
|
**代码实现**:`WxMaMultiServicesImpl`, `WxMpMultiServicesImpl` 等
|
||||||
|
|
||||||
|
### 2. 共享模式(SHARED,新增)
|
||||||
|
|
||||||
|
**实现方式**:使用单个 WxService 实例管理所有租户配置,通过 ThreadLocal 切换租户,所有租户共享同一个 HTTP 客户端。
|
||||||
|
|
||||||
|
**优点**:
|
||||||
|
- ✅ 共享 HTTP 客户端,大幅节省资源
|
||||||
|
- ✅ 适合租户数量较多的场景(支持 100+ 租户)
|
||||||
|
- ✅ 内存占用更小
|
||||||
|
|
||||||
|
**缺点**:
|
||||||
|
- ❌ 依赖 ThreadLocal 切换配置,在异步场景需要特别注意
|
||||||
|
- ❌ 需要注意线程上下文传递
|
||||||
|
|
||||||
|
**代码实现**:`WxMaMultiServicesSharedImpl`, `WxMpMultiServicesSharedImpl` 等
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
### 配置示例
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
wx:
|
||||||
|
ma: # 或 mp, cp, channel
|
||||||
|
apps:
|
||||||
|
tenant1:
|
||||||
|
app-id: wxd898fcb01713c555
|
||||||
|
app-secret: 47a2422a5d04a27e2b3ed1f1f0b0dbad
|
||||||
|
tenant2:
|
||||||
|
app-id: wx1234567890abcdef
|
||||||
|
app-secret: 1234567890abcdef1234567890abcdef
|
||||||
|
|
||||||
|
config-storage:
|
||||||
|
type: memory
|
||||||
|
http-client-type: http_client
|
||||||
|
# 多租户模式配置(新增)
|
||||||
|
multi-tenant-mode: shared # isolated(默认)或 shared
|
||||||
|
```
|
||||||
|
|
||||||
|
### 代码使用(两种模式代码完全相同)
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
public class WxController {
|
||||||
|
@Autowired
|
||||||
|
private WxMaMultiServices wxMaMultiServices; // 或 WxMpMultiServices
|
||||||
|
|
||||||
|
@GetMapping("/api/{tenantId}")
|
||||||
|
public String handle(@PathVariable String tenantId) {
|
||||||
|
WxMaService wxService = wxMaMultiServices.getWxMaService(tenantId);
|
||||||
|
// 使用 wxService 调用微信 API
|
||||||
|
return wxService.getAccessToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能对比
|
||||||
|
|
||||||
|
以 100 个租户为例:
|
||||||
|
|
||||||
|
| 指标 | 隔离模式 | 共享模式 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| HTTP 客户端数量 | 100 个 | 1 个 |
|
||||||
|
| 内存占用(估算) | ~500MB | ~50MB |
|
||||||
|
| 线程安全 | ✅ 完全安全 | ⚠️ 需注意异步场景 |
|
||||||
|
| 性能 | 略高(无 ThreadLocal 切换) | 略低(有 ThreadLocal 切换) |
|
||||||
|
| 适用场景 | 中小规模 | 大规模 |
|
||||||
|
|
||||||
|
## 支持的模块
|
||||||
|
|
||||||
|
目前已实现共享模式支持的模块:
|
||||||
|
|
||||||
|
- ✅ **小程序(MiniApp)**:`wx-java-miniapp-multi-spring-boot-starter`
|
||||||
|
- ✅ **公众号(MP)**:`wx-java-mp-multi-spring-boot-starter`
|
||||||
|
|
||||||
|
后续版本将支持:
|
||||||
|
- ⏳ 企业微信(CP)
|
||||||
|
- ⏳ 视频号(Channel)
|
||||||
|
- ⏳ 企业微信第三方应用(CP-TP)
|
||||||
|
|
||||||
|
## 迁移指南
|
||||||
|
|
||||||
|
### 从旧版本升级
|
||||||
|
|
||||||
|
升级到 4.8.0+ 后:
|
||||||
|
|
||||||
|
1. **默认行为不变**:如果不配置 `multi-tenant-mode`,将继续使用隔离模式(与旧版本行为一致)
|
||||||
|
2. **向后兼容**:所有现有代码无需修改
|
||||||
|
3. **可选升级**:如需节省资源,可配置 `multi-tenant-mode: shared` 启用共享模式
|
||||||
|
|
||||||
|
### 选择建议
|
||||||
|
|
||||||
|
**使用隔离模式(ISOLATED)的场景**:
|
||||||
|
- 租户数量较少(< 50 个)
|
||||||
|
- 使用异步编程、响应式编程
|
||||||
|
- 对线程安全有严格要求
|
||||||
|
- 对资源占用不敏感
|
||||||
|
|
||||||
|
**使用共享模式(SHARED)的场景**:
|
||||||
|
- 租户数量较多(> 50 个)
|
||||||
|
- 同步编程场景
|
||||||
|
- 对资源占用敏感
|
||||||
|
- 可以接受 ThreadLocal 的约束
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
### 共享模式下的异步编程
|
||||||
|
|
||||||
|
如果使用共享模式,在异步编程时需要注意 ThreadLocal 的传递:
|
||||||
|
|
||||||
|
```java
|
||||||
|
// ❌ 错误:异步线程无法获取到正确的配置
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
wxService.getUserService().getUserInfo(...); // 可能使用错误的租户配置
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ 正确:在主线程获取必要信息,传递给异步线程
|
||||||
|
String appId = wxService.getWxMaConfig().getAppid();
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
log.info("AppId: {}", appId); // 使用已获取的配置信息
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 详细文档
|
||||||
|
|
||||||
|
- 小程序模块详细说明:[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)
|
||||||
|
|
||||||
|
## 相关链接
|
||||||
|
|
||||||
|
- Issue: [#3835](https://github.com/binarywang/WxJava/issues/3835)
|
||||||
|
- Pull Request: [#3840](https://github.com/binarywang/WxJava/pull/3840)
|
||||||
|
|
||||||
|
## 致谢
|
||||||
|
|
||||||
|
感谢 issue 提出者对项目架构的深入思考和建议,这帮助我们提供了更灵活、更高效的多租户解决方案。
|
||||||
@@ -0,0 +1,205 @@
|
|||||||
|
# 微信小程序多租户配置说明
|
||||||
|
|
||||||
|
## 多租户模式对比
|
||||||
|
|
||||||
|
从 4.8.0 版本开始,wx-java-miniapp-multi-spring-boot-starter 支持两种多租户实现模式:
|
||||||
|
|
||||||
|
### 1. 隔离模式(ISOLATED,默认)
|
||||||
|
|
||||||
|
每个租户创建独立的 `WxMaService` 实例,各自拥有独立的 HTTP 客户端。
|
||||||
|
|
||||||
|
**优点:**
|
||||||
|
- 线程安全,无需担心并发问题
|
||||||
|
- 不依赖 ThreadLocal,适合异步/响应式编程
|
||||||
|
- 租户间完全隔离,互不影响
|
||||||
|
|
||||||
|
**缺点:**
|
||||||
|
- 每个租户创建独立的 HTTP 客户端,资源占用较多
|
||||||
|
- 适合租户数量不多的场景(建议 < 50 个租户)
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- SaaS 应用,租户数量较少
|
||||||
|
- 异步编程、响应式编程场景
|
||||||
|
- 对线程安全有严格要求
|
||||||
|
|
||||||
|
### 2. 共享模式(SHARED)
|
||||||
|
|
||||||
|
使用单个 `WxMaService` 实例管理所有租户配置,所有租户共享同一个 HTTP 客户端。
|
||||||
|
|
||||||
|
**优点:**
|
||||||
|
- 共享 HTTP 客户端,大幅节省资源
|
||||||
|
- 适合租户数量较多的场景(支持 100+ 租户)
|
||||||
|
- 内存占用更小
|
||||||
|
|
||||||
|
**缺点:**
|
||||||
|
- 依赖 ThreadLocal 切换配置,在异步场景需要特别注意
|
||||||
|
- 需要注意线程上下文传递
|
||||||
|
|
||||||
|
**适用场景:**
|
||||||
|
- 租户数量较多(> 50 个)
|
||||||
|
- 同步编程场景
|
||||||
|
- 对资源占用有严格要求
|
||||||
|
|
||||||
|
## 配置方式
|
||||||
|
|
||||||
|
### 使用隔离模式(默认)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
wx:
|
||||||
|
ma:
|
||||||
|
# 多租户配置
|
||||||
|
apps:
|
||||||
|
tenant1:
|
||||||
|
app-id: wxd898fcb01713c555
|
||||||
|
app-secret: 47a2422a5d04a27e2b3ed1f1f0b0dbad
|
||||||
|
token: aBcDeFg123456
|
||||||
|
aes-key: abcdefgh123456abcdefgh123456abc
|
||||||
|
tenant2:
|
||||||
|
app-id: wx1234567890abcdef
|
||||||
|
app-secret: 1234567890abcdef1234567890abcdef
|
||||||
|
token: token123
|
||||||
|
aes-key: aeskey123aeskey123aeskey123aes
|
||||||
|
|
||||||
|
# 配置存储(可选)
|
||||||
|
config-storage:
|
||||||
|
type: memory # memory, jedis, redisson, redis_template
|
||||||
|
http-client-type: http_client # http_client, ok_http, jodd_http
|
||||||
|
# multi-tenant-mode: isolated # 默认值,可以不配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用共享模式
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
wx:
|
||||||
|
ma:
|
||||||
|
# 多租户配置
|
||||||
|
apps:
|
||||||
|
tenant1:
|
||||||
|
app-id: wxd898fcb01713c555
|
||||||
|
app-secret: 47a2422a5d04a27e2b3ed1f1f0b0dbad
|
||||||
|
tenant2:
|
||||||
|
app-id: wx1234567890abcdef
|
||||||
|
app-secret: 1234567890abcdef1234567890abcdef
|
||||||
|
# ... 可配置更多租户
|
||||||
|
|
||||||
|
# 配置存储
|
||||||
|
config-storage:
|
||||||
|
type: memory
|
||||||
|
http-client-type: http_client
|
||||||
|
multi-tenant-mode: shared # 启用共享模式
|
||||||
|
```
|
||||||
|
|
||||||
|
## 代码使用
|
||||||
|
|
||||||
|
两种模式下的代码使用方式**完全相同**:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/ma")
|
||||||
|
public class MiniAppController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WxMaMultiServices wxMaMultiServices;
|
||||||
|
|
||||||
|
@GetMapping("/userInfo/{tenantId}")
|
||||||
|
public String getUserInfo(@PathVariable String tenantId, @RequestParam String code) {
|
||||||
|
// 获取指定租户的 WxMaService
|
||||||
|
WxMaService wxMaService = wxMaMultiServices.getWxMaService(tenantId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
WxMaJscode2SessionResult session = wxMaService.jsCode2SessionInfo(code);
|
||||||
|
return "OpenId: " + session.getOpenid();
|
||||||
|
} catch (WxErrorException e) {
|
||||||
|
return "错误: " + e.getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 性能对比
|
||||||
|
|
||||||
|
以 100 个租户为例:
|
||||||
|
|
||||||
|
| 指标 | 隔离模式 | 共享模式 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| HTTP 客户端数量 | 100 个 | 1 个 |
|
||||||
|
| 内存占用(估算) | ~500MB | ~50MB |
|
||||||
|
| 线程安全 | ✅ 完全安全 | ⚠️ 需注意异步场景 |
|
||||||
|
| 性能 | 略高(无 ThreadLocal 切换) | 略低(有 ThreadLocal 切换) |
|
||||||
|
| 适用场景 | 中小规模 | 大规模 |
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
### 共享模式下的异步编程
|
||||||
|
|
||||||
|
如果使用共享模式,在异步编程时需要注意 ThreadLocal 的传递:
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class MiniAppService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WxMaMultiServices wxMaMultiServices;
|
||||||
|
|
||||||
|
public void asyncOperation(String tenantId) {
|
||||||
|
WxMaService wxMaService = wxMaMultiServices.getWxMaService(tenantId);
|
||||||
|
|
||||||
|
// ❌ 错误:异步线程无法获取到正确的配置
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
// 这里 wxMaService.getWxMaConfig() 可能返回错误的配置
|
||||||
|
wxMaService.getUserService().getUserInfo(...);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ✅ 正确:在主线程获取配置,传递给异步线程
|
||||||
|
WxMaConfig config = wxMaService.getWxMaConfig();
|
||||||
|
String appId = config.getAppid();
|
||||||
|
CompletableFuture.runAsync(() -> {
|
||||||
|
// 使用已获取的配置信息
|
||||||
|
log.info("AppId: {}", appId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 动态添加/删除租户
|
||||||
|
|
||||||
|
两种模式都支持运行时动态添加或删除租户配置。
|
||||||
|
|
||||||
|
## 迁移指南
|
||||||
|
|
||||||
|
如果您正在使用旧版本,升级到 4.8.0+ 后:
|
||||||
|
|
||||||
|
1. **默认行为不变**:如果不配置 `multi-tenant-mode`,将继续使用隔离模式(与旧版本行为一致)
|
||||||
|
2. **向后兼容**:所有现有代码无需修改
|
||||||
|
3. **可选升级**:如需节省资源,可配置 `multi-tenant-mode: shared` 启用共享模式
|
||||||
|
|
||||||
|
## 源码分析
|
||||||
|
|
||||||
|
issue讨论地址:[#3835](https://github.com/binarywang/WxJava/issues/3835)
|
||||||
|
|
||||||
|
### 为什么有两种设计?
|
||||||
|
|
||||||
|
1. **基础实现类的 `configMap`**:
|
||||||
|
- 位置:`BaseWxMaServiceImpl`
|
||||||
|
- 特点:单个 Service 实例 + 多个配置 + ThreadLocal 切换
|
||||||
|
- 设计目的:支持在一个应用中管理多个小程序账号
|
||||||
|
|
||||||
|
2. **Spring Boot Starter 的 `services` Map**:
|
||||||
|
- 位置:`WxMaMultiServicesImpl`
|
||||||
|
- 特点:多个 Service 实例 + 每个实例一个配置
|
||||||
|
- 设计目的:为 Spring Boot 提供更符合依赖注入风格的多租户支持
|
||||||
|
|
||||||
|
### 新版本改进
|
||||||
|
|
||||||
|
新版本通过配置项让用户自主选择实现方式:
|
||||||
|
|
||||||
|
```
|
||||||
|
用户 → WxMaMultiServices 接口
|
||||||
|
↓
|
||||||
|
┌────┴────┐
|
||||||
|
↓ ↓
|
||||||
|
隔离模式 共享模式
|
||||||
|
(多Service) (单Service+configMap)
|
||||||
|
```
|
||||||
|
|
||||||
|
这样既保留了线程安全的优势(隔离模式),又提供了资源节省的选项(共享模式)。
|
||||||
+92
-30
@@ -4,6 +4,7 @@ import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaMultiProperti
|
|||||||
import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaSingleProperties;
|
import com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaSingleProperties;
|
||||||
import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices;
|
import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServices;
|
||||||
import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServicesImpl;
|
import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServicesImpl;
|
||||||
|
import com.binarywang.spring.starter.wxjava.miniapp.service.WxMaMultiServicesSharedImpl;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import cn.binarywang.wx.miniapp.api.WxMaService;
|
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||||
@@ -16,8 +17,10 @@ import cn.binarywang.wx.miniapp.config.impl.WxMaDefaultConfigImpl;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,9 +36,10 @@ public abstract class AbstractWxMaConfiguration {
|
|||||||
protected WxMaMultiServices wxMaMultiServices(WxMaMultiProperties wxMaMultiProperties) {
|
protected WxMaMultiServices wxMaMultiServices(WxMaMultiProperties wxMaMultiProperties) {
|
||||||
Map<String, WxMaSingleProperties> appsMap = wxMaMultiProperties.getApps();
|
Map<String, WxMaSingleProperties> appsMap = wxMaMultiProperties.getApps();
|
||||||
if (appsMap == null || appsMap.isEmpty()) {
|
if (appsMap == null || appsMap.isEmpty()) {
|
||||||
log.warn("微信公众号应用参数未配置,通过 WxMaMultiServices#getWxMaService(\"tenantId\")获取实例将返回空");
|
log.warn("微信小程序应用参数未配置,通过 WxMaMultiServices#getWxMaService(\"tenantId\")获取实例将返回空");
|
||||||
return new WxMaMultiServicesImpl();
|
return new WxMaMultiServicesImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
|
* 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
|
||||||
*
|
*
|
||||||
@@ -49,12 +53,29 @@ public abstract class AbstractWxMaConfiguration {
|
|||||||
.collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting()))
|
.collect(Collectors.groupingBy(c -> c.getAppId() == null ? 0 : c.getAppId(), Collectors.counting()))
|
||||||
.entrySet().stream().anyMatch(e -> e.getValue() > 1);
|
.entrySet().stream().anyMatch(e -> e.getValue() > 1);
|
||||||
if (multi) {
|
if (multi) {
|
||||||
throw new RuntimeException("请确保微信公众号配置 appId 的唯一性");
|
throw new RuntimeException("请确保微信小程序配置 appId 的唯一性");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WxMaMultiServicesImpl services = new WxMaMultiServicesImpl();
|
|
||||||
|
|
||||||
|
// 根据配置选择多租户模式
|
||||||
|
WxMaMultiProperties.MultiTenantMode mode = wxMaMultiProperties.getConfigStorage().getMultiTenantMode();
|
||||||
|
if (mode == WxMaMultiProperties.MultiTenantMode.SHARED) {
|
||||||
|
return createSharedMultiServices(appsMap, wxMaMultiProperties);
|
||||||
|
} else {
|
||||||
|
return createIsolatedMultiServices(appsMap, wxMaMultiProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建隔离模式的多租户服务(每个租户独立 WxMaService 实例)
|
||||||
|
*/
|
||||||
|
private WxMaMultiServices createIsolatedMultiServices(
|
||||||
|
Map<String, WxMaSingleProperties> appsMap,
|
||||||
|
WxMaMultiProperties wxMaMultiProperties) {
|
||||||
|
|
||||||
|
WxMaMultiServicesImpl services = new WxMaMultiServicesImpl();
|
||||||
Set<Map.Entry<String, WxMaSingleProperties>> entries = appsMap.entrySet();
|
Set<Map.Entry<String, WxMaSingleProperties>> entries = appsMap.entrySet();
|
||||||
|
|
||||||
for (Map.Entry<String, WxMaSingleProperties> entry : entries) {
|
for (Map.Entry<String, WxMaSingleProperties> entry : entries) {
|
||||||
String tenantId = entry.getKey();
|
String tenantId = entry.getKey();
|
||||||
WxMaSingleProperties wxMaSingleProperties = entry.getValue();
|
WxMaSingleProperties wxMaSingleProperties = entry.getValue();
|
||||||
@@ -64,9 +85,75 @@ public abstract class AbstractWxMaConfiguration {
|
|||||||
WxMaService wxMaService = this.wxMaService(storage, wxMaMultiProperties);
|
WxMaService wxMaService = this.wxMaService(storage, wxMaMultiProperties);
|
||||||
services.addWxMaService(tenantId, wxMaService);
|
services.addWxMaService(tenantId, wxMaService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("微信小程序多租户服务初始化完成,使用隔离模式(ISOLATED),共配置 {} 个租户", appsMap.size());
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建共享模式的多租户服务(单个 WxMaService 实例管理多个配置)
|
||||||
|
*/
|
||||||
|
private WxMaMultiServices createSharedMultiServices(
|
||||||
|
Map<String, WxMaSingleProperties> appsMap,
|
||||||
|
WxMaMultiProperties wxMaMultiProperties) {
|
||||||
|
|
||||||
|
// 创建共享的 WxMaService 实例
|
||||||
|
WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage();
|
||||||
|
WxMaService sharedService = createWxMaServiceByType(storage.getHttpClientType());
|
||||||
|
configureWxMaService(sharedService, storage);
|
||||||
|
|
||||||
|
// 准备所有租户的配置,使用 TreeMap 保证顺序一致性
|
||||||
|
Map<String, WxMaConfig> configsMap = new HashMap<>();
|
||||||
|
String defaultTenantId = new TreeMap<>(appsMap).firstKey();
|
||||||
|
|
||||||
|
for (Map.Entry<String, WxMaSingleProperties> entry : appsMap.entrySet()) {
|
||||||
|
String tenantId = entry.getKey();
|
||||||
|
WxMaSingleProperties wxMaSingleProperties = entry.getValue();
|
||||||
|
WxMaDefaultConfigImpl config = this.wxMaConfigStorage(wxMaMultiProperties);
|
||||||
|
this.configApp(config, wxMaSingleProperties);
|
||||||
|
this.configHttp(config, storage);
|
||||||
|
configsMap.put(tenantId, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置多配置到共享的 WxMaService
|
||||||
|
sharedService.setMultiConfigs(configsMap, defaultTenantId);
|
||||||
|
|
||||||
|
log.info("微信小程序多租户服务初始化完成,使用共享模式(SHARED),共配置 {} 个租户,共享一个 HTTP 客户端", appsMap.size());
|
||||||
|
return new WxMaMultiServicesSharedImpl(sharedService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型创建 WxMaService 实例
|
||||||
|
*/
|
||||||
|
private WxMaService createWxMaServiceByType(WxMaMultiProperties.HttpClientType httpClientType) {
|
||||||
|
switch (httpClientType) {
|
||||||
|
case OK_HTTP:
|
||||||
|
return new WxMaServiceOkHttpImpl();
|
||||||
|
case JODD_HTTP:
|
||||||
|
return new WxMaServiceJoddHttpImpl();
|
||||||
|
case HTTP_CLIENT:
|
||||||
|
return new WxMaServiceHttpClientImpl();
|
||||||
|
default:
|
||||||
|
return new WxMaServiceImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置 WxMaService 的通用参数
|
||||||
|
*/
|
||||||
|
private void configureWxMaService(WxMaService wxMaService, WxMaMultiProperties.ConfigStorage storage) {
|
||||||
|
int maxRetryTimes = storage.getMaxRetryTimes();
|
||||||
|
if (maxRetryTimes < 0) {
|
||||||
|
maxRetryTimes = 0;
|
||||||
|
}
|
||||||
|
int retrySleepMillis = storage.getRetrySleepMillis();
|
||||||
|
if (retrySleepMillis < 0) {
|
||||||
|
retrySleepMillis = 1000;
|
||||||
|
}
|
||||||
|
wxMaService.setRetrySleepMillis(retrySleepMillis);
|
||||||
|
wxMaService.setMaxRetryTimes(maxRetryTimes);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置 WxMaDefaultConfigImpl
|
* 配置 WxMaDefaultConfigImpl
|
||||||
*
|
*
|
||||||
@@ -77,34 +164,9 @@ public abstract class AbstractWxMaConfiguration {
|
|||||||
|
|
||||||
public WxMaService wxMaService(WxMaConfig wxMaConfig, WxMaMultiProperties wxMaMultiProperties) {
|
public WxMaService wxMaService(WxMaConfig wxMaConfig, WxMaMultiProperties wxMaMultiProperties) {
|
||||||
WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage();
|
WxMaMultiProperties.ConfigStorage storage = wxMaMultiProperties.getConfigStorage();
|
||||||
WxMaMultiProperties.HttpClientType httpClientType = storage.getHttpClientType();
|
WxMaService wxMaService = createWxMaServiceByType(storage.getHttpClientType());
|
||||||
WxMaService wxMaService;
|
|
||||||
switch (httpClientType) {
|
|
||||||
case OK_HTTP:
|
|
||||||
wxMaService = new WxMaServiceOkHttpImpl();
|
|
||||||
break;
|
|
||||||
case JODD_HTTP:
|
|
||||||
wxMaService = new WxMaServiceJoddHttpImpl();
|
|
||||||
break;
|
|
||||||
case HTTP_CLIENT:
|
|
||||||
wxMaService = new WxMaServiceHttpClientImpl();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
wxMaService = new WxMaServiceImpl();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
wxMaService.setWxMaConfig(wxMaConfig);
|
wxMaService.setWxMaConfig(wxMaConfig);
|
||||||
int maxRetryTimes = storage.getMaxRetryTimes();
|
configureWxMaService(wxMaService, storage);
|
||||||
if (maxRetryTimes < 0) {
|
|
||||||
maxRetryTimes = 0;
|
|
||||||
}
|
|
||||||
int retrySleepMillis = storage.getRetrySleepMillis();
|
|
||||||
if (retrySleepMillis < 0) {
|
|
||||||
retrySleepMillis = 1000;
|
|
||||||
}
|
|
||||||
wxMaService.setRetrySleepMillis(retrySleepMillis);
|
|
||||||
wxMaService.setMaxRetryTimes(maxRetryTimes);
|
|
||||||
return wxMaService;
|
return wxMaService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
@@ -116,6 +116,15 @@ public class WxMaMultiProperties implements Serializable {
|
|||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
private int retrySleepMillis = 1000;
|
private int retrySleepMillis = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多租户实现模式.
|
||||||
|
* <ul>
|
||||||
|
* <li>ISOLATED: 为每个租户创建独立的 WxMaService 实例(默认)</li>
|
||||||
|
* <li>SHARED: 使用单个 WxMaService 实例管理所有租户配置,共享 HTTP 客户端</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
private MultiTenantMode multiTenantMode = MultiTenantMode.ISOLATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StorageType {
|
public enum StorageType {
|
||||||
@@ -151,4 +160,19 @@ public class WxMaMultiProperties implements Serializable {
|
|||||||
*/
|
*/
|
||||||
JODD_HTTP
|
JODD_HTTP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum MultiTenantMode {
|
||||||
|
/**
|
||||||
|
* 隔离模式:为每个租户创建独立的 WxMaService 实例.
|
||||||
|
* 优点:线程安全,不依赖 ThreadLocal
|
||||||
|
* 缺点:每个租户创建独立的 HTTP 客户端,资源占用较多
|
||||||
|
*/
|
||||||
|
ISOLATED,
|
||||||
|
/**
|
||||||
|
* 共享模式:使用单个 WxMaService 实例管理所有租户配置.
|
||||||
|
* 优点:共享 HTTP 客户端,节省资源
|
||||||
|
* 缺点:依赖 ThreadLocal 切换配置,异步场景需注意
|
||||||
|
*/
|
||||||
|
SHARED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.miniapp.service;
|
||||||
|
|
||||||
|
import cn.binarywang.wx.miniapp.api.WxMaService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信小程序 {@link WxMaMultiServices} 共享式实现.
|
||||||
|
* <p>
|
||||||
|
* 使用单个 WxMaService 实例管理多个租户配置,通过 switchover 切换租户。
|
||||||
|
* 相比 {@link WxMaMultiServicesImpl},此实现共享 HTTP 客户端,节省资源。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 注意:由于使用 ThreadLocal 切换配置,在异步或多线程场景需要特别注意线程上下文切换。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Binary Wang
|
||||||
|
* created on 2026/1/9
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WxMaMultiServicesSharedImpl implements WxMaMultiServices {
|
||||||
|
private final WxMaService sharedWxMaService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxMaService getWxMaService(String tenantId) {
|
||||||
|
if (tenantId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 使用 switchover 检查配置是否存在,保持与隔离模式 API 行为一致(不存在时返回 null)
|
||||||
|
if (!sharedWxMaService.switchover(tenantId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return sharedWxMaService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeWxMaService(String tenantId) {
|
||||||
|
if (tenantId != null) {
|
||||||
|
sharedWxMaService.removeConfig(tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加租户配置到共享的 WxMaService 实例
|
||||||
|
*
|
||||||
|
* @param tenantId 租户 ID
|
||||||
|
* @param wxMaService 要添加配置的 WxMaService(仅使用其配置,不使用其实例)
|
||||||
|
*/
|
||||||
|
public void addWxMaService(String tenantId, WxMaService wxMaService) {
|
||||||
|
if (tenantId != null && wxMaService != null) {
|
||||||
|
sharedWxMaService.addConfig(tenantId, wxMaService.getWxMaConfig());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+98
-32
@@ -4,18 +4,25 @@ import com.binarywang.spring.starter.wxjava.mp.properties.WxMpMultiProperties;
|
|||||||
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpSingleProperties;
|
import com.binarywang.spring.starter.wxjava.mp.properties.WxMpSingleProperties;
|
||||||
import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServices;
|
import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServices;
|
||||||
import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServicesImpl;
|
import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServicesImpl;
|
||||||
|
import com.binarywang.spring.starter.wxjava.mp.service.WxMpMultiServicesSharedImpl;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import me.chanjar.weixin.mp.api.WxMpService;
|
import me.chanjar.weixin.mp.api.WxMpService;
|
||||||
import me.chanjar.weixin.mp.api.impl.*;
|
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpClientImpl;
|
||||||
|
import me.chanjar.weixin.mp.api.impl.WxMpServiceHttpComponentsImpl;
|
||||||
|
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
|
||||||
|
import me.chanjar.weixin.mp.api.impl.WxMpServiceJoddHttpImpl;
|
||||||
|
import me.chanjar.weixin.mp.api.impl.WxMpServiceOkHttpImpl;
|
||||||
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
|
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
|
||||||
import me.chanjar.weixin.mp.config.WxMpHostConfig;
|
import me.chanjar.weixin.mp.config.WxMpHostConfig;
|
||||||
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
|
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +41,7 @@ public abstract class AbstractWxMpConfiguration {
|
|||||||
log.warn("微信公众号应用参数未配置,通过 WxMpMultiServices#getWxMpService(\"tenantId\")获取实例将返回空");
|
log.warn("微信公众号应用参数未配置,通过 WxMpMultiServices#getWxMpService(\"tenantId\")获取实例将返回空");
|
||||||
return new WxMpMultiServicesImpl();
|
return new WxMpMultiServicesImpl();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
|
* 校验 appId 是否唯一,避免使用 redis 缓存 token、ticket 时错乱。
|
||||||
*
|
*
|
||||||
@@ -50,9 +58,26 @@ public abstract class AbstractWxMpConfiguration {
|
|||||||
throw new RuntimeException("请确保微信公众号配置 appId 的唯一性");
|
throw new RuntimeException("请确保微信公众号配置 appId 的唯一性");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WxMpMultiServicesImpl services = new WxMpMultiServicesImpl();
|
|
||||||
|
|
||||||
|
// 根据配置选择多租户模式
|
||||||
|
WxMpMultiProperties.MultiTenantMode mode = wxMpMultiProperties.getConfigStorage().getMultiTenantMode();
|
||||||
|
if (mode == WxMpMultiProperties.MultiTenantMode.SHARED) {
|
||||||
|
return createSharedMultiServices(appsMap, wxMpMultiProperties);
|
||||||
|
} else {
|
||||||
|
return createIsolatedMultiServices(appsMap, wxMpMultiProperties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建隔离模式的多租户服务(每个租户独立 WxMpService 实例)
|
||||||
|
*/
|
||||||
|
private WxMpMultiServices createIsolatedMultiServices(
|
||||||
|
Map<String, WxMpSingleProperties> appsMap,
|
||||||
|
WxMpMultiProperties wxMpMultiProperties) {
|
||||||
|
|
||||||
|
WxMpMultiServicesImpl services = new WxMpMultiServicesImpl();
|
||||||
Set<Map.Entry<String, WxMpSingleProperties>> entries = appsMap.entrySet();
|
Set<Map.Entry<String, WxMpSingleProperties>> entries = appsMap.entrySet();
|
||||||
|
|
||||||
for (Map.Entry<String, WxMpSingleProperties> entry : entries) {
|
for (Map.Entry<String, WxMpSingleProperties> entry : entries) {
|
||||||
String tenantId = entry.getKey();
|
String tenantId = entry.getKey();
|
||||||
WxMpSingleProperties wxMpSingleProperties = entry.getValue();
|
WxMpSingleProperties wxMpSingleProperties = entry.getValue();
|
||||||
@@ -63,9 +88,78 @@ public abstract class AbstractWxMpConfiguration {
|
|||||||
WxMpService wxMpService = this.wxMpService(storage, wxMpMultiProperties);
|
WxMpService wxMpService = this.wxMpService(storage, wxMpMultiProperties);
|
||||||
services.addWxMpService(tenantId, wxMpService);
|
services.addWxMpService(tenantId, wxMpService);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.info("微信公众号多租户服务初始化完成,使用隔离模式(ISOLATED),共配置 {} 个租户", appsMap.size());
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建共享模式的多租户服务(单个 WxMpService 实例管理多个配置)
|
||||||
|
*/
|
||||||
|
private WxMpMultiServices createSharedMultiServices(
|
||||||
|
Map<String, WxMpSingleProperties> appsMap,
|
||||||
|
WxMpMultiProperties wxMpMultiProperties) {
|
||||||
|
|
||||||
|
// 创建共享的 WxMpService 实例
|
||||||
|
WxMpMultiProperties.ConfigStorage storage = wxMpMultiProperties.getConfigStorage();
|
||||||
|
WxMpService sharedService = createWxMpServiceByType(storage.getHttpClientType());
|
||||||
|
configureWxMpService(sharedService, storage);
|
||||||
|
|
||||||
|
// 准备所有租户的配置,使用 TreeMap 保证顺序一致性
|
||||||
|
Map<String, WxMpConfigStorage> configsMap = new HashMap<>();
|
||||||
|
String defaultTenantId = new TreeMap<>(appsMap).firstKey();
|
||||||
|
|
||||||
|
for (Map.Entry<String, WxMpSingleProperties> entry : appsMap.entrySet()) {
|
||||||
|
String tenantId = entry.getKey();
|
||||||
|
WxMpSingleProperties wxMpSingleProperties = entry.getValue();
|
||||||
|
WxMpDefaultConfigImpl config = this.wxMpConfigStorage(wxMpMultiProperties);
|
||||||
|
this.configApp(config, wxMpSingleProperties);
|
||||||
|
this.configHttp(config, storage);
|
||||||
|
this.configHost(config, wxMpMultiProperties.getHosts());
|
||||||
|
configsMap.put(tenantId, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置多配置到共享的 WxMpService
|
||||||
|
sharedService.setMultiConfigStorages(configsMap, defaultTenantId);
|
||||||
|
|
||||||
|
log.info("微信公众号多租户服务初始化完成,使用共享模式(SHARED),共配置 {} 个租户,共享一个 HTTP 客户端", appsMap.size());
|
||||||
|
return new WxMpMultiServicesSharedImpl(sharedService);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据类型创建 WxMpService 实例
|
||||||
|
*/
|
||||||
|
private WxMpService createWxMpServiceByType(WxMpMultiProperties.HttpClientType httpClientType) {
|
||||||
|
switch (httpClientType) {
|
||||||
|
case OK_HTTP:
|
||||||
|
return new WxMpServiceOkHttpImpl();
|
||||||
|
case JODD_HTTP:
|
||||||
|
return new WxMpServiceJoddHttpImpl();
|
||||||
|
case HTTP_CLIENT:
|
||||||
|
return new WxMpServiceHttpClientImpl();
|
||||||
|
case HTTP_COMPONENTS:
|
||||||
|
return new WxMpServiceHttpComponentsImpl();
|
||||||
|
default:
|
||||||
|
return new WxMpServiceImpl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置 WxMpService 的通用参数
|
||||||
|
*/
|
||||||
|
private void configureWxMpService(WxMpService wxMpService, WxMpMultiProperties.ConfigStorage storage) {
|
||||||
|
int maxRetryTimes = storage.getMaxRetryTimes();
|
||||||
|
if (maxRetryTimes < 0) {
|
||||||
|
maxRetryTimes = 0;
|
||||||
|
}
|
||||||
|
int retrySleepMillis = storage.getRetrySleepMillis();
|
||||||
|
if (retrySleepMillis < 0) {
|
||||||
|
retrySleepMillis = 1000;
|
||||||
|
}
|
||||||
|
wxMpService.setRetrySleepMillis(retrySleepMillis);
|
||||||
|
wxMpService.setMaxRetryTimes(maxRetryTimes);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置 WxMpDefaultConfigImpl
|
* 配置 WxMpDefaultConfigImpl
|
||||||
*
|
*
|
||||||
@@ -76,37 +170,9 @@ public abstract class AbstractWxMpConfiguration {
|
|||||||
|
|
||||||
public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpMultiProperties wxMpMultiProperties) {
|
public WxMpService wxMpService(WxMpConfigStorage configStorage, WxMpMultiProperties wxMpMultiProperties) {
|
||||||
WxMpMultiProperties.ConfigStorage storage = wxMpMultiProperties.getConfigStorage();
|
WxMpMultiProperties.ConfigStorage storage = wxMpMultiProperties.getConfigStorage();
|
||||||
WxMpMultiProperties.HttpClientType httpClientType = storage.getHttpClientType();
|
WxMpService wxMpService = createWxMpServiceByType(storage.getHttpClientType());
|
||||||
WxMpService wxMpService;
|
|
||||||
switch (httpClientType) {
|
|
||||||
case OK_HTTP:
|
|
||||||
wxMpService = new WxMpServiceOkHttpImpl();
|
|
||||||
break;
|
|
||||||
case JODD_HTTP:
|
|
||||||
wxMpService = new WxMpServiceJoddHttpImpl();
|
|
||||||
break;
|
|
||||||
case HTTP_CLIENT:
|
|
||||||
wxMpService = new WxMpServiceHttpClientImpl();
|
|
||||||
break;
|
|
||||||
case HTTP_COMPONENTS:
|
|
||||||
wxMpService = new WxMpServiceHttpComponentsImpl();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
wxMpService = new WxMpServiceImpl();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
wxMpService.setWxMpConfigStorage(configStorage);
|
wxMpService.setWxMpConfigStorage(configStorage);
|
||||||
int maxRetryTimes = storage.getMaxRetryTimes();
|
configureWxMpService(wxMpService, storage);
|
||||||
if (maxRetryTimes < 0) {
|
|
||||||
maxRetryTimes = 0;
|
|
||||||
}
|
|
||||||
int retrySleepMillis = storage.getRetrySleepMillis();
|
|
||||||
if (retrySleepMillis < 0) {
|
|
||||||
retrySleepMillis = 1000;
|
|
||||||
}
|
|
||||||
wxMpService.setRetrySleepMillis(retrySleepMillis);
|
|
||||||
wxMpService.setMaxRetryTimes(maxRetryTimes);
|
|
||||||
return wxMpService;
|
return wxMpService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
@@ -116,6 +116,15 @@ public class WxMpMultiProperties implements Serializable {
|
|||||||
* </pre>
|
* </pre>
|
||||||
*/
|
*/
|
||||||
private int retrySleepMillis = 1000;
|
private int retrySleepMillis = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多租户实现模式.
|
||||||
|
* <ul>
|
||||||
|
* <li>ISOLATED: 为每个租户创建独立的 WxMpService 实例(默认)</li>
|
||||||
|
* <li>SHARED: 使用单个 WxMpService 实例管理所有租户配置,共享 HTTP 客户端</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
private MultiTenantMode multiTenantMode = MultiTenantMode.ISOLATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StorageType {
|
public enum StorageType {
|
||||||
@@ -155,4 +164,19 @@ public class WxMpMultiProperties implements Serializable {
|
|||||||
*/
|
*/
|
||||||
JODD_HTTP
|
JODD_HTTP
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum MultiTenantMode {
|
||||||
|
/**
|
||||||
|
* 隔离模式:为每个租户创建独立的 WxMpService 实例.
|
||||||
|
* 优点:线程安全,不依赖 ThreadLocal
|
||||||
|
* 缺点:每个租户创建独立的 HTTP 客户端,资源占用较多
|
||||||
|
*/
|
||||||
|
ISOLATED,
|
||||||
|
/**
|
||||||
|
* 共享模式:使用单个 WxMpService 实例管理所有租户配置.
|
||||||
|
* 优点:共享 HTTP 客户端,节省资源
|
||||||
|
* 缺点:依赖 ThreadLocal 切换配置,异步场景需注意
|
||||||
|
*/
|
||||||
|
SHARED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
@@ -0,0 +1,53 @@
|
|||||||
|
package com.binarywang.spring.starter.wxjava.mp.service;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import me.chanjar.weixin.mp.api.WxMpService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信公众号 {@link WxMpMultiServices} 共享式实现.
|
||||||
|
* <p>
|
||||||
|
* 使用单个 WxMpService 实例管理多个租户配置,通过 switchover 切换租户。
|
||||||
|
* 相比 {@link WxMpMultiServicesImpl},此实现共享 HTTP 客户端,节省资源。
|
||||||
|
* </p>
|
||||||
|
* <p>
|
||||||
|
* 注意:由于使用 ThreadLocal 切换配置,在异步或多线程场景需要特别注意线程上下文切换。
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Binary Wang
|
||||||
|
* created on 2026/1/9
|
||||||
|
*/
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WxMpMultiServicesSharedImpl implements WxMpMultiServices {
|
||||||
|
private final WxMpService sharedWxMpService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WxMpService getWxMpService(String tenantId) {
|
||||||
|
if (tenantId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 使用 switchover 检查配置是否存在,保持与隔离模式 API 行为一致(不存在时返回 null)
|
||||||
|
if (!sharedWxMpService.switchover(tenantId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return sharedWxMpService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeWxMpService(String tenantId) {
|
||||||
|
if (tenantId != null) {
|
||||||
|
sharedWxMpService.removeConfigStorage(tenantId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加租户配置到共享的 WxMpService 实例
|
||||||
|
*
|
||||||
|
* @param tenantId 租户 ID
|
||||||
|
* @param wxMpService 要添加配置的 WxMpService(仅使用其配置,不使用其实例)
|
||||||
|
*/
|
||||||
|
public void addWxMpService(String tenantId, WxMpService wxMpService) {
|
||||||
|
if (tenantId != null && wxMpService != null) {
|
||||||
|
sharedWxMpService.addConfigStorage(tenantId, wxMpService.getWxMpConfigStorage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user