本文档说明用户端模块 fs-user-app 在 SaaS 场景下的改造点:如何按租户切库、如何兼容旧版本以及前端需要配合的地方。
sys_config.projectConfig 在租户库中存储,并在请求链路中按租户加载。X-Tenant-Code 传递租户编码:tenant_info.tenant_code 字段;X-Tenant-Code:Client(带 X-Tenant-Code)
↓
AppTenantSwitchFilter(fs-user-app)
1. 切到主库 MASTER
2. 查 tenant_info(tenantCode)
3. TenantDataSourceManager.switchTenant(tenant) → 切库到租户库
4. 从租户库 sys_config.projectConfig 加载租户配置 → TenantConfigContext + ProjectConfig
↓
Controller / Service / Mapper
- Mapper 所在数据源 = 当前租户库
- 业务中读取租户配置(WxOpenProperties、ProjectConfig 等)= 当前租户配置
文件:fs-user-app/src/main/java/com/fs/framework/config/DataSourceConfig.java
DataSourceType.MASTER(枚举)作为 targetDataSources 的 key,而 DynamicDataSourceContextHolder 使用字符串(DataSourceType.MASTER.name())作为 lookup key,两者不一致。// 使用字符串 key(MASTER/SLAVE),与 DynamicDataSourceContextHolder 中 setDataSourceType 保持一致
targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource);
效果:
后续调用 DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name()) 时,能正确匹配到 master 数据源。
文件:fs-user-app/src/main/java/com/fs/framework/datasource/TenantDataSourceManager.java
核心职责:
TenantInfo(主库 tenant_info 表)动态创建 DruidDataSource;DynamicDataSource 的 resolvedDataSources 中;DynamicDataSourceContextHolder.setDataSourceType("tenant:{id}") 切到指定租户库;clear() 方法清理当前线程的数据源标记。关键实现要点:
private static final Map<String, DataSource> TENANT_DS_CACHE,避免重复创建连接池;AbstractRoutingDataSource.resolvedDataSources,在运行时追加租户数据源;tenant:{tenantId},与 admin/company 的约定一致。文件:fs-user-app/src/main/java/com/fs/framework/filter/AppTenantSwitchFilter.java
类型:OncePerRequestFilter,@Component,@Order(5)。
工作流程(每个 HTTP 请求):
X-Tenant-Code:DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());TenantInfoService.selectTenantInfoList 根据 tenantCode 查询租户信息:
status != 1 或已过期:记录日志 + 放行。tenantDataSourceManager.switchTenant(tenant):sysConfigMapper.selectConfigByConfigKey("projectConfig");TenantConfigContext.set(JSONObject.parseObject(cfg.getConfigValue()));ProjectConfig.loadTenantConfigsFromContext(),使 Wx / 支付等配置指向当前租户。filterChain.doFilter(request, response) 继续后续业务处理。finally 中清理:
ProjectConfig.clearTenantConfigs();TenantConfigContext.clear();tenantDataSourceManager.clear();(内部清理 DynamicDataSourceContextHolder)。说明:
APPToken 仍按原逻辑生成和校验)。X-Tenant-Code,对应业务就会在该租户库上执行、并使用当前租户的项目配置。X-Tenant-Code 的请求:X-Tenant-Code:tenant_info 中配置两个租户 A/B,对应不同业务库。X-Tenant-Code: A 和 X-Tenant-Code: B 调用同一接口(例如获取课程、订单列表),确认结果数据来自不同库。sys_config.projectConfig 中配置不同的微信/支付参数,观察调用相关功能时是否生效为各自租户的配置。X-Tenant-Code 调用常用接口,确认行为与改造前一致(不报错、不串库)。X-Tenant-Code(不存在 / 已禁用 / 已过期),确认日志中有告警,且请求退回单库行为(不异常中断)。tenantId;TenantKeyRedisSerializer,并结合安全上下文或 Jwt 承载 tenantId 进行扩展(当前版本暂保留原有 Redis 行为,避免对线上 C 端产生影响)。\n