feat: add phase five operations foundation
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
- [`docs/migration/phase-2-engineering-foundation.md`](migration/phase-2-engineering-foundation.md)
|
||||
- [`docs/migration/phase-3-tenant-isolation-and-shared-question-bank.md`](migration/phase-3-tenant-isolation-and-shared-question-bank.md)
|
||||
- [`docs/migration/phase-4-external-provider-decoupling.md`](migration/phase-4-external-provider-decoupling.md)
|
||||
- [`docs/migration/phase-5-backoffice-worker-operations.md`](migration/phase-5-backoffice-worker-operations.md)
|
||||
|
||||
迁移原则:
|
||||
|
||||
@@ -17,6 +18,7 @@
|
||||
- 不保留 Supabase 运行时依赖、Storage provider 或 Auth 兼容层。
|
||||
- 数据一致性落 PostgreSQL FK / unique / check / index 约束。
|
||||
- 多租户数据默认带 `TenantId`,跨租户引用优先使用 composite FK。
|
||||
- ORM 不能自动表达的跨表租户不变量,使用集中 PostgreSQL trigger / constraint trigger SQL helper,由 EF Core migration 调用并用真实 PostgreSQL 集成测试验证。
|
||||
- 外部身份、短信、对象存储、支付和通知都通过 Application 接口与 `TenantExternalProvider` 配置解耦。
|
||||
- JSON 字段使用 C# `JsonElement` + PostgreSQL `jsonb`,不落字符串。
|
||||
- 旧版明显是占位、临时脚本或平台自动化的部分,不直接硬搬,先重新设计边界。
|
||||
@@ -34,7 +36,9 @@
|
||||
- Scalar / OpenAPI 基础入口。
|
||||
- 统一租户外部服务配置:`TenantExternalProvider` + `TenantSecret`。
|
||||
- 身份、短信、阿里云 OSS、支付和通知 Provider 抽象,业务层不感知 Supabase Storage、OSS bucket、微信/支付/短信 SDK 或密钥读取细节。
|
||||
- Senparc 微信登录与小程序码生成边界。
|
||||
- Senparc 微信登录、小程序码和微信支付 V3 边界。
|
||||
- 后台权限、菜单、平台/租户角色与操作审计底座。
|
||||
- `Microsoft.Extensions.Hosting` Worker 与统一后台任务模型。
|
||||
|
||||
### 数据库
|
||||
|
||||
@@ -50,6 +54,7 @@
|
||||
- 推广、邀请码、归因、CRM 队列、佣金结算。
|
||||
- 运营内容、通知、徽章、审计。
|
||||
- 平台账单、催缴、审计告警、对账/退款相关模型。
|
||||
- 后台权限、菜单、角色、用户角色绑定和后台任务模型。
|
||||
- PocketBase 导入审计。
|
||||
- 自定义域名 DNS/TLS 生命周期和租户前端运行时配置。
|
||||
|
||||
|
||||
@@ -164,6 +164,26 @@
|
||||
- SessionQuestion 的版本属于对应题目。
|
||||
- 学习记录与当前租户、学生和会话一致。
|
||||
|
||||
### EF Core 与 PostgreSQL 约束分工
|
||||
|
||||
EF Core 负责实体、Fluent Configuration、Migration 生成和迁移执行入口,迁移仍然是 code-first 管理,不允许去生产库手工补结构。
|
||||
|
||||
但以下跨表、跨租户不变量不能指望 ORM 自动推导:
|
||||
|
||||
- `TenantQuestionReference` 只能引用平台主体公共题或当前租户私题,不能引用其他租户私题。
|
||||
- `TaxonomyNode` 的父节点只能属于平台主体或当前租户,不能挂到其他租户节点。
|
||||
- 需要读取 `tenants.mode` 或比较多列 owner 关系的规则。
|
||||
|
||||
原因是 PostgreSQL `CHECK` 不能跨表查询,普通 FK 只能证明目标记录存在,不能表达“目标 owner 必须是平台主体或本租户”。这类规则必须落到 PostgreSQL trigger / constraint trigger。
|
||||
|
||||
实现要求:
|
||||
|
||||
- 触发器 SQL 必须集中在基础设施层,例如 `Tiku.Infrastructure/Persistence/PostgreSqlTenantConstraintSql.cs`。
|
||||
- Migration 只调用集中 SQL helper,例如 `migrationBuilder.Sql(PostgreSqlTenantConstraintSql.CreateTenantQuestionReferenceGuard)`。
|
||||
- 不允许把触发器 SQL 零散复制到多个 migration。
|
||||
- 每个触发器必须有真实 PostgreSQL 集成测试覆盖允许路径和拒绝路径。
|
||||
- 如果将来新增类似“平台或本租户”的 owner 规则,优先补集中 SQL helper 和模型/集成测试,不要只靠 Service 手写校验。
|
||||
|
||||
## 测试矩阵
|
||||
|
||||
真实 PostgreSQL 测试创建平台主体、租户 A、租户 B 及三套内容,至少覆盖:
|
||||
|
||||
194
docs/migration/phase-5-backoffice-worker-operations.md
Normal file
194
docs/migration/phase-5-backoffice-worker-operations.md
Normal file
@@ -0,0 +1,194 @@
|
||||
# 阶段五:后台能力底座、Worker 基座与运营闭环
|
||||
|
||||
状态:已完成第一轮底座实现并通过本地验收(2026-07-28)。
|
||||
|
||||
本阶段从核心 SaaS 架构重构进入后台运营能力补齐。目标不是逐字兼容旧 NestJS,而是在当前 ASP.NET Core + EF Core + PostgreSQL 架构下重建后台权限、菜单、审计、交易运营和 Worker 基座,并参考 yudao 的后台能力清单补齐系统底座。
|
||||
|
||||
## 固定 SDK 选型
|
||||
|
||||
微信生态统一使用 Senparc:
|
||||
|
||||
- `Senparc.Weixin`
|
||||
- `Senparc.Weixin.MP`
|
||||
- `Senparc.Weixin.WxOpen`
|
||||
- `Senparc.Weixin.TenPayV3`
|
||||
|
||||
已移除并禁止:
|
||||
|
||||
- `SKIT.FlurlHttpClient.Wechat.*`
|
||||
|
||||
其他外部服务:
|
||||
|
||||
- OSS:`AlibabaCloud.OSS.V2`
|
||||
- 阿里云短信:`AlibabaCloud.SDK.Dysmsapi20170525`
|
||||
- 支付宝:`AlipaySDKNet.Standard`
|
||||
- Worker:`Microsoft.Extensions.Hosting` + `BackgroundService`
|
||||
|
||||
业务层仍然只依赖 Application 接口:
|
||||
|
||||
- `IIdentityProvider`
|
||||
- `IObjectStorageService`
|
||||
- `ISmsProvider`
|
||||
- `IPaymentProvider`
|
||||
- `INotificationProvider`
|
||||
|
||||
第三方 SDK namespace 只允许出现在 Infrastructure provider 实现中。
|
||||
|
||||
## 5A:微信 Provider 收敛
|
||||
|
||||
`WechatPayProvider` 已切换到 `Senparc.Weixin.TenPayV3`:
|
||||
|
||||
- JSAPI / H5 下单参数生成走 Senparc TenPayV3。
|
||||
- JSAPI 前端支付参数使用 Senparc 签名 helper 生成。
|
||||
- 支付 provider 继续通过 `IPaymentProvider` 暴露,不向 Application 或 API 泄漏 Senparc 类型。
|
||||
- 架构测试禁止生产代码引用 `SKIT.FlurlHttpClient.Wechat`。
|
||||
|
||||
说明:当前 `IPaymentProvider` 的回调入口抽象为 body + headers,不直接暴露 `HttpContext`。Senparc 官方推荐的 `TenPayNotifyHandler(HttpContext)` 回调验签/解密模式后续可以在 Infrastructure 的 HTTP 适配层补强,但不能把 Senparc 类型扩散到业务层。
|
||||
|
||||
## 5B:后台权限、菜单、角色与审计底座
|
||||
|
||||
新增后台基础模型:
|
||||
|
||||
- `BackendPermission`
|
||||
- `BackendMenu`
|
||||
- `TenantBackendRole`
|
||||
- `TenantBackendRolePermission`
|
||||
- `TenantBackendRoleMenu`
|
||||
- `TenantBackendUserRole`
|
||||
- `PlatformBackendRole`
|
||||
- `PlatformBackendRolePermission`
|
||||
- `PlatformBackendRoleMenu`
|
||||
- `PlatformBackendUserRole`
|
||||
|
||||
设计边界:
|
||||
|
||||
- 权限点是稳定字符串 code。
|
||||
- 菜单只控制后台 UI 展示,不作为唯一 API 鉴权来源。
|
||||
- 平台角色和租户角色分表。
|
||||
- 租户角色绑定包含 `TenantId`,平台角色绑定不带租户键。
|
||||
- 角色、权限、菜单、用户角色绑定写操作落 `AuditLog`。
|
||||
|
||||
新增接口:
|
||||
|
||||
- `GET /api/backoffice/tenant/bootstrap`
|
||||
- `POST /api/backoffice/tenant/roles`
|
||||
- `PUT /api/backoffice/tenant/roles/{roleId}/bindings`
|
||||
- `PUT /api/backoffice/tenant/users/{userId}/roles`
|
||||
- `GET /api/backoffice/platform/bootstrap`
|
||||
- `POST /api/backoffice/platform/roles`
|
||||
- `PUT /api/backoffice/platform/roles/{roleId}/bindings`
|
||||
- `PUT /api/backoffice/platform/users/{userId}/roles`
|
||||
|
||||
`TenantAdminDirect` 继续作为过渡入口;新增后台能力使用 `backoffice` 模块命名。
|
||||
|
||||
## 5E:交易运营底座
|
||||
|
||||
在现有 commerce 模型上补齐租户后台运营服务:
|
||||
|
||||
- 退款申请。
|
||||
- 退款状态流转。
|
||||
- 退款事件记录。
|
||||
- 对账批次创建。
|
||||
- 对账 issue 查询与状态流转。
|
||||
- 退款、对账写操作落审计。
|
||||
|
||||
退款状态流转由服务控制,不能任意跳转。支付、回调、退款后续仍统一走 `IPaymentProvider`,初期不默认开启真实自动退款。
|
||||
|
||||
新增租户交易运营接口:
|
||||
|
||||
- `GET /api/tenant-commerce/refunds`
|
||||
- `POST /api/tenant-commerce/refunds`
|
||||
- `POST /api/tenant-commerce/refunds/status`
|
||||
- `GET /api/tenant-commerce/refunds/{refundRequestId}/events`
|
||||
- `GET /api/tenant-commerce/reconciliation/batches`
|
||||
- `POST /api/tenant-commerce/reconciliation/batches`
|
||||
- `GET /api/tenant-commerce/reconciliation/issues`
|
||||
- `POST /api/tenant-commerce/reconciliation/issues/status`
|
||||
|
||||
## 5F:Worker 与后台任务基座
|
||||
|
||||
新增统一任务模型 `BackgroundJob`:
|
||||
|
||||
- `JobType`
|
||||
- `TenantId`
|
||||
- `Payload`
|
||||
- `Status`
|
||||
- `RetryCount`
|
||||
- `MaxRetries`
|
||||
- `LockedBy`
|
||||
- `LockExpiresAt`
|
||||
- `RunAfter`
|
||||
- `StartedAt`
|
||||
- `CompletedAt`
|
||||
- `LastError`
|
||||
- `OutputAssetId`
|
||||
- `Result`
|
||||
|
||||
Worker 基于 `Microsoft.Extensions.Hosting` + `BackgroundService`,暂不引入 Hangfire。后续需要复杂 cron 时再评估 `Quartz.Extensions.Hosting`。
|
||||
|
||||
当前任务处理器建立了幂等租户 Scope 入口和状态机骨架,覆盖以下 job type:
|
||||
|
||||
- `content_export`
|
||||
- `content_import`
|
||||
- `asset_security_scan`
|
||||
- `statistics_aggregation`
|
||||
- `commerce_reconciliation`
|
||||
- `tenant_domain_recheck`
|
||||
|
||||
新增租户后台任务接口:
|
||||
|
||||
- `GET /api/backoffice/tenant/jobs`
|
||||
- `POST /api/backoffice/tenant/jobs`
|
||||
|
||||
API 只负责创建任务和查询任务;Worker 必须通过 `ITenantExecutionScope` 初始化执行上下文。
|
||||
|
||||
## 阿里云短信 Provider
|
||||
|
||||
`ISmsProvider` 默认接入租户 Provider 配置:
|
||||
|
||||
- 未配置短信 provider 时,本地/测试降级为 `noop`。
|
||||
- 配置 `aliyun_sms` 时,从 `TenantExternalProvider(capability=sms)` 读取公开配置。
|
||||
- `signName`、`templateCode`、`endpoint`、`regionId` 等公开字段放 `ConfigPublic`。
|
||||
- `accessKeyId`、`accessKeySecret` 只允许通过 `TenantSecret` 解密获得。
|
||||
- 发送失败会记录 `SmsVerificationStatus.Failed`,不会留下可验证验证码。
|
||||
|
||||
## 数据库约束补强
|
||||
|
||||
阶段五重建 `InitialSchema` 时同步补齐阶段三遗漏的两个 PostgreSQL 硬约束:
|
||||
|
||||
- `TenantQuestionReference` 只能引用平台主体公共题或当前租户私题。
|
||||
- `TaxonomyNode` 父节点只能属于平台主体或当前租户。
|
||||
|
||||
这类跨表租户不变量不能靠 EF Core FK / check constraint 自动表达,统一放在 `PostgreSqlTenantConstraintSql`,由 migration 调用,并由真实 PostgreSQL 集成测试覆盖。
|
||||
|
||||
## 架构测试
|
||||
|
||||
新增或强化禁止项:
|
||||
|
||||
- 生产代码不得引用 `SKIT.FlurlHttpClient.Wechat`。
|
||||
- 业务层不得直接引用阿里云 OSS、阿里云短信、Senparc、支付宝 SDK namespace。
|
||||
- 生产代码不得回流 Supabase provider、Supabase Storage 或旧专用 provider 表模型。
|
||||
- 普通业务目录继续禁止 `IgnoreQueryFilters`、`FromSql`、`ExecuteSql` 和直接 `NpgsqlCommand`。
|
||||
|
||||
## 验收结果
|
||||
|
||||
本阶段本地验收:
|
||||
|
||||
```bash
|
||||
dotnet restore TIKU-BACKEND.slnx
|
||||
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
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
- build:0 警告,0 错误。
|
||||
- unit tests:18/18 通过。
|
||||
- integration tests:264/264 通过。
|
||||
- migration script:生成成功。
|
||||
- 空 PostgreSQL 通过 `Tiku.DbMigrator` 建库成功。
|
||||
- PostgreSQL 验证两个租户约束 trigger 已创建。
|
||||
|
||||
Reference in New Issue
Block a user