[17 tools called] 先修改 user-app 的数据源 key,然后新增租户数据源管理类和租户切换过滤器,最后编译+写文档。 [22 tools called] 下面是这次对 C 端 `fs-user-app` 的 SaaS 改造情况、用法和说明。 --- ### 一、这次具体改了什么(代码层面) #### 1. 数据源 key 统一为字符串(MASTER/SLAVE) - **文件**:`com.fs.framework.config.DataSourceConfig` - **改动**:将原来用枚举作为 key 的写法统一为字符串 key: ```java targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource); targetDataSources.put(DataSourceType.SLAVE.name(), slaveDataSource); ``` - **原因**:`DynamicDataSourceContextHolder.setDataSourceType()` 传的是字符串(如 `"MASTER"`),以前 `targetDataSources` 用的是 `DataSourceType.MASTER`(枚举),两者不相等,后面做动态切库会出问题,所以先把 key 统一成字符串。 #### 2. 新增租户数据源管理类 `TenantDataSourceManager` - **文件**:`com.fs.framework.datasource.TenantDataSourceManager` - **作用**: - 根据主库里的 `TenantInfo`(`tenant_info` 表)动态创建租户库的数据源(`DruidDataSource`); - 把新数据源挂到当前 `DynamicDataSource` 的 `resolvedDataSources` 上; - 调用 `DynamicDataSourceContextHolder.setDataSourceType("tenant:{tenantId}")` 把当前线程切到对应租户库; - 提供 `clear()` 方法在请求结束时清理 ThreadLocal,避免线程复用串库。 - **实现要点**: - 内部用 `TENANT_DS_CACHE` 缓存租户数据源,避免重复创建连接池; - 用反射访问 `AbstractRoutingDataSource.resolvedDataSources`,运行时追加数据源; - 租户 key 统一为 `tenant:{id}`,和 admin/company 一致; - 注释里已经详细解释了用途和注意点。 #### 3. 新增租户切换过滤器 `AppTenantSwitchFilter` - **文件**:`com.fs.framework.filter.AppTenantSwitchFilter` - **注解**:`@Component`、`@Order(5)`,继承 `OncePerRequestFilter`。 - **入口约定**: - 使用请求头 **`X-Tenant-Code`** 作为 C 端的租户标识(和 admin 的 `tenantCode` 含义一致)。 - 前端 app / H5 / 小程序只要在请求头上带上这个租户编码,本次改造就会按该租户切库并加载租户配置。 - **过滤逻辑(按请求)**: 1. 从请求头读取 `X-Tenant-Code`: - 为空:**直接放行**,不做任何租户切换,兼容老逻辑(单库)。 2. 切到主库 MASTER: - `DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());` 3. 用 `TenantInfoService` 在主库根据 `tenantCode` 查 `tenant_info`: - 找不到 / 状态不为启用 / 已过期:记录日志,**放行但不切库**(保持单库行为)。 4. 对有效租户调用 `tenantDataSourceManager.switchTenant(tenant)` 切到该租户库: - 如无数据源则创建一个 `DruidDataSource` 并挂到 `DynamicDataSource`; - 当前线程的数据源 key 变为 `tenant:{id}`。 5. 在租户库中查询租户级项目配置: - `SysConfig cfg = sysConfigMapper.selectConfigByConfigKey("projectConfig");` - 若有值:`TenantConfigContext.set(JSONObject.parseObject(cfg.getConfigValue()));` - 然后调用 `ProjectConfig.loadTenantConfigsFromContext()`,让 Wx/支付等配置指向当前租户。 6. 调用 `filterChain.doFilter(request, response)`,后面的 Controller/Service/Mapper 就跑在该租户库上,并使用该租户的项目配置。 7. `finally` 中清理上下文: - `ProjectConfig.clearTenantConfigs();` - `TenantConfigContext.clear();` - `tenantDataSourceManager.clear();`(内部清除数据源标记)。 - **兼容性**: - **不带 `X-Tenant-Code`**:一切照旧(单库)。 - **错误/禁用/过期租户**:仅打日志,不中断请求,退回单库行为(你可以后面再根据需要调整为直接返回错误)。 --- ### 二、C 端前端需要怎么配合(非常关键) 1. **每个 C 端请求都带上 `X-Tenant-Code`**: - header 名:`X-Tenant-Code`; - 值:你的租户编码(主库 `tenant_info.tenant_code`)。 2. 包括以下类型的请求: - 登录接口(`/app/app/login`、`/store/app/app/login` 等); - 用户信息、课程、订单、SOP 等所有业务接口; - 登出(如有)等。 3. 租户编码获取方式(你可以按业务决定): - 用户进入 app 之前选择医院/企业; - 二级域名解析(如 `tenantA.xxx.com` → `tenantCode=tenantA`),前端启动时就把这个值带在 header 里。 **只要 header 带得上,多租户这层就自动生效。** --- ### 三、现有登录 / Token / Redis 行为说明 - user-app 目前的登录逻辑(`AppLoginController` / `AppLoginScrmController`)**没有改**: - 还是用 `JwtUtils.generateToken(userId)` 生成 `APPToken`; - 还是把 token 存到 Redis 的 `userToken:{userId}`; - 鉴权还是用 header 里的 `APPToken` 解出 `userId`。 - 这次的 SaaS 改造: - **不改 token 结构**,不强行在 token 里塞 `tenantId`; - **按请求头的 `X-Tenant-Code` 切库**,把“用户归属哪个租户”的责任交给前端来决定; - Redis 这块暂时也不按租户做前缀,保证旧逻辑无感知(如果后面你要对 C 端 Redis 也做租户隔离,可以再单独一轮改造)。 --- ### 四、如何自测 user-app C 端的多租户 你可以用 Postman 或直接在 app 上做几组简单验证: 1. **多租户切库验证** - 在主库的 `tenant_info` 中配置两个租户 A / B,对应不同业务库,两个库中插入不同的测试数据; - 调用同一个 C 端接口(比如课程列表),分别带: - `X-Tenant-Code: A` - `X-Tenant-Code: B` - 确认两个请求看到的是各自库中的数据(比如医院名/课程名不同)。 2. **租户配置隔离验证** - 在租户 A/B 的各自库的 `sys_config.projectConfig` 里,配置不同的微信 / 支付参数(可以改一个明显的字段); - 调用依赖这些配置的接口(如微信登录、支付相关接口),看是否按不同租户配置生效。 3. **兼容性验证** - 不带 `X-Tenant-Code` 调接口,确保行为与改造前一致(不报错,不异常,仍然能正常访问)。 4. **异常租户验证** - 用一个不存在的 `X-Tenant-Code` 请求,观察日志里有告警(租户不存在 / 禁用 / 过期),同时请求不会抛异常,只是退回用主库。 --- ### 五、user-app 模块改造说明文档 我已经帮你在项目里生成了一个专门的文档: - **路径**:`docs/user-app模块SaaS改造说明.md` 里面详细写了: - 本次改造的目标、原则; - DataSourceConfig 的改动; - `TenantDataSourceManager` 和 `AppTenantSwitchFilter` 的设计与实现细节; - 前端如何带 `X-Tenant-Code`; - 推荐的自测场景。 你可以直接把这个文档发给前端和其他后端同事,对接起来会更顺畅。 --- ### 六、总结(给你一个操作要点版) - **后端**: - 已经支持通过 `X-Tenant-Code` 切换到对应租户库,并加载该租户的 `projectConfig`。 - 老逻辑兼容,不带头还是单库。 - **前端需要做的一件事**: - 所有 user-app 请求(含登录)统一加上请求头: - `X-Tenant-Code: <对应的租户编码>`。 如果你希望下一步把 C 端的 Token 和 Redis 也做成像 admin/company 那种“token 里带 tenantId、Redis key 按租户隔离”的模式,我也可以再帮你设计一套不影响现有用户的渐进改法。