|
| 1 | +--- |
| 2 | +title: 多租户 |
| 3 | +--- |
| 4 | + |
| 5 | +fba 已新增多租户模式适配,但==当前主仓库提供的是“多租户运行模式支持”而不是完整的租户业务系统=={.note} |
| 6 | + |
| 7 | +完整租户能力需安装 [tenant 插件](https://github.com/fastapi-practices/tenant),该插件当前提供: |
| 8 | + |
| 9 | +- 租户管理 |
| 10 | +- 套餐管理 |
| 11 | +- 行级数据隔离 |
| 12 | + |
| 13 | +::: caution |
| 14 | +此能力当前仍处于实验性阶段,相关适配来自 [PR #1101](https://github.com/fastapi-practices/fastapi-best-architecture/pull/1101) |
| 15 | +::: |
| 16 | + |
| 17 | +## 常见隔离模型 |
| 18 | + |
| 19 | +参考 [django-tenants](https://github.com/django-tenants/django-tenants?tab=readme-ov-file#why-schemas) |
| 20 | +对多租户的总结,常见方案通常有 3 类: |
| 21 | + |
| 22 | +### 独立式方案 |
| 23 | + |
| 24 | +每个租户使用独立数据库,隔离性最强,但运维、迁移、资源管理和成本控制都会更复杂 |
| 25 | + |
| 26 | +### 半隔离式方案 |
| 27 | + |
| 28 | +所有租户共用一个数据库实例,但每个租户拥有独立 schema。它通常被视为一种折中方案: |
| 29 | + |
| 30 | +- 相比独立数据库,更容易统一运维 |
| 31 | +- 相比共享 schema,隔离边界更清晰 |
| 32 | +- 能复用同一数据库连接、缓存和内存资源 |
| 33 | + |
| 34 | +### 共享方案 |
| 35 | + |
| 36 | +所有租户共用同一套表结构,通常在业务表中增加 `tenant_id` 一类字段,再通过查询条件、约束、上下文和中间件完成隔离 |
| 37 | + |
| 38 | +## Schema 方案 |
| 39 | + |
| 40 | +`django-tenants` 选择的是“半隔离式方案”,并认为它在简单性与性能之间取得了较好的平衡: |
| 41 | + |
| 42 | +- 只需要维护一个数据库实例 |
| 43 | +- 对现有业务代码的入侵通常较小 |
| 44 | +- 租户之间的物理边界比 `tenant_id` 过滤更直观 |
| 45 | +- 在 PostgreSQL 中可借助 `search_path` 按请求切换到目标 schema |
| 46 | + |
| 47 | +不过,这并不意味此方案一定优于其他方案。它同样有前提和边界,例如更依赖数据库能力、对 PostgreSQL 更友好、跨数据库兼容性也更受限制 |
| 48 | + |
| 49 | +## 方案应用 |
| 50 | + |
| 51 | +当前 fba 的多租户模式采用“共享方案”,这样做的主要目的是: |
| 52 | + |
| 53 | +- 更容易兼容 PostgreSQL 与 MySQL |
| 54 | +- 更容易复用现有模型、插件和初始化 SQL 体系 |
| 55 | +- 对现有单租户代码的改造路径更平滑 |
| 56 | + |
| 57 | +对应地,这种方案也要求开发者更加重视 `tenant_id` 注入、查询过滤、唯一约束和插件兼容性,否则更容易出现越权或串租户风险 |
| 58 | + |
| 59 | +## 如何开启 |
| 60 | + |
| 61 | +:::: steps |
| 62 | + |
| 63 | +1. 安装 [tenant 插件](https://github.com/fastapi-practices/tenant) |
| 64 | + |
| 65 | +2. 修改 `backend/core/conf.py` |
| 66 | + |
| 67 | + ```py |
| 68 | + # 租户 |
| 69 | + TENANT_ENABLED: bool = True |
| 70 | + TENANT_DEFAULT_ID: int = 0 |
| 71 | + ``` |
| 72 | + |
| 73 | +3. 重新初始化数据库 |
| 74 | + |
| 75 | + 启用后,部分表会新增 `tenant_id` 字段,部分唯一约束和初始化 SQL 也会变化 |
| 76 | + |
| 77 | + ::: warning |
| 78 | + 如果你已经执行过初始化、迁移或已写入正式数据,不应直接在原数据库上切换,建议清空相关表后重新初始化 |
| 79 | + ::: |
| 80 | + |
| 81 | +:::: |
| 82 | + |
| 83 | +## 默认租户 |
| 84 | + |
| 85 | +`TENANT_DEFAULT_ID` 默认为 `0`,它主要用于: |
| 86 | + |
| 87 | +- 默认租户兜底 |
| 88 | +- 非授权接口初始化上下文 |
| 89 | +- 日志记录中的默认租户 ID |
| 90 | +- 登录接口未显式传入 `tenant_id` 时的默认值 |
| 91 | + |
| 92 | +## 注意事项 |
| 93 | + |
| 94 | +- 完整的租户后台、套餐管理、租户状态校验等业务能力由 `tenant` 插件提供,主仓库主要负责基础适配 |
| 95 | +- 当前实现为共享数据库下的租户模式适配,隔离能力以插件内的行级数据隔离实现为准 |
| 96 | +- 如果你同时使用 OAuth2、通知公告等插件,请确认对应插件版本已兼容多租户模式 |
0 commit comments