# TIKU Backend 题库 SaaS 后端,当前目标是从旧 PocketBase / Supabase 方案迁移到可控的 ASP.NET Core + PostgreSQL 架构。 这个仓库目前处在新后端初始化和安全底座建设阶段:Domain 实体、EF Core Fluent 配置、PostgreSQL 初始建库 migration、模型约束测试已经建立;API 层已经开始接入 ASP.NET Core 认证授权、本地登录、数据库 Session 和当前租户上下文。 ## 为什么换掉 Supabase 旧方案的问题不只是“用了 Supabase”,而是安全、权限、业务约束分散在太多地方: - 应用代码、Supabase RLS、SQL policy、Edge/脚本、前端约定之间边界不清。 - 多租户业务复杂后,RLS policy 会越来越难审查,调试成本高。 - 团队现在已经有后端开发,继续把核心权限和业务一致性绑在 BaaS 规则上,反而增加维护难度。 - 复杂导入、对账、退款、CRM、资产安全扫描、平台账单等后台任务,更适合由明确的 Worker + 应用层权限 + 数据库约束来承载。 - 新系统需要长期演进,直接掌控 PostgreSQL schema、迁移、索引、FK、事务边界,会比依赖 Supabase 平台约束更可控。 新的方向不是“数据库裸奔”,而是把职责重新分层: - ASP.NET Core 负责认证、授权、租户上下文、业务流程、审计记录。 - PostgreSQL 负责数据完整性、外键、唯一约束、检查约束、索引、JSONB/数组/ltree/citext 等原生能力。 - Worker 负责导入、对账、退款、通知、资产扫描、统计等后台任务。 - 测试负责持续验证 EF 模型和数据库约束没有退化。 ## 技术栈 - .NET / ASP.NET Core - Entity Framework Core - PostgreSQL - Npgsql - xUnit 项目分层: ```text Tiku.Api # HTTP API 入口 Tiku.Application # 应用服务、用例编排 Tiku.Domain # 领域实体、枚举、基础类型 Tiku.Infrastructure # EF Core、PostgreSQL 持久化配置、migration Tiku.DbMigrator # 数据库迁移启动项目 Tiku.Worker # 后台任务 Tiku.UnitTests # 单元测试 Tiku.IntegrationTests # EF 模型/持久化约束测试 ``` ## 数据库策略 当前数据库以 PostgreSQL 为核心能力,而不是把 PostgreSQL 当成普通 KV 存储: - JSON 字段统一使用 C# `JsonElement`,映射到 PostgreSQL `jsonb`。 - 树形内容路径使用 `ltree`。 - 大小写不敏感编码/域名/兑换码等使用 `citext`。 - PostgreSQL array 使用 `text[]` / `uuid[]`,不把数组塞成字符串。 - 钱相关字段用整型 cents 或明确 decimal precision,避免浮点误差。 - 多租户表默认有 `TenantId`。 - 租户内跨表引用优先使用 composite FK,例如 `(tenant_id, id)`。 - 关键业务唯一性落数据库唯一索引或过滤唯一索引。 - 用 check constraint 保护金额、数量、状态范围等底线。 当前 migration 已压成一个 greenfield baseline: ```text Tiku.Infrastructure/Persistence/Migrations/20260725220742_InitialSchema.cs ``` 因为新库还没正式上线,保留一堆开发过程 migration 意义不大。现在单个初始 migration 更适合作为新后端的起点。 ## 新安全策略 这次迁移的核心安全思路是:不照搬 Supabase RLS,而是在应用层建立明确、可测试、可审计的安全边界,同时用 PostgreSQL 约束守住数据完整性底线。 ### 已落地 - ASP.NET Core Controller API 已接入统一认证/授权管线。 - 本地认证不依赖 Supabase Auth: - 手机号 + 密码登录 - 短信验证码登录 - 微信网页 OAuth 登录 - 微信小程序 `wx.login` 登录 - JWT access token - 数据库 `auth_sessions` refresh/session - `auth_login_events` 登录事件 - JWT 不只验签,也会校验对应 `auth_sessions` 是否仍有效;登出或撤销 session 后,旧 access token 会被拒绝。 - 当前请求上下文已经拆成应用层抽象: - `ICurrentUser` - `ICurrentTenant` - 第一批授权策略已经建立: - 已登录用户 - 当前租户成员 - 租户管理员 - 第一批认证/租户接口已经建立: - `GET /api/tenant/resolve` - `GET /api/tenant/current-public` - `POST /api/auth/login/password` - `POST /api/auth/login/sms` - `POST /api/auth/oauth/wechat` - `POST /api/auth/oauth/wechat-miniapp` - `POST /api/auth/refresh` - `POST /api/auth/logout` - `GET /api/me` - `GET /api/tenants/current` - `GET /api/health` - 多租户数据表普遍包含 `TenantId`。 - 大量租户内关系使用 composite FK,避免只靠应用代码约定租户一致性。 - 不迁移 Supabase RLS、`app_private` schema、生产保护 policy。 - 敏感配置不原样落明文字段,例如 CRM / 支付 / webhook 只保留 `secret_ref` 或公开配置。 - PB 导入有专门审计表: - `pb_import_runs` - `pb_raw_records` - `pb_import_issues` - 关键业务流保留审计/事件表,例如: - `audit_logs` - `auth_login_events` - `payment_events` - `commerce_refund_events` - `commerce_reconciliation_issue_events` - `content_asset_access_events` - `content_asset_security_scan_events` - `platform_audit_alerts` - 模型测试覆盖: - 表数量和表名 - JSONB 映射 - `ltree` 映射 - 关键唯一索引 - decimal precision - 租户关系 composite FK - PB raw record 可追踪主键 - API / Auth 测试覆盖: - 租户按 `tenantCode` / `host` 解析 - 公开品牌、主题、功能开关和 public config 返回 - health smoke test - 未登录返回 401 - 无权限返回 403 - 密码登录成功/失败 - 短信验证码登录成功/失败 - 微信登录 upsert 用户、身份和租户成员 - 登录后访问当前用户和当前租户 - 登出后旧 access token / refresh token 被拒绝 ### 计划落地 后续 API / Worker 实现时,需要继续把下面这些策略固化成代码和测试: - 业务 API 不直接信任请求 body 的 `tenantId`;登录阶段可选择租户,登录后业务请求以 JWT/session 中解析出的当前租户为准。 - API 层继续基于用户、租户成员、角色模板、权限 JSON 扩展授权策略。 - 平台管理员、租户管理员、教师/运营、学生等权限边界继续细化到业务模块。 - Repository / Query 层默认带租户过滤,平台级查询必须显式声明。 - 所有跨租户资源访问必须走应用服务校验,不允许 controller 直查。 - 审计日志覆盖高风险操作: - 登录、认证失败、短信验证 - 订单/支付/退款/对账调整 - 题库导入/导出/公库采纳 - 资产下载签名和安全扫描 - 租户配置、域名、品牌、支付账号变更 - 平台账单、催收、告警处理 - Secret 管理独立化,只在应用层通过安全服务解析 `secret_ref`。 - 短信发送目前先完成验证码表、哈希校验和限流模型,真实供应商通过接口适配接入。 - 后台 Worker 使用最小权限的应用服务,不直接绕过业务规则写库。 - 对账、导入、批处理任务要求幂等键和可重跑设计。 - 生产环境连接串、密钥、对象存储凭据不进入仓库。 ## 常用命令 ```bash dotnet build /home/xiong/Games/Anii/TIKU-BACKEND/TIKU-BACKEND.slnx dotnet test /home/xiong/Games/Anii/TIKU-BACKEND/TIKU-BACKEND.slnx dotnet format /home/xiong/Games/Anii/TIKU-BACKEND/TIKU-BACKEND.slnx --verify-no-changes git -C /home/xiong/Games/Anii/TIKU-BACKEND diff --check ``` 生成数据库 SQL: ```bash dotnet ef migrations script \ --project Tiku.Infrastructure \ --startup-project Tiku.DbMigrator ``` ## API 迁移模板 后续从旧 Nest/Supabase 迁移 API 时,优先按当前认证和租户接口的模板推进: 1. DTO 放到 `Tiku.Api/Contracts`,只包含 HTTP 输入输出形状、校验属性和 OpenAPI 描述。 2. Controller 保持轻量,只做路由、授权、DTO 到 Application request 的映射。 3. 业务流程放到 `Tiku.Application`,EF/外部服务实现放到 `Tiku.Infrastructure`。 4. 已登录业务接口默认从 `ICurrentTenant` / `ICurrentUser` 取上下文,不直接信任 body 里的 `tenantId`。 5. 公开接口只返回 branding、feature flags、public config 等可暴露字段,不泄露 secret/refund/payment/internal metadata。 6. 每迁一个小闭环就补集成测试和 Scalar/OpenAPI 描述,测试通过后单独提交。 建议下一批迁移顺序: ```text Catalog 基础只读 API -> 地区 / 模块 / 学校 / 专业 / 科目 / 分类 Content Navigation 只读 API -> 内容入口 / 内容树 / 题集 / 练习蓝图 Question Bank 只读 API -> 题库列表 / 题目详情 / 题目版本 ``` ## 当前状态 - 数据库模型迁移已完成到初始 schema。 - migration 已整理为单个初始建库 migration。 - API 安全底座已建立:JWT、Session、本地登录、当前用户、当前租户、基础授权策略。 - 租户公开入口已建立:tenant resolve、public config、health。 - 当前模型测试、认证服务测试、API 认证/租户闭环测试通过。 - 下一步重点是继续把题库、内容、导入、订单等业务 API 接入这套安全轨道,而不是重新散写权限判断。