feat: 完成外部服务解耦与租户级 Provider 模块化,移除 Supabase 依赖

This commit is contained in:
2026-07-27 17:56:25 +08:00
parent 70b99e6063
commit 71793a2de0
4 changed files with 227 additions and 16 deletions

View File

@@ -4,6 +4,26 @@
当前判断很明确:现在团队已经有后端开发,继续把核心认证、权限、多租户隔离、业务一致性交给 BaaS 规则会让复杂度藏在平台、SQL policy 和前端约定之间。新后端把这些东西收回应用层和数据库约束里,开发、排查、审计都会更直接。
## 当前架构定位
本项目按 greenfield 后端推进,不兼容旧 Supabase 数据库、旧 RLS、旧 Storage bucket 约定、旧 Refresh Token 或旧题单 JSON。旧 NestJS/Supabase 仓库只作为业务行为和接口清单参考,不再作为运行时依赖。
核心设计取舍:
- PostgreSQL 只作为标准 PostgreSQL 使用,不绑定 Supabase 托管能力。
- 数据结构由 EF Core entity、Fluent Configuration 和 Migration 管理,`Tiku.DbMigrator` 是执行迁移的入口。
- 多租户隔离不使用 PostgreSQL RLS通过请求租户上下文、EF Core Query Filter、写入拦截器、PostgreSQL 约束和真实集成测试共同兜底。
- 身份、短信、对象存储、支付和通知全部通过 Application 层接口表达业务意图,第三方 SDK 和密钥读取只允许出现在 Infrastructure provider 边界。
- 租户自定义域名由可信 Host 解析,不接受客户端通过 query/header 伪造切换租户。
- 公共题库由唯一平台主体拥有,订阅有效租户可访问公共题,同时租户私题只属于本租户。
阶段设计文档:
- [`docs/migration/phase-1-repository-baseline.md`](docs/migration/phase-1-repository-baseline.md)
- [`docs/migration/phase-2-engineering-foundation.md`](docs/migration/phase-2-engineering-foundation.md)
- [`docs/migration/phase-3-tenant-isolation-and-shared-question-bank.md`](docs/migration/phase-3-tenant-isolation-and-shared-question-bank.md)
- [`docs/migration/phase-4-external-provider-decoupling.md`](docs/migration/phase-4-external-provider-decoupling.md)
## 技术栈与分层
- ASP.NET Core Controller API
@@ -35,6 +55,7 @@ Tiku.IntegrationTests # API / EF 模型集成测试
- ASP.NET Authorization 负责权限策略。
- JWT + 数据库 `auth_sessions` 负责 access/refresh/session 闭环。
- `ICurrentUser` / 只读 `ITenantContext` 统一当前用户和请求租户上下文。
- Refresh Token 采用 `v1.{tenantId}.{sessionId}.{secret}` 结构,刷新和退出先解析租户再按 `TenantId + SessionId + TokenHash` 定位。
- EF Core Query Filter、写入拦截器和 PostgreSQL 组合约束共同阻断跨租户读写。
- PostgreSQL FK / unique / check / index 负责数据完整性底线。
- 审计事件表记录关键行为。
@@ -49,6 +70,7 @@ Tiku.IntegrationTests # API / EF 模型集成测试
- 跨租户引用优先使用 composite FK例如 `(tenant_id, id)`
- 业务 API 默认从当前请求上下文解析租户,不信任请求 body 里的 `tenantId`
- 租户域名、品牌、设置、角色、班级、学生运营都已经有独立模型。
- `IgnoreQueryFilters()``FromSql``ExecuteSql` 和直接 `NpgsqlCommand` 只能出现在受审计基础设施边界。
这比旧版在 API、RLS、前端之间反复拼 tenant 条件更可控。
@@ -72,13 +94,43 @@ Tiku.IntegrationTests # API / EF 模型集成测试
- Options `ValidateOnStart()` 启动校验。
- ZLinq 作为后续热路径低分配工具。
### 5. 资源存储不绑 Supabase
### 5. 公共题库和租户私库统一闭环
旧版资源层实际使用 Node `ali-oss`。新后端已经按这个方向迁移到 `AlibabaCloud.OSS.V2`
题库不再按“租户复制公共题”建模,而是拆成所有权和消费引用
- 唯一 `PlatformOwned` 平台主体拥有公共题库、公共题、公共题版本和公共分类主干。
- 有效订阅租户自动访问公共题库,不通过逐题库 grant 表做主授权。
- 租户私有题库、私有题和扩展分类只属于上传租户。
- `TenantQuestionReference` 作为租户消费公共题或本租户私题的受控引用,禁止引用其他租户私题。
- API 对外只暴露 `QuestionLocator { source, questionId }`,其中 `source` 只能是 `platform``tenant`
- `PracticeSessionQuestion` 锁定题目版本,确保公共题发布新版本后,历史答题和进行中练习仍按原版本回放。
### 6. 统一前端运行时
租户通过自定义域名访问统一托管前端,后端只信任 Host 解析结果:
- 自定义域名走 `CNAME -> 统一前端/网关`,浏览器使用同域 `/api` 调后端。
- `TenantResolutionMiddleware` 在认证之前解析租户未知、Pending、禁用域名直接 404。
- JWT tenant claim 必须和 Host 解析结果一致,否则 403。
- `GET /api/runtime/bootstrap` 根据当前 Host 返回品牌、主题、功能开关、导航和首页模块。
- 配置采用 Draft / Preview / Publish公开配置禁止任意 HTML、JavaScript、外部脚本和内部密钥。
### 7. 外部服务不绑 Supabase
新后端已经完全脱离 Supabase Auth / Storage 兼容层。业务层只依赖接口和统一租户 Provider 配置:
- Identity`IIdentityProvider`,默认自有 JWT、Session、密码、短信和微信认证。
- SMS`ISmsProvider`,只负责发送验证码或模板短信,验证码生成、哈希和频控仍在业务服务。
- Object Storage`IObjectStorageService`,默认阿里云 OSS`local_dev` 仅用于本地测试。
- Payment`IPaymentProvider`,通过统一 Provider 配置加载账户和密钥。
- Notification`INotificationProvider`,默认站内通知持久化,不让业务代码跨模块直接 new 通知实体。
- Provider 配置统一落 `TenantExternalProvider` + `TenantSecret`
- `ConfigPublic` 只保存公开字段,例如 appId、merchantId、region、endpoint、bucketAlias、templateCode。
- 密钥只通过 `SecretRef` 关联 `TenantSecret`,禁止把 secret/token/key/privateKey 写入公开配置。
资源存储方向:
- Application 只依赖 `IObjectStorageService`
- Infrastructure 收敛阿里云 OSS SDK 细节。
- 租户外部服务统一通过 `TenantExternalProvider` + `TenantSecret` 配置。
- 对象 key 默认要求租户前缀,避免资源混放。
- 上传签名前校验 MIME、大小和租户对象存储 provider。
- 下载/预览必须先过业务授权,再签发临时 URL。
@@ -105,6 +157,7 @@ Tiku.Infrastructure/Persistence/Migrations/20260727093301_InitialSchema.cs
- 版本锁定练习、答题、收藏、错题、报告、统计
- 自定义域名 DNS/TLS 生命周期和版本化前端运行时配置
- 商品、订单、支付、权益、兑换码、优惠券
- 统一外部 Provider 配置和租户密钥
- 推广、CRM、佣金
- Banner、FAQ、公告、通知、徽章、审计
- 平台账单、催收、审计告警
@@ -120,9 +173,11 @@ Tiku.Infrastructure/Persistence/Migrations/20260727093301_InitialSchema.cs
- 短信验证码登录。
- 微信网页 OAuth 登录。
- 微信小程序登录。
- 租户级身份 Provider 配置解析。
- 当前用户 `/api/me`
- 当前租户 `/api/tenants/current`
- 租户公开解析和公开配置。
- Host 解析的前端运行时配置 `/api/runtime/bootstrap`
- 统一异常响应和请求日志。
### 已迁移 API
@@ -139,6 +194,19 @@ Tiku.Infrastructure/Persistence/Migrations/20260727093301_InitialSchema.cs
- vocabulary / handbook 只读
- asset / image / app asset / video catalog 只读
- asset download / preview 授权签名
- tenant external providers / identity providers / payment providers 管理入口
- runtime bootstrap
## 新开发约束
新增业务时默认遵守以下边界:
- 新增租户实体必须实现租户 marker并通过模型测试确认 Query Filter、租户唯一索引和组合外键。
- 普通 Controller / Service 不接受可写 `tenantId`、任意 owner tenant GUID、任意 bucket 或任意 provider 细节。
- 题目写接口使用 `QuestionLocator`,答题接口使用 `sessionQuestionId`,不得恢复裸 `QuestionId` 练习写入。
- 自定义域名请求不得通过 `tenantCode``host` query 或客户端转发头切换租户。
- 第三方 SDK、密钥读取、OSS bucket 拼接、微信/支付/短信 provider 细节只允许在 Infrastructure provider 实现中出现。
- 不新增 Supabase provider、Supabase URL 拼接、Supabase Storage bucket 逻辑或 Supabase Auth 兼容层。
## 还剩多少待迁移
@@ -151,17 +219,14 @@ Tiku.Infrastructure/Persistence/Migrations/20260727093301_InitialSchema.cs
两边路径设计并非逐字兼容,不能用 `342 - 237` 推算剩余工作量。详细机械比较和后续取舍入口见 [`docs/migration/contracts/operation-inventory.csv`](docs/migration/contracts/operation-inventory.csv)。旧版里有不少平台运营、商业化自动化、审计告警、积分、督导、对账等后段能力,应按新架构重新筛选。
优先级建议
当前阶段三和阶段四已经完成租户隔离、共享题库、学习闭环基础、运行时前端配置和外部服务解耦。后续不建议继续按旧 endpoint 数量机械补齐,应按业务闭环推进
1. 运营内容只读Banner、FAQ、公告、考试日期、商品/SVIP 套餐
2. 学习闭环:练习会话、提交答案、收藏、错题、单词进度
3. 资源管理:上传签名、上传确认、导入任务查询
4. 租户后台:角色、班级、学生、域名、品牌、登录 Provider
5. 商业化:订单、支付、权益、兑换码、优惠券
6. 内容管理:题目、题集、词汇、手册、视频的后台写接口
7. 推广/CRM/佣金。
8. 平台后台:租户、账单、对账、退款、催收、审计告警。
9. Worker导入、统计、资产扫描、通知、对账、账单。
1. 高级交易运营:退款、对账、调账凭证、支付异常处理
2. 租户内容导出与 Worker 骨架:导入异步化、导出任务、资源扫描、统计聚合
3. 平台后台基础:平台总览、租户管理、平台员工、平台公共题库运营
4. 平台账单、发票、催缴、审计告警
5. AI 推荐报告:学校推荐、报告生成、导出任务
6. 零散增强:视频观看进度、租户洞察、监督规则、更细 RBAC 权限点
## 常用命令