53 lines
2.3 KiB
Markdown
53 lines
2.3 KiB
Markdown
# 第三阶段:租户隔离、共享题库与前端运行时
|
||
|
||
状态:已完成主线设计和实现。
|
||
|
||
## 目标
|
||
|
||
- 普通业务代码默认只能读取和写入当前租户数据。
|
||
- 公共题库由平台主体拥有,有效租户可访问。
|
||
- 租户私题只属于本租户。
|
||
- 公共题和私题可混合组卷、答题、收藏、错题和统计。
|
||
- 租户自定义域名安全解析到统一前端运行时配置。
|
||
|
||
## 结果
|
||
|
||
- `ITenantContext` 只读化,请求租户由中间件解析。
|
||
- EF Core Query Filter 自动按租户过滤。
|
||
- SaveChanges 拦截器自动写入当前租户并拒绝跨租户写入。
|
||
- DbContext 使用 scoped `AddDbContext`,避免池化串租户状态。
|
||
- `TenantResolutionMiddleware` 在认证前按可信 Host 解析租户。
|
||
- JWT tenant claim 与 Host 解析不一致时返回 403。
|
||
- 未知、Pending、禁用域名在业务前返回 404。
|
||
- 唯一 `PlatformOwned` 租户拥有公共题库、公共题、公共题版本和公共分类主干。
|
||
- `TenantQuestionReference` 作为公共题/私题消费引用。
|
||
- `PracticeSessionQuestion` 锁定题目版本,历史答题按原版本回放。
|
||
- `TenantFrontendConfig` 支持 Draft / Preview / Publish。
|
||
- `GET /api/runtime/bootstrap` 仅根据当前 Host 返回公开配置。
|
||
|
||
## 数据库边界
|
||
|
||
EF Core 负责实体、索引、外键和普通约束。以下跨表租户不变量由集中 PostgreSQL guard 管理:
|
||
|
||
- `TenantQuestionReference` 只能引用平台公共题或当前租户私题。
|
||
- `TaxonomyNode` 父节点只能属于平台主体或当前租户。
|
||
|
||
维护规则见根目录 README 的“数据库与 ORM 分工”。
|
||
|
||
## 验收
|
||
|
||
- A 租户不能读取、修改、删除或 Attach B 租户数据。
|
||
- A 可访问公共题和 A 私题,不能构造 B 私题引用。
|
||
- 公共题 V1 创建会话后发布 V2,旧会话仍使用 V1,新会话使用 V2。
|
||
- Host A 携带 Tenant B JWT 返回 403。
|
||
- Pending/未知域名和 Suspended 租户无法获取 runtime bootstrap。
|
||
- 新增租户实体漏写 filter、组合外键或租户索引时模型测试失败。
|
||
|
||
```bash
|
||
dotnet build TIKU-BACKEND.slnx --no-restore
|
||
dotnet test Tiku.UnitTests/Tiku.UnitTests.csproj --no-build
|
||
dotnet test Tiku.IntegrationTests/Tiku.IntegrationTests.csproj --no-build
|
||
dotnet ef migrations script --project Tiku.Infrastructure --startup-project Tiku.DbMigrator
|
||
git diff --check
|
||
```
|