# 第三阶段:租户隔离、共享题库与前端运行时 状态:已完成主线设计和实现。 ## 目标 - 普通业务代码默认只能读取和写入当前租户数据。 - 公共题库由平台主体拥有,有效租户可访问。 - 租户私题只属于本租户。 - 公共题和私题可混合组卷、答题、收藏、错题和统计。 - 租户自定义域名安全解析到统一前端运行时配置。 ## 结果 - `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 ```