# 第二阶段:.NET 工程底座 完成日期:2026-07-27。 本阶段按 greenfield 项目建设,不承担旧 Supabase / NestJS 数据兼容。数据库结构以 EF Core 实体、Fluent Configuration 和 Migration 为唯一权威来源;API 运行时不自动修改数据库。 ## 数据库开发与发布边界 - 开发采用 code first:修改实体或 Fluent Configuration 后生成 EF Core Migration。 - Migration 统一保存在 `Tiku.Infrastructure/Persistence/Migrations/`。 - `Tiku.DbMigrator` 是执行 Migration 的独立入口。 - `Tiku.Api` 和 `Tiku.Worker` 不调用 `Database.Migrate()`,避免多实例启动时争抢 DDL,也避免应用进程持有结构变更权限。 - 发布前生成并审查 SQL;生产环境由部署流程显式执行 DbMigrator 或审核后的 SQL。 - Development 未配置连接串时默认连接本机 `tiku` 数据库,并使用当前系统用户名,不内置密码。 - 非 Development 环境必须显式配置 `ConnectionStrings:Database` 或 `DATABASE_URL`,缺失时启动失败。 常用命令: ```bash dotnet ef migrations add \ --project Tiku.Infrastructure \ --startup-project Tiku.DbMigrator dotnet ef migrations script \ --project Tiku.Infrastructure \ --startup-project Tiku.DbMigrator dotnet run --project Tiku.DbMigrator ``` ## 真实 PostgreSQL 测试基线 `Tiku.IntegrationTests` 已移除 EF Core InMemory provider,全部集成测试连接真实 PostgreSQL: - 管理连接可通过 `TIKU_TEST_POSTGRES_ADMIN` 覆盖。 - 默认管理连接为 `Host=localhost;Database=postgres;Username=<当前系统用户>`。 - 测试进程创建一次已执行完整 Migration 的模板数据库。 - 每个 `ApiTestFactory` 从模板克隆独立 `tiku_it_*` 数据库,测试之间不共享业务数据。 - Factory 释放时终止连接并删除克隆库;进程退出时删除模板库。 - 测试只允许管理 `tiku_it_*` 命名空间,不访问或清理已有 `tiku` 业务数据库。 真实关系型测试暴露并修正了以下 InMemory 无法可靠验证的问题: - 测试夹具缺失的 Region、School、Subject、Category、Question 等外键实体。 - `Question` 与 `QuestionVersion` 当前版本引用形成的插入循环,改为事务内分阶段保存。 - `ReferralLead.FirstTrackId` 与 `ReferralTrack.LeadId` 形成的插入循环,改为事务内分阶段保存。 - PostgreSQL JSON 映射不能将未定义的 `default(JsonElement)` 生成为 SQL literal,缺省数组改为有效的 `[]`。 - 徽章发放不再伪造超长 legacy ID;通知使用新记录 ID 生成独立、稳定的去重键。 ## 配置与密钥安全 - 根配置不再提交可用于生产的 JWT signing key。 - Development 只使用明确标识的开发 JWT key;Production 拒绝该 key。 - 租户第三方凭据使用 AES-256-GCM envelope 保存。 - 每条密文使用 12-byte nonce、16-byte authentication tag。 - AAD 绑定 `tenantId`、`secretRef` 和 `keyId`,密文不能跨租户或跨引用替换。 - 主密钥必须是 Base64 编码的 32 字节值;Production 拒绝仓库内的开发测试 key。 - 运行时配置键为 `Security:TenantSecrets:KeyId` 和 `Security:TenantSecrets:MasterKey`;环境变量可使用 `TIKU_TENANT_SECRET_KEY_ID` 与 `TIKU_TENANT_SECRET_MASTER_KEY`。 - 新 schema 只保留 `encryption_key_id`、`encrypted_payload`、`encryption_nonce`、`encryption_tag`,不保留明文字段或明文回退读取路径。 本阶段的 `EncryptTenantSecretPayloads` Migration 会直接删除旧 `secret_payload` 字段。这是 greenfield 决策,不提供旧数据转换或兼容窗口。 ## 验证结果 2026-07-27 在本机 PostgreSQL 18.4、.NET SDK 10.0.301 上完成: ```text dotnet build:通过,0 warning / 0 error dotnet test:通过,265/265 UnitTests:16/16 IntegrationTests(真实 PostgreSQL):249/249 dotnet format --verify-no-changes:通过 git diff --check:通过 EF Core migration script:通过,生成 4184 行 SQL 测试数据库清理:通过,无 tiku_it_* 残留 ``` ## 第二阶段退出条件 - [x] EF Core Migration 成为 code-first schema 的唯一版本历史。 - [x] API 与 Worker 不在启动时自动执行 Migration。 - [x] 生产数据库、JWT 和租户密钥配置缺失时 fail fast。 - [x] 租户第三方凭据只保存 AES-GCM 密文 envelope。 - [x] 集成测试完全切换到隔离的真实 PostgreSQL 数据库。 - [x] 修复真实数据库暴露的外键、循环依赖、JSON 和字段长度问题。 - [x] 全量构建、测试、格式与 Migration SQL 验证通过。