docs: rewrite documentation from current implementation
This commit is contained in:
28
docs/README.md
Normal file
28
docs/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# TIKU Backend 文档
|
||||
|
||||
这里仅记录当前代码已经实现的架构、运行方式和维护约束。接口细节以 Development 环境的 OpenAPI/Scalar 为准,数据库结构以 EF Core Migration 和模型快照为准。
|
||||
|
||||
## 阅读入口
|
||||
|
||||
| 文档 | 内容 | 适合谁 |
|
||||
| --- | --- | --- |
|
||||
| [本地开发与运行](quickstart.md) | PostgreSQL 初始化、启动 API/Worker、验证命令、常见问题 | 新开发者 |
|
||||
| [系统架构与业务边界](architecture/overview.md) | 项目依赖、运行时组件、当前业务模块、请求与消息链路 | 开发与评审人员 |
|
||||
| [认证、授权与租户隔离](architecture/security-and-tenancy.md) | 登录、Session、JWT、Cookie/CSRF、Realm、RBAC、Capability、DataScope、租户隔离 | API 与安全开发者 |
|
||||
| [配置与后台任务](operations.md) | 环境配置、Production 启动门禁、Redis/RabbitMQ、Worker、健康检查 | 开发与运维人员 |
|
||||
|
||||
## 权威来源
|
||||
|
||||
- API 契约:`Tiku.Api/Controllers`、请求/响应 DTO 和运行时 OpenAPI。
|
||||
- 数据模型:`Tiku.Domain`、`Tiku.Infrastructure/Persistence/Configurations` 和 EF Core Migration。
|
||||
- 认证授权:`Tiku.Api/Configuration`、`Tiku.Api/Middleware`、`Tiku.Application/Security`、`Tiku.Infrastructure/Security`。
|
||||
- 后台任务:`Tiku.Worker`、`Tiku.Application/Jobs`、`Tiku.Infrastructure/Jobs` 和 `Tiku.Infrastructure/Messaging`。
|
||||
- 外部服务:Application 接口与 Infrastructure 实现;运行时租户配置存储在 `TenantExternalProvider` 和 `TenantSecret`。
|
||||
|
||||
## 维护规则
|
||||
|
||||
1. 文档只描述当前可从代码或自动化测试确认的行为。
|
||||
2. 新增租户实体时,同时验证 Query Filter、租户唯一索引、组合外键和写入拦截器。
|
||||
3. 数据库结构变更必须生成 EF Core Migration,并检查 migration script 和 pending model changes。
|
||||
4. 不在文档中保存连接密码、JWT 私钥、证书密码、Provider 密钥或平台管理员临时密码。
|
||||
5. 不再维护阶段路线图、旧后端接口对比、评审快照或开发过程记录。
|
||||
@@ -1,32 +0,0 @@
|
||||
# ADR 0001:以 .NET + PostgreSQL 作为唯一目标后端
|
||||
|
||||
- 状态:已接受
|
||||
- 日期:2026-07-27
|
||||
|
||||
## 背景
|
||||
|
||||
旧 NestJS 后端与 Supabase 数据库、认证、存储和规则耦合较深。新的 ASP.NET Core 后端已经具备 EF Core 模型、迁移链和业务 API。前后端尚未正式开发,仍可直接收敛技术路线。
|
||||
|
||||
## 决策
|
||||
|
||||
1. 本仓库是题库 SaaS 唯一继续演进的后端。
|
||||
2. 旧 NestJS 仓库冻结为业务行为、接口契约和数据迁移参考。
|
||||
3. EF Core Migration 是普通数据库结构变更的权威历史;生产通过 `Tiku.DbMigrator` 显式执行迁移,API 不自动同步结构。
|
||||
4. 应用只依赖标准 PostgreSQL 和 Npgsql,不以 Supabase 作为运行时依赖或托管目标。
|
||||
5. 身份、对象存储、短信、支付、通知和 AI 通过 Application 接口隔离供应商,并通过 `TenantExternalProvider` + `TenantSecret` 配置。
|
||||
6. Yudao 只参考 RBAC、租户套餐、审计和后台产品设计,不作为运行时依赖。
|
||||
7. 新功能按业务闭环迁移和验收,不以 Controller 或 endpoint 数量作为完成标准。
|
||||
|
||||
## 边界
|
||||
|
||||
- 认证和授权由 ASP.NET Core、JWT、数据库 Session 和应用权限策略负责。
|
||||
- 多租户隔离由请求租户上下文、EF 查询/写入防护、数据库约束和真实 PostgreSQL 测试共同负责。
|
||||
- 普通表、列、索引、外键和普通约束由 EF Core Migration 管理。
|
||||
- EF 不能表达的跨表租户不变量使用集中 PostgreSQL guard,由 migration 调用并接受测试。
|
||||
- 旧 OpenAPI 用于发现能力缺口,不要求新接口逐字兼容。
|
||||
|
||||
## 结果
|
||||
|
||||
- 不建设长期双后端或双写链路。
|
||||
- 不保留 Supabase 作为认证、存储或数据库运行时目标。
|
||||
- 更换 PostgreSQL 托管商或外部服务供应商不要求重写业务代码。
|
||||
@@ -1,98 +0,0 @@
|
||||
# 认证与授权待补强清单
|
||||
|
||||
> 2026-07-29 实施状态:可信代理启动校验、外部登录成员生命周期、Redis 跨实例频控与故障关闭、固定目录数据库 Capability、事务化 System Scope 审计、MassTransit EF Bus/Consumer Outbox 与即时 BackgroundJob Consumer、浏览器 Cookie/CSRF 主链路及 endpoint manifest 已落地。本地 RabbitMQ 4.3.4 已验证停机期间事务提交、outbox 积压及重启补发;生产网关 ACL 和生产 Broker 演练仍属于部署验收项。
|
||||
|
||||
当前生效规则见 [认证、授权与 Host 安全策略](authentication-authorization-security.md)。本文只记录尚需补强的安全事项,不重复描述已实现体系。
|
||||
|
||||
## P0:可信代理与 Host fail-closed
|
||||
|
||||
- Production 必须配置正式 `PlatformHosts` 和 `TrustedProxyAddresses`。
|
||||
- Production 不允许只保留 `localhost` / `127.0.0.1` 作为平台 Host。
|
||||
- 未受信来源伪造 `X-Forwarded-Host` 不能改变 realm 或 tenant context。
|
||||
- 受信代理只接受一跳转发,网关必须覆盖客户端伪造的 Forwarded Headers。
|
||||
- API 公网入口必须只能由受信网关访问。
|
||||
|
||||
实现说明:应用已强制 `ForwardLimit=1`,Production 缺少正式 Host、显式 `AllowedHosts` 或可信代理地址时启动失败;公网 ACL 和网关覆盖转发头由部署层落实。
|
||||
|
||||
验收:
|
||||
|
||||
- Host A + Tenant B token 返回 403。
|
||||
- 未知 Host 的非豁免路径返回 404。
|
||||
- Production 缺少可信代理或正式平台 Host 时启动失败。
|
||||
|
||||
## P0:Disabled / Invited 成员生命周期
|
||||
|
||||
- 外部身份登录不得静默恢复 Disabled membership。
|
||||
- Invited membership 不得被微信登录静默激活。
|
||||
- 首次外部登录是否允许创建学生成员,必须由租户自注册策略控制。
|
||||
- 成员恢复只能由管理员显式操作并写审计。
|
||||
|
||||
实现说明:`TenantAuthPolicy.AllowExternalStudentSelfRegistration` 控制首次外部登录;Disabled/Invited 不会被登录激活,管理员成员变更会同步撤销 Session、写审计并发布生命周期事件。
|
||||
|
||||
验收:
|
||||
|
||||
- Disabled 成员旧 access/refresh 立即失效。
|
||||
- Disabled 成员不能通过微信 Web 或小程序登录恢复。
|
||||
- 关闭自注册时,首次外部登录被拒绝。
|
||||
|
||||
## P1:SaaS Capability 授权
|
||||
|
||||
RBAC 只回答“用户是否有操作权限”;Capability 负责“租户是否购买、启用并可使用该能力”。
|
||||
|
||||
默认组合:
|
||||
|
||||
```text
|
||||
Tenant Active
|
||||
+ Subscription 有效
|
||||
+ Module / Feature 可用
|
||||
+ Operation Permission
|
||||
+ DataScope / Resource Scope
|
||||
```
|
||||
|
||||
实现说明:`SaasFeature`、显式 `PermissionModule.RequiredFeatureCode`、不可变套餐版本、`TenantFeatureOverride` 与 `IFeatureAccessService` 已进入数据库授权链路。未知 Feature、未购买模块和失效订阅均 fail-closed;角色绑定保留历史权限,但鉴权和菜单只使用当前有效权限。
|
||||
|
||||
验收:
|
||||
|
||||
- 有 permission 但套餐不含模块,返回 403。
|
||||
- 套餐包含模块但没有 permission,返回 403。
|
||||
- PastDue / Cancelled / Expired 不能创建新的受限资源。
|
||||
- 修改套餐后,旧 access token 不需要等待过期即可失去能力。
|
||||
|
||||
## P1:接口最小权限与 DataScope 审计
|
||||
|
||||
- 建立 endpoint authorization manifest:method、route、realm、module、permission、DataScope、audit action。
|
||||
- 后台写接口不得只使用 `[Authorize]`。
|
||||
- tenant/platform 权限不得串用。
|
||||
- `[AllowAnonymous]` 只能出现在白名单路由。
|
||||
- All-only 资源必须显式声明。
|
||||
- 新增 Controller action 未进入 manifest 时测试失败。
|
||||
|
||||
实现说明:`AuthorizationManifestTests` 对全部 Controller HTTP Action 的 method、route、匿名标记和 policy 生成稳定摘要;MVC convention 同时为全部非匿名 Controller endpoint 生成 realm、module、permission、operation、All-only 与 audit action 运行时元数据,变更会触发测试失败并要求安全评审。
|
||||
|
||||
验收:
|
||||
|
||||
- 列表、详情、创建、更新、删除、批量、导出和 Worker job 使用一致 DataScope。
|
||||
- 租户 A 管理员不能读取或操作租户 B 数据。
|
||||
|
||||
## P1:System Scope 审计
|
||||
|
||||
- `ITenantExecutionScope` 创建 System Scope 时必须记录 caller、reason、target tenant 和 request/job id。
|
||||
- 平台操作、Worker、迁移验证和受审计公共题库服务才允许使用 System Scope。
|
||||
- 跨租户写操作必须落 `AuditLog`。
|
||||
|
||||
实现说明:`SystemScopeRequest` 强制 caller、reason、target tenant 和 correlation ID;没有租户目标时必须显式声明 `IsGlobal`,且 Worker/公共题库不能创建全局 scope。旧参数签名已移除。成功路径的 entered 审计、跨租户业务写入和 completed 审计处于同一 PostgreSQL 事务,异常路径回滚业务并持久化 entered/failed 审计。
|
||||
|
||||
验收:
|
||||
|
||||
- 未声明 reason 的 System Scope 创建失败。
|
||||
- Worker scope 不串租户。
|
||||
- 高风险平台操作都有审计记录。
|
||||
|
||||
## P2:客户端与协议规范
|
||||
|
||||
- 浏览器 token 存储策略在正式前固定:纯 Bearer、本域 BFF 或 cookie 方案只能选一种主链路。
|
||||
- Access token 继续短期有效,不把角色和权限写入 JWT。
|
||||
- 登录审计和错误响应避免泄露手机号、openId、邮箱完整值。
|
||||
- 出现第三方生态登录、开放 API 或多客户端授权需求时,再评估 OpenIddict / OIDC,不继续扩展私有协议。
|
||||
|
||||
实现说明:浏览器使用 `/api/browser-auth` + Secure/HttpOnly Cookie + Origin/CSRF 校验;原 `/api/auth` Bearer 契约继续供小程序、原生和服务调用。
|
||||
@@ -1,146 +0,0 @@
|
||||
# 认证、授权与 Host 安全策略
|
||||
|
||||
本文是当前生效安全规范。新增接口或修改登录流程时,以本文档和自动化测试为准;前端菜单、JWT 字符串和历史角色约定不能代替 API 授权。
|
||||
|
||||
## 授权域
|
||||
|
||||
- `tenant`:租户业务域,必须绑定 Active 租户和 Active `TenantMembership`。
|
||||
- `platform`:平台运营域,只能从配置的 Platform Host 进入,不绑定租户。
|
||||
|
||||
核心规则:
|
||||
|
||||
- Host、JWT scope、tenant claim、数据库 Session 和请求租户上下文必须一致。
|
||||
- JWT 只证明已认证会话,不承载可直接授权的角色或权限。
|
||||
- 后台权限每次从数据库角色绑定解析;菜单只控制 UI 展示。
|
||||
- 租户后台能力同时要求 Active tenant、有效订阅、模块权益和 operation permission;Capability 仍以 PostgreSQL 为准。
|
||||
- 数据权限必须进入 SQL;无法可靠映射 owner、region 或 class 的资源采用 All-only fail-closed。
|
||||
- 用户、成员、租户、后台角色、权限、SecurityStamp 或 Session 任一失效,旧 token 不能继续取得能力。
|
||||
- Controller 默认要求认证;公开接口必须显式 `[AllowAnonymous]`。
|
||||
|
||||
```text
|
||||
Client
|
||||
-> Trusted proxy
|
||||
-> TenantResolutionMiddleware
|
||||
-> JWT + AuthSession validation
|
||||
-> Authorization handler + current access context
|
||||
-> EF tenant filter + DataScope SQL + PostgreSQL constraints
|
||||
```
|
||||
|
||||
## 账号与 Session
|
||||
|
||||
- 账号由 ASP.NET Core Identity 管理。
|
||||
- 密码最少 8 位且必须同时包含字母和数字;PBKDF2 迭代次数 210,000。
|
||||
- 连续 5 次密码失败后锁定 15 分钟。
|
||||
- 普通租户用户使用手机号和密码或手机号短信验证码登录;平台管理员当前使用账号和密码登录。
|
||||
- 微信等外部身份只保存 provider subject、openid、unionid,不保存 `session_key` 或原始 secret。
|
||||
- Data Protection key 持久化到 PostgreSQL;非 Development 环境必须提供带私钥的 PKCS#12 证书保护 key ring。
|
||||
|
||||
Access token:
|
||||
|
||||
- RSA SHA-256 签名,Header 必须包含 `kid`。
|
||||
- 固定 15 分钟。
|
||||
- 包含 `sub`、`sid`、`jti`、`iat`、`iss`、`aud`、`exp`、`scope`。
|
||||
- tenant token 必须包含 `tid`;platform token 禁止包含 `tid`。
|
||||
- 不包含 role 或 permission claim。
|
||||
|
||||
Refresh token:
|
||||
|
||||
```text
|
||||
v2.{t|p}.{tenantId|-}.{sessionId}.{64-byte-random-secret}
|
||||
```
|
||||
|
||||
- 数据库只保存完整 refresh token 的 SHA-256 hash。
|
||||
- 刷新在事务内轮换 Session。
|
||||
- 并发刷新只允许一个成功。
|
||||
- 已轮换 token 被复用时视为重放,撤销整个 token family 并写审计。
|
||||
- logout 撤销当前 refresh token family;logout-all 更新 SecurityStamp 并撤销用户全部 Session。
|
||||
|
||||
浏览器入口使用 `/api/browser-auth/*`:access/refresh token 仅写入 Secure、HttpOnly Cookie,响应体不返回 token;不安全方法必须通过同源 Origin 与双提交 CSRF 校验。`/api/auth/*` Bearer 契约继续供小程序、原生客户端和服务调用。
|
||||
|
||||
## Host 与 tenant 解析
|
||||
|
||||
Host 是认证上下文,不是普通参数。`TenantResolutionMiddleware` 在 Authentication 前执行。
|
||||
|
||||
| 请求入口 | 租户上下文 | 允许 realm | 默认结果 |
|
||||
| --- | --- | --- | --- |
|
||||
| Platform Host | 无租户 | platform;白名单入口可用 tenantCode 引导 tenant 登录 | 继续 |
|
||||
| Active 租户 Host | Host 绑定租户 | tenant | 继续 |
|
||||
| 租户 Host + 其他 tenantCode/header | 冲突 | 无 | 403 |
|
||||
| Platform Host + platform realm + tenantCode | 非法混合 | 无 | 400 |
|
||||
| 未知 Host | 无 | 无 | 非豁免路径 404 |
|
||||
| Pending/禁用域名 | 无 | 无 | 404 |
|
||||
|
||||
规则:
|
||||
|
||||
- 自定义域名不接受 `tenantCode`、`host` query 或客户端转发头覆盖。
|
||||
- tenant JWT 的 `tid` 必须与 Host 解析租户一致。
|
||||
- platform JWT 不能访问租户 Host。
|
||||
- 平台 Host 上的租户登录引导才允许受控使用 `tenantCode`。
|
||||
- 只接受可信代理写入的 Forwarded Headers;直连客户端伪造无效。
|
||||
|
||||
## RBAC、菜单与 DataScope
|
||||
|
||||
租户后台与平台后台角色分离:
|
||||
|
||||
- 租户角色、权限、菜单、用户角色绑定都带租户上下文。
|
||||
- 平台角色不带租户键,不能自动读取租户业务数据。
|
||||
- 菜单只决定 UI bootstrap 展示,不作为 API 授权依据。
|
||||
- 后台 API 必须声明明确 permission;高风险写操作必须记录审计。
|
||||
- UI bootstrap 只返回“有效 permission 推导菜单”与有效 Capability 的交集;租户不能绑定当前无权使用的模块权限。
|
||||
- Trial/Active 且在有效期内可写;PastDue/Cancelled/Expired 仅允许已有权益模块的历史读取。
|
||||
|
||||
DataScope:
|
||||
|
||||
- `All`:当前租户内该模块全部资源。
|
||||
- `Restricted`:按 region/class/owner 等资源关系过滤。
|
||||
- `Self`:只允许当前用户关联资源。
|
||||
- 无法可靠表达资源关系的模块只允许 `All`,不能退化为仅按 tenant 查询。
|
||||
|
||||
## 短信验证码
|
||||
|
||||
- 验证码生成、哈希、频控、过期和校验由自有业务服务负责。
|
||||
- `ISmsProvider` 只负责发送。
|
||||
- 发送失败必须记录失败状态,不能留下可验证验证码。
|
||||
- 登录、绑定、找回密码等场景使用独立 purpose 和频控键。
|
||||
- Redis Lua 同时执行跨实例 IP、账号、租户、手机号和 purpose 窗口计数;key 只使用 GUID 或不可逆哈希。
|
||||
- Redis 不可用时密码尝试、短信发送和短信校验失败关闭;普通授权请求仍直接查询 PostgreSQL。
|
||||
|
||||
## 可靠安全事件
|
||||
|
||||
- `Tiku.Contracts` 只包含版本化 DTO,不引用 EF、HTTP 或 Provider SDK。
|
||||
- API 使用 MassTransit EF Bus Outbox,Worker consumer 使用 EF inbox/outbox;业务变更、审计和消息由同一 DbContext 提交。
|
||||
- RabbitMQ 消息只负责非权威失效版本、菜单刷新和下游通知;成员、租户、Session 或套餐失效不等待 consumer。
|
||||
- 即时 `BackgroundJob` 由 `BackgroundJobRequestedV1` Consumer 执行;延时任务和失败后的定时重试继续由数据库调度器处理,同一即时任务不会同时进入两种消费路径。业务 handler 必须使用受审计 System Scope 提供的 scoped `DbContext`。
|
||||
- System Scope 只能通过完整 `SystemScopeRequest` 创建;成功路径将 entered 审计、跨租户业务写入和 completed 审计放入同一 PostgreSQL 事务。
|
||||
|
||||
## 审计与错误
|
||||
|
||||
必须落审计:
|
||||
|
||||
- 登录、刷新重放、logout-all、强制改密;
|
||||
- 角色、权限、成员状态、租户状态、Provider 配置、支付运营动作;
|
||||
- System Scope 和跨租户平台操作。
|
||||
|
||||
错误响应:
|
||||
|
||||
- 401:未认证或 token/session 无效。
|
||||
- 403:已认证但 realm、tenant、permission、DataScope 或套餐能力不满足。
|
||||
- 404:未知 Host、不可见资源或需要隐藏存在性的资源。
|
||||
- 响应不得泄露完整手机号、openId、邮箱、密钥、支付账号或内部 provider payload。
|
||||
|
||||
## 生产配置清单
|
||||
|
||||
- 正式 `PlatformHosts`。
|
||||
- 可信代理地址和网络 ACL。
|
||||
- 非通配 `AllowedHosts`。
|
||||
- JWT issuer、audience、当前 `KeyId`、RSA 私钥和旧公钥集合。
|
||||
- Data Protection 证书。
|
||||
- CORS 明确 Origin。
|
||||
- Redis 7.2+ 连接串;Production 缺失时拒绝启动。
|
||||
- RabbitMQ 4.x Host、virtual host 与凭据;Production 缺失时拒绝启动。
|
||||
- 默认镜像不依赖 `x-delayed-message` 插件;Consumer 使用有限即时重试,延时业务重试落回 PostgreSQL `RunAfter`。
|
||||
- 公网只暴露覆盖 Forwarded Headers 的可信网关,API ACL 只允许该网关访问。
|
||||
- Secret encryption key。
|
||||
- 短信、对象存储、支付、通知和 AI provider 只通过租户 Provider 配置读取密钥。
|
||||
|
||||
待补强事项见 [认证与授权待补强清单](authentication-authorization-hardening-plan.md)。
|
||||
@@ -1,9 +0,0 @@
|
||||
# Endpoint authorization manifest
|
||||
|
||||
Controller 授权面由 `AuthorizationManifestTests` 按 HTTP method、route、controller/action、匿名标记和 policy 生成稳定摘要。
|
||||
`EndpointAuthorizationMetadataConvention` 为全部非匿名 Controller endpoint 生成 realm、module、permission、CapabilityOperation、All-only DataScope 与 audit action 元数据,测试从运行时 `EndpointDataSource` 验证覆盖。新增、删除或修改 Action 时摘要测试必须失败,评审者确认元数据后才能更新 count/hash。
|
||||
|
||||
该清单是防止接口绕过评审的变更门禁;实际授权事实仍来自 PostgreSQL permission、Capability 和 DataScope,不能用摘要替代运行时校验。
|
||||
|
||||
- Action 数量:332
|
||||
- SHA-256:`a80fe477ba3021625e17c9fc639e5109bab678178f8024a51c3c732bf5a46d3f`
|
||||
124
docs/architecture/overview.md
Normal file
124
docs/architecture/overview.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 系统架构与业务边界
|
||||
|
||||
本文描述当前仓库的实际代码结构和运行时职责。接口路径、DTO 和响应模型以运行时 OpenAPI 为准。
|
||||
|
||||
## 分层与依赖
|
||||
|
||||
```text
|
||||
+------------------+
|
||||
| Tiku.Contracts |
|
||||
+--------^---------+
|
||||
|
|
||||
+-----------+ +---------------+---------------+
|
||||
| Tiku.Api | | Tiku.Worker / Tiku.DbMigrator |
|
||||
+-----+-----+ +---------------+---------------+
|
||||
| |
|
||||
+-------------+-------------+
|
||||
v
|
||||
+---------------------+
|
||||
| Tiku.Infrastructure |
|
||||
+----------+----------+
|
||||
v
|
||||
+---------------------+
|
||||
| Tiku.Application |
|
||||
+----------+----------+
|
||||
v
|
||||
+---------------------+
|
||||
| Tiku.Domain |
|
||||
+---------------------+
|
||||
```
|
||||
|
||||
- `Tiku.Domain` 保存领域实体、枚举和基础类型。除 Identity stores 抽象外,不依赖持久化或 Provider SDK。
|
||||
- `Tiku.Application` 定义用例契约、Provider 接口、安全上下文和业务目录,依赖 Domain。
|
||||
- `Tiku.Infrastructure` 实现 EF Core、PostgreSQL、Identity、外部 Provider、消息和后台任务,依赖 Application、Domain 与 Contracts。
|
||||
- `Tiku.Contracts` 保存 API 与 Worker 使用的版本化消息 DTO,不引用 HTTP、EF Core 或 Provider SDK。
|
||||
- `Tiku.Api`、`Tiku.Worker` 和 `Tiku.DbMigrator` 是独立运行入口。
|
||||
|
||||
## 运行时组件
|
||||
|
||||
### API
|
||||
|
||||
`Tiku.Api/Program.cs` 只负责组合服务、构建应用和启用请求管线。管线的关键顺序是:
|
||||
|
||||
```text
|
||||
Forwarded Headers
|
||||
-> HTTPS / 压缩 / 静态文件
|
||||
-> Routing / CORS
|
||||
-> Host 租户解析
|
||||
-> 浏览器 CSRF
|
||||
-> JWT 认证 / 认证专用限流 / 全局限流
|
||||
-> 当前用户上下文 / 授权
|
||||
-> SaaS Feature 校验
|
||||
-> Output Cache
|
||||
-> Controllers
|
||||
```
|
||||
|
||||
OpenAPI 和 Scalar 只在 Development 映射。平台管理静态文件由 `Tiku.Api/wwwroot` 同源托管,默认入口是 `/platform-admin/`。
|
||||
|
||||
### DbMigrator
|
||||
|
||||
`Tiku.DbMigrator` 是唯一迁移入口,执行顺序为:
|
||||
|
||||
1. 解析 `ConnectionStrings:Database` 或 `DATABASE_URL`。
|
||||
2. 进入带审计原因的 System Scope。
|
||||
3. 执行 `Database.MigrateAsync()`。
|
||||
4. seed 内置 SaaS Feature、PermissionModule、BackendPermission 和 BackendMenu 目录。
|
||||
5. Development 全新数据库自动 seed 平台管理员;非 Development 仅在显式传入 `--bootstrap-platform-admin` 时创建管理员。
|
||||
|
||||
API 和 Worker 都不自动迁移数据库。
|
||||
|
||||
### Worker
|
||||
|
||||
`Tiku.Worker` 当前注册四个独立 Hosted Service:
|
||||
|
||||
| Worker | 周期 | 当前职责 |
|
||||
| --- | --- | --- |
|
||||
| `TenantDomainWorker` | `TenantDomains:PollSeconds`,限制为 10~3600 秒 | 校验自定义域名 CNAME/TXT,调用网关 TLS 接口并失效租户缓存 |
|
||||
| `SaasSubscriptionWorker` | 60 秒 | 处理到期、宽限期等 SaaS 订阅生命周期 |
|
||||
| `FeatureUsageWorker` | `FeatureUsageReconciliation:IntervalMinutes`,限制为 1~1440 分钟 | 按真实业务数据校准租户 Feature 用量 |
|
||||
| `BackgroundJobsWorker` | 2 秒,4 个分区 | 租约处理 PostgreSQL 中的延时/待执行后台任务;未配置 RabbitMQ 时也处理即时任务 |
|
||||
|
||||
后台任务当前支持 `content_import`、`content_export`、`statistics_aggregation`、`commerce_reconciliation` 和 `tenant_domain_recheck`。`asset_security_scan` 会明确失败,直到配置实际扫描 Provider;不能把它描述为已接通扫描服务。
|
||||
|
||||
配置 RabbitMQ 后,即时安全事件和后台任务请求使用 MassTransit;API 使用 EF Bus Outbox,Worker Consumer 使用 EF inbox/outbox。延时任务仍由 PostgreSQL `RunAfter` 和租约 Worker 处理。
|
||||
|
||||
## 数据与持久化
|
||||
|
||||
- 数据库使用标准 PostgreSQL,普通 schema 由 EF Core entity、Fluent Configuration 和 Migration 管理。
|
||||
- 当前模型启用 `citext`、`ltree` 和 `pg_trgm` 扩展,并统一映射为 `snake_case`。
|
||||
- Data Protection key ring 由 API 持久化到 PostgreSQL;非 Development 必须使用 X509 证书保护。
|
||||
- MassTransit inbox/outbox 表与业务表处于同一 `TikuDbContext`。
|
||||
- PostgreSQL 不启用 RLS;租户隔离由应用和数据库多层共同保证,详见[认证、授权与租户隔离](security-and-tenancy.md)。
|
||||
|
||||
## 当前业务模块
|
||||
|
||||
### 平台端
|
||||
|
||||
- 租户、Owner、域名、状态、员工、角色和审计告警。
|
||||
- 平台公共题库、分类节点、题目、导入和资源上传。
|
||||
- SaaS Feature、额度定义、套餐版本、报价、订单、支付、退款、订阅、发票和催缴。
|
||||
- 平台级 CRM、短信渠道/模板和支付应用配置。
|
||||
|
||||
### 租户端
|
||||
|
||||
- 员工、角色、权限、菜单、DataScope 和租户设置。
|
||||
- 私有题库、公共题库引用、内容目录、词汇、手册、视频、分数线、站点内容、导入导出和资源。
|
||||
- 学生、班级、CRM 跟进、监管规则、报表和审计。
|
||||
- 学生商城、订单、支付、退款、优惠券、积分、推广和分佣。
|
||||
- 租户 SaaS 目录、账务、订阅、用量、发票和 onboarding 状态。
|
||||
- 身份、短信、对象存储、支付、通知和 AI 的租户 Provider 配置边界。
|
||||
|
||||
### 学生端
|
||||
|
||||
- Host 对应的运行时品牌、导航、Feature 和登录方式 bootstrap。
|
||||
- 账号登录、个人资料、通知、签到和积分。
|
||||
- 题目目录、练习会话、作答、收藏、错题、视频播放和进度。
|
||||
- 学生商品、订单、支付、优惠券、权益和推广关系。
|
||||
|
||||
是否存在某个具体操作,应以 Controller 和 OpenAPI 为准,不能仅凭本节的模块名称推断。
|
||||
|
||||
## 外部服务边界
|
||||
|
||||
Application 通过接口表达身份、短信、对象存储、支付、通知、域名和 AI 能力;Infrastructure 当前包含自托管身份、阿里云短信/OSS、微信、支付宝、站内通知、DNS JSON 查询和 HTTP 网关实现。
|
||||
|
||||
租户级 Provider 元数据和密钥分别存入 `TenantExternalProvider` 与 `TenantSecret`。密钥由 32 字节 master key 加密,API 不应把明文、`SecretRef` 或 Provider 内部 payload 返回给客户端。
|
||||
@@ -1,230 +0,0 @@
|
||||
# SaaS 题库产品边界与后续接口路线
|
||||
|
||||
本文档定义平台端、租户端、学生端的目标边界,以及下一阶段接口开发顺序。当前 ASP.NET Core 后端是实现基线;旧 NestJS 和 `tiki-web` 只用于核对业务行为,yudao 只用于参考套餐、商城、支付和后台运营的模块划分。
|
||||
|
||||
## 当前判断
|
||||
|
||||
现有后端已经具备继续开发的基础:Host 租户解析、强租户隔离、共享与私有题库、RBAC、Provider 解耦、租户前端运行时配置、学生练习闭环、交易基础和 Worker 基座均已落地。
|
||||
|
||||
第九阶段已经完成 SaaS 产品与交付闭环。当前主要缺口是:
|
||||
|
||||
- 教师发布作业、考试、批阅和查看班级结果的教学闭环。
|
||||
- Provider 自助配置、公共题库运营和高流量查询读模型仍需完善。
|
||||
|
||||
## 三端边界
|
||||
|
||||
### 平台端
|
||||
|
||||
平台端是 SaaS 控制面,负责:
|
||||
|
||||
- 租户、租户 Owner、状态、域名和生命周期。
|
||||
- SaaS 业务模块、套餐、附加包、价格和额度。
|
||||
- 租户订阅、SaaS 订单、支付、退款、账单、发票、催缴和用量。
|
||||
- 平台员工、平台角色、平台权限、审计和告警。
|
||||
- 公共题库、公共分类、题目版本、发布和反馈质量运营。
|
||||
- 平台自身的收款 Provider,不使用租户配置的学生商城支付账号。
|
||||
|
||||
### 租户端
|
||||
|
||||
租户端是机构控制面,负责:
|
||||
|
||||
- 员工、自定义角色、权限、班级、学生和数据范围。
|
||||
- 私有题库、公共题库消费、分类扩展、组卷、导入和导出。
|
||||
- 作业、考试、每日一练、批阅和教学报告。
|
||||
- 品牌、主题、导航、首页模块和自定义域名。
|
||||
- 身份、SMS、对象存储、学生商城支付、通知和 AI Provider。
|
||||
- 学生商品、会员、优惠券、激活码、积分、CRM、推广和分佣。
|
||||
- 本租户 SaaS 订阅、账单、用量、续费和升级。
|
||||
|
||||
### 学生端
|
||||
|
||||
学生端是租户域名下的数据面,负责:
|
||||
|
||||
- 根据 Host 获取租户品牌、功能、导航和允许的登录方式。
|
||||
- 登录、绑定、个人资料和通知。
|
||||
- 题库、练习、考试、作业、错题、收藏和学习报告。
|
||||
- 词汇、知识手册、视频、分数线等可选内容模块。
|
||||
- 租户自己的学生商城、订单、支付、优惠券、积分和权益。
|
||||
|
||||
## 套餐能力与权限分层
|
||||
|
||||
不能用一套“模块”同时表达套餐、权限和菜单。目标模型固定为:
|
||||
|
||||
| 概念 | 用途 |
|
||||
| --- | --- |
|
||||
| `SaaSFeature` | 平台可销售的业务能力 |
|
||||
| `SaasOfferingVersionFeature` | 不可变套餐版本包含哪些业务能力 |
|
||||
| `SaasOfferingVersionLimit` | 套餐版本的员工、学生、题目、存储、导出和 AI 额度 |
|
||||
| `PermissionModule` | 后台权限页面的业务分组 |
|
||||
| `BackendPermission` | `view/create/update/import/export/approve/retry` 等操作权限 |
|
||||
| `BackendMenu` | 根据有效权限生成的前端导航,不作为鉴权依据 |
|
||||
|
||||
建议的可售卖能力包括:
|
||||
|
||||
- `question_bank.private`
|
||||
- `learning.practice`
|
||||
- `learning.assignment`
|
||||
- `learning.exam`
|
||||
- `content.vocabulary`
|
||||
- `content.handbook`
|
||||
- `content.video`
|
||||
- `content.scoreline`
|
||||
- `marketing.site_content`
|
||||
- `student.management`
|
||||
- `commerce.student_store`
|
||||
- `crm.followup`
|
||||
- `growth.referral_commission`
|
||||
- `ai.teacher_assistant`
|
||||
|
||||
身份安全、角色管理、账务中心和续费入口属于核心能力。即使套餐过期,也不能阻止租户查看账单、配置管理员或完成续费。
|
||||
|
||||
每次受保护的业务请求必须同时满足:
|
||||
|
||||
```text
|
||||
租户有效
|
||||
+ 订阅状态允许当前读写操作
|
||||
+ 套餐或附加包包含业务能力
|
||||
+ 未超过对应额度
|
||||
+ 当前角色具有操作权限
|
||||
+ DataScope 允许访问目标数据
|
||||
```
|
||||
|
||||
## 双交易域
|
||||
|
||||
平台 SaaS 商城和租户学生商城必须是两个独立边界。
|
||||
|
||||
### PlatformBilling
|
||||
|
||||
平台向租户收费,包含:
|
||||
|
||||
- SaaS 套餐、附加包和报价。
|
||||
- SaaS 订单、支付、退款、订阅、账单和发票。
|
||||
- 平台收款 Provider 和平台支付回调。
|
||||
- 租户用量、超额计费、额度预警和催缴。
|
||||
|
||||
### TenantCommerce
|
||||
|
||||
租户向学生收费,包含:
|
||||
|
||||
- SVIP、课程资料和其他学生商品。
|
||||
- 学生订单、支付、退款、优惠券、激活码和权益。
|
||||
- 当前租户配置的支付 Provider 和回调。
|
||||
|
||||
两类订单、支付账号、回调地址、审计和对账不得共用业务表或服务。
|
||||
|
||||
## 已完成的 SaaS 商城与交付接口
|
||||
|
||||
### 平台 SaaS 商城
|
||||
|
||||
- 平台模块、额度定义、基础套餐、附加包和不可变版本统一在 `/api/platform-admin/saas/**`。
|
||||
- 租户目录、报价、下单、支付、订阅变更、续费、取消、用量和发票统一在 `/api/tenant-billing/**`。
|
||||
- 人工、微信和支付宝平台收款使用平台主体 Provider,订单和回调与学生商城分离。
|
||||
- `/api/tenant-onboarding/status` 汇总 Owner、订阅、域名、登录方式、Provider 和前端发布状态。
|
||||
|
||||
租户自助账务接口建议统一在 `/api/tenant-billing/**`:
|
||||
|
||||
```text
|
||||
GET /api/tenant-billing/catalog
|
||||
POST /api/tenant-billing/quotes
|
||||
POST /api/tenant-billing/orders
|
||||
POST /api/tenant-billing/payments
|
||||
GET /api/tenant-billing/orders/{orderNo}
|
||||
GET /api/tenant-billing/subscription
|
||||
POST /api/tenant-billing/subscription/change
|
||||
POST /api/tenant-billing/subscription/renew
|
||||
POST /api/tenant-billing/subscription/cancel
|
||||
GET /api/tenant-billing/usage
|
||||
GET /api/tenant-billing/invoices
|
||||
```
|
||||
|
||||
### Provider 自助管理
|
||||
|
||||
统一使用 `TenantExternalProvider + TenantSecret`,补齐:
|
||||
|
||||
```text
|
||||
GET /api/tenant-admin/providers
|
||||
PUT /api/tenant-admin/providers
|
||||
POST /api/tenant-admin/providers/test
|
||||
POST /api/tenant-admin/providers/activate
|
||||
POST /api/tenant-admin/providers/disable
|
||||
PUT /api/tenant-admin/providers/secrets
|
||||
POST /api/tenant-admin/providers/secrets/rotate
|
||||
```
|
||||
|
||||
运行时 bootstrap 需要增加脱敏的登录方式配置,不能返回 SecretRef、密钥或第三方内部配置。
|
||||
|
||||
### 教师教学闭环
|
||||
|
||||
- 作业、考试、每日一练的创建和发布。
|
||||
- 发布目标:班级、学生组、指定学生。
|
||||
- 开始时间、截止时间、限时、补交和自动交卷规则。
|
||||
- 学生答题草稿、断点续答和最终提交。
|
||||
- 客观题自动批改,主观题教师批阅、复核和评语。
|
||||
- 完成率、成绩分布、薄弱知识点和学生明细。
|
||||
- 试卷、成绩、每日一练和战报导出。
|
||||
|
||||
现有 `PracticeBlueprint` 和 `PracticeSession` 可以作为题目装配及作答底座,但不能代替教师发布对象和班级任务状态。
|
||||
|
||||
### 平台公共题库运营
|
||||
|
||||
- 公共题库和公共分类主干管理。
|
||||
- 题目草稿、审核、发布、撤回和版本对比。
|
||||
- 重复题检测、反馈汇总和人工复核。
|
||||
- 使用量、错误率、反馈率和版本采用情况。
|
||||
- 已发布旧版本禁止物理删除。
|
||||
|
||||
### 查询性能和读模型
|
||||
|
||||
- 普通列表采用游标分页和稳定排序,禁止默认返回大集合。
|
||||
- 题目、院校和知识点搜索优先使用 PostgreSQL trigram/全文索引。
|
||||
- 首页、排行榜和运营看板使用聚合表或异步投影。
|
||||
- 公共目录和 runtime bootstrap 使用 Redis 缓存并主动失效。
|
||||
- 导入、导出、统计、资源扫描和对账进入 Worker。
|
||||
- 使用 OpenTelemetry 观测慢查询、接口耗时、缓存命中和 Worker 延迟。
|
||||
|
||||
## 实施顺序
|
||||
|
||||
### 9A~9C:已完成
|
||||
|
||||
- `SaasFeature`、`PermissionModule`、`BackendPermission`、`BackendMenu` 已分层。
|
||||
- `SaasOfferingVersion` 发布后由 Application 和 PostgreSQL guard 双重禁止修改。
|
||||
- `IFeatureAccessService` 统一处理租户、订阅、Feature、覆盖、额度与权限过滤。
|
||||
- PlatformBilling 与 TenantCommerce 使用独立订单、支付、回调和 Provider 配置。
|
||||
- 平台创建租户及 Owner 后,租户可完成购买、开通和 onboarding。
|
||||
|
||||
### 9D:教师教学与考试
|
||||
|
||||
- 作业、考试、班级发布、批阅和教学报告。
|
||||
- 智能组卷、每日一练、PDF 命题和战报持久化。
|
||||
- 导出任务通过 Worker 和对象存储交付。
|
||||
|
||||
### 9E:学生端与性能治理
|
||||
|
||||
- runtime 登录选项、手机号绑定和找回密码。
|
||||
- 作业/考试中心、断点续答和报告。
|
||||
- 根据产品决定是否迁移备考时间线和择校功能。
|
||||
- 完成分页、索引、缓存、聚合投影和性能基线测试。
|
||||
|
||||
### 9F:AI 独立阶段
|
||||
|
||||
- 面向租户教师的基础对话和后续 Function Call。
|
||||
- AI 题目反馈审核,只输出建议和人工复核标记。
|
||||
- Semantic Kernel 仅存在于 Infrastructure。
|
||||
- 租户 API Key 保存到 `TenantSecret`。
|
||||
- AI 调用量、成本和额度进入 SaaS 计量体系。
|
||||
|
||||
## 验收原则
|
||||
|
||||
- 套餐未包含的功能不能分配权限、不能显示菜单、不能调用 API、不能由 Worker 绕过执行。
|
||||
- 平台角色、租户角色和学生身份不能跨 realm 使用。
|
||||
- 租户 A 不能读取或修改租户 B 的配置、学生、题库、订单和 Provider。
|
||||
- 平台 SaaS 支付与租户学生支付使用不同配置、订单域和回调链路。
|
||||
- 套餐过期后业务写入受限,但账务、续费、安全和历史数据仍可访问。
|
||||
- 高风险操作、支付状态变化、订阅变化和 Provider 变化都有审计记录。
|
||||
- 关键查询在接近生产的数据量下验证执行计划、分页稳定性和响应时间。
|
||||
|
||||
## 参考边界
|
||||
|
||||
- 旧 NestJS:核对已有接口语义、状态机和异常行为,不要求保留旧 URL。
|
||||
- `tiki-web`:参考已实际使用的刷题、词汇、手册、商城、营销、教研和运营功能,不复制 PocketBase 查询方式。
|
||||
- yudao:参考租户套餐、商城订单、支付、退款、权限和审计的模块拆分,不照搬菜单 ID 套餐模型或 Java 运行时。
|
||||
124
docs/architecture/security-and-tenancy.md
Normal file
124
docs/architecture/security-and-tenancy.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# 认证、授权与租户隔离
|
||||
|
||||
本文说明当前请求实际经过的安全边界。新增接口、实体或后台任务时,必须保持这些边界闭合。
|
||||
|
||||
## 认证入口
|
||||
|
||||
API 支持两组认证接口:
|
||||
|
||||
- `/api/auth/**` 返回 access token 与 refresh token,适合 Bearer 客户端。
|
||||
- `/api/browser-auth/**` 把 token 写入 HttpOnly Cookie,适合同源浏览器客户端。
|
||||
|
||||
当前登录方式:
|
||||
|
||||
- 平台账号:账号/密码。
|
||||
- 租户账号:手机号/密码、手机号/短信验证码。
|
||||
- 租户可配置微信网页授权和微信小程序授权。
|
||||
|
||||
主要流程包括短信发送、密码登录、短信登录、微信登录、refresh、logout、logout-all 和首次登录强制改密。具体请求与响应字段以 Scalar 为准。
|
||||
|
||||
密码至少 8 位,并必须同时包含字母和数字。连续 5 次失败触发 15 分钟 Identity lockout。短信验证码由本服务生成和哈希,发送 Provider 只负责投递;验证码校验最多允许 5 次尝试。
|
||||
|
||||
## JWT 与 Session
|
||||
|
||||
- access token 使用 RSA SHA-256 签名,默认有效期 15 分钟。
|
||||
- refresh token 默认有效期 30 天,服务端只保存哈希。
|
||||
- JWT 必须包含用户、Session、`jti`、签发时间和 `realm`;租户 realm 还必须包含租户 ID。
|
||||
- 每次 JWT 认证都会核对数据库 Session、用户/成员状态、安全版本和租户上下文,不把 JWT 声明当作永久授权事实。
|
||||
- refresh token 轮换并检测重放;logout 撤销当前 Session,logout-all 撤销用户全部 Session。
|
||||
- 平台 token 只能在平台 Host 使用;租户 token 必须与 Host 或允许路径上的 tenant code 解析结果一致。上下文冲突返回 401,不允许静默切换租户。
|
||||
|
||||
Production 必须显式配置 JWT `KeyId`、私钥和验证公钥集合,不能使用 Development 临时密钥。
|
||||
|
||||
## 浏览器 Cookie 与 CSRF
|
||||
|
||||
Browser Auth 使用 access、refresh 和 CSRF Cookie:
|
||||
|
||||
- access/refresh Cookie 为 HttpOnly。
|
||||
- Bearer handler 只会在同源浏览器请求中回退读取 access Cookie;显式 `Authorization` header 优先。
|
||||
- 使用 Cookie 的非安全方法必须通过 `BrowserCsrfMiddleware` 的 Origin/Referer 与 CSRF token 校验。
|
||||
- 跨源浏览器使用必须同时正确配置 `Cors` 和 `BrowserAuth:AllowedOrigins`;允许凭据时不能使用通配 Origin。
|
||||
|
||||
非浏览器客户端应使用 Bearer token,不应复制浏览器 Cookie 流程。
|
||||
|
||||
## Realm、Permission、Feature 与 DataScope
|
||||
|
||||
每个受保护操作可能同时经过四层判断:
|
||||
|
||||
```text
|
||||
Realm(platform / tenant)
|
||||
+ BackendPermission(操作权限)
|
||||
+ SaaSFeature(套餐能力)
|
||||
+ DataScope(资源范围)
|
||||
```
|
||||
|
||||
- Realm 防止平台身份、租户员工和学生身份跨授权域复用。
|
||||
- `BackendPermission` 控制 `view/manage/read/write/operate` 等操作。
|
||||
- `SaaSFeature` 是固定代码目录,当前包括后台基础、私有题库、练习、作业、考试、词汇、手册、视频、分数线、站点内容、学生管理、学生商城、CRM、推广分佣和教师 AI。
|
||||
- 菜单由有效 Permission 与 Feature 共同推导,只用于 UI bootstrap,不是 API 授权依据。
|
||||
- DataScope 支持 `All`、`Restricted` 和 `Self`。无法提供可靠资源 predicate 时返回空查询,不能退化为“当前租户全部数据”。
|
||||
- 套餐状态、Feature override 和额度使用量来自 PostgreSQL;Redis 只用于失效通知和缓存,不能成为授权真相。
|
||||
|
||||
Controller 默认受 Fallback Policy 保护,匿名接口必须显式标记 `[AllowAnonymous]`。`EndpointAuthorizationMetadataConvention` 为非匿名 Controller endpoint 补充 realm、module、permission、Feature 操作、All-only DataScope 和审计元数据,集成测试从运行时 `EndpointDataSource` 验证覆盖。
|
||||
|
||||
## Host 与租户上下文
|
||||
|
||||
`TenantResolutionMiddleware` 在认证前解析租户:
|
||||
|
||||
1. 对平台 Host,不默认建立租户上下文。
|
||||
2. 对非平台 Host,按启用的租户域名查找租户;未匹配且不属于豁免路径时返回 404。
|
||||
3. 只有 `TenantCodePathPrefixes` 明确允许的路径,才能在平台 Host 使用 `x-tenant-code` 或 `tenantCode` 解析租户。
|
||||
4. JWT tenant ID 与已解析租户必须一致,否则认证失败。
|
||||
|
||||
Development 默认平台 Host 是 `localhost` 和 `127.0.0.1`。Production 启动校验要求:
|
||||
|
||||
- 至少一个非 loopback 的正式平台 Host;
|
||||
- 非通配 `AllowedHosts`;
|
||||
- 至少一个合法的 `TrustedProxyAddresses`;
|
||||
- 仅信任一跳且来源位于可信代理列表的 `X-Forwarded-For/Host/Proto`。
|
||||
|
||||
客户端不得通过任意 header、query 或转发头绕过以上路径和可信代理限制。
|
||||
|
||||
## 数据库租户隔离
|
||||
|
||||
当前 PostgreSQL 连接角色不依赖 RLS。租户隔离由以下机制共同完成:
|
||||
|
||||
### 查询
|
||||
|
||||
`TikuDbContext` 自动为所有包含 `TenantId` 的实体应用 Query Filter。普通请求只有在租户上下文已解析且 ID 匹配时可见;System Scope 才能绕过。
|
||||
|
||||
模型启动校验会拒绝:
|
||||
|
||||
- 含 `TenantId` 但未实现 `ITenantOwned` 的实体;
|
||||
- 缺少租户 Query Filter 的实体;
|
||||
- 未包含 `TenantId` 且未显式声明全局唯一的 unique index;
|
||||
- 租户实体之间未使用租户限定 principal key 的外键。
|
||||
|
||||
### 写入
|
||||
|
||||
`TenantIsolationSaveChangesInterceptor` 检查新增、修改和删除实体的租户所有权,防止普通请求写入其他租户或伪造 `TenantId`。Controller 与 Service 不应接受可任意填写的租户 ID、owner tenant ID、bucket 或 Secret 引用。
|
||||
|
||||
### 数据库约束
|
||||
|
||||
能用 FK、unique 和 check 表达的规则优先使用 EF 配置。当前集中 PostgreSQL guard 额外保证:
|
||||
|
||||
- `TenantQuestionReference` 只能指向平台公共题或当前租户私题,且 source 必须匹配所有者类型。
|
||||
- `TaxonomyNode` 的父节点只能属于平台主体或当前租户。
|
||||
- 已发布 SaaS 套餐版本及其 Feature/额度清单不可修改,并校验订阅与订单快照的一致性。
|
||||
|
||||
这些 guard 由 Migration helper 统一安装和移除,不允许在多份 Migration 中复制 SQL。
|
||||
|
||||
## System Scope 与可靠事件
|
||||
|
||||
跨租户 Worker、迁移、seed 和平台级后台操作必须通过 `ITenantContextInitializer.InitializeSystem` 或受审计的 `ITenantExecutionScope` 进入 System Scope,并提供明确原因。业务代码不得直接关闭 Query Filter。
|
||||
|
||||
配置 RabbitMQ 时:
|
||||
|
||||
- API 使用 EF Bus Outbox,把业务写入、审计和消息放在同一数据库事务边界。
|
||||
- Worker Consumer 使用 EF inbox/outbox 和有限即时重试。
|
||||
- Session、成员、租户和套餐状态始终从 PostgreSQL 重新校验,不等待消息消费后才失效。
|
||||
- 延时/定时重试使用 PostgreSQL `RunAfter`,不依赖 RabbitMQ delayed-message 插件。
|
||||
|
||||
## 安全配置门禁
|
||||
|
||||
Production 还会在启动时验证 Redis、RabbitMQ、Data Protection 证书、租户 Secret master key、短信 pepper、CORS 和外部服务配置。完整配置入口见[配置与后台任务](../operations.md)。
|
||||
@@ -46,7 +46,7 @@
|
||||
<circle class="badge" cx="96" cy="211" r="18"/>
|
||||
<text class="badge-text" x="96" y="211">P</text>
|
||||
<text class="node-title" x="124" y="207">平台管理端</text>
|
||||
<text class="node-text" x="84" y="239">/platform-admin 静态原型</text>
|
||||
<text class="node-text" x="84" y="239">/platform-admin 静态管理端</text>
|
||||
<text class="node-tiny" x="84" y="258">平台账号 · SaaS 运营</text>
|
||||
|
||||
<rect class="node-blue" x="66" y="307" width="178" height="92" rx="6"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
@@ -1,111 +0,0 @@
|
||||
# 迁移路线与剩余范围
|
||||
|
||||
本文档是旧 PocketBase / Supabase / NestJS 后端迁移到 ASP.NET Core + PostgreSQL 后端的状态入口。
|
||||
|
||||
## 固定原则
|
||||
|
||||
- 新前端按新 REST API 对接;旧 URL 默认不兼容。
|
||||
- 旧 NestJS 只作为行为清单和验收参考。
|
||||
- 不保留 Supabase 运行时依赖、Auth/Storage provider 或 RLS 模型。
|
||||
- 数据一致性优先落 PostgreSQL FK / unique / check / index;跨表租户不变量用集中 PostgreSQL guard。
|
||||
- 外部身份、短信、对象存储、支付、通知和 AI 都通过 Application 接口与 `TenantExternalProvider` 配置解耦。
|
||||
- 新功能按业务闭环验收,不按 endpoint 数量验收。
|
||||
|
||||
## 已完成主线
|
||||
|
||||
- 仓库转正:本仓库是唯一目标后端,见 [ADR 0001](adr/0001-authoritative-dotnet-backend.md)。
|
||||
- 工程底座:ASP.NET Core、EF Core、Npgsql、PostgreSQL、Serilog、RateLimiter、Options 校验。
|
||||
- 数据库:greenfield `InitialSchema`,真实 PostgreSQL 迁移和集成测试。
|
||||
- 租户隔离:Host 解析、`ITenantContext`、EF Query Filter、SaveChanges 拦截器、组合外键、PostgreSQL guard。
|
||||
- 共享题库:平台公共题库、租户私题、`TenantQuestionReference`、版本锁定练习。
|
||||
- 前端运行时:自定义域名、DNS/TLS 生命周期、`GET /api/runtime/bootstrap`。
|
||||
- 外部服务解耦:`TenantExternalProvider` + `TenantSecret`,身份、短信、OSS、支付、通知 provider 边界。
|
||||
- 后台底座:平台/租户 RBAC、菜单、审计、平台后台、租户后台、交易运营、Worker 任务模型。
|
||||
- 学生体验:视频搜索/播放/进度、题目解析视频、签到、积分流水、内容导入异步化。
|
||||
- AI 基础:已引入 SK 包到 Infrastructure,已固定 provider-neutral 对话存储方向;业务功能仍待实现。
|
||||
- SaaS 商城:可售卖 Feature、不可变套餐版本、独立 PlatformBilling、租户自助账务、业务额度、订阅周期 Worker 和 onboarding 已落地。
|
||||
|
||||
阶段归档:
|
||||
|
||||
- [第一阶段:仓库转正基线](migration/phase-1-repository-baseline.md)
|
||||
- [第二阶段:.NET 工程底座](migration/phase-2-engineering-foundation.md)
|
||||
- [第三阶段:强租户隔离、共享题库与租户前端运行时](migration/phase-3-tenant-isolation-and-shared-question-bank.md)
|
||||
- [第四阶段:外部服务解耦](migration/phase-4-external-provider-decoupling.md)
|
||||
- [第五阶段:后台能力与 Worker 基座](migration/phase-5-backoffice-worker-operations.md)
|
||||
- [第七阶段:学生端体验与内容消费闭环](migration/phase-7-student-experience-and-content-consumption.md)
|
||||
- [第八阶段:AI 底座与教师端对话](migration/phase-8-ai-foundation.md)
|
||||
- [第九阶段:SaaS 模块商城与租户交付闭环](migration/phase-9-saas-marketplace-and-onboarding.md)
|
||||
- [API 契约基线](migration/contracts/README.md)
|
||||
|
||||
后续产品化开发统一按 [SaaS 题库产品边界与后续接口路线](architecture/saas-product-and-api-roadmap.md) 执行。该文档定义三端边界、平台 SaaS 商城与租户学生商城的双交易域、套餐能力与 RBAC 分层,以及阶段 9A~9F。
|
||||
|
||||
## 剩余范围
|
||||
|
||||
### 1. AI 教师端对话与反馈审核
|
||||
|
||||
- 租户教师后台基础对话。
|
||||
- function calling 只允许调用受审计的后端业务函数。
|
||||
- AI 审核题目反馈只生成建议、风险等级和人工复核标记,不直接改业务状态。
|
||||
- 租户 AI Provider 配置、API Key 托管、调用审计和成本记录。
|
||||
- 后续再做推荐报告、导出、RAG 和多模型路由。
|
||||
|
||||
### 2. 内容导出与导入增强
|
||||
|
||||
- 内容导出任务创建、查询和下载。
|
||||
- 题库、题目、学生数据导出到对象存储。
|
||||
- 导入 preview/result/issue 更细化。
|
||||
- 大文件导入进度、失败行回放和重试。
|
||||
|
||||
### 3. Worker 实处理器补强
|
||||
|
||||
- `content_export` 完整导出。
|
||||
- `asset_security_scan` 接真实扫描 provider。
|
||||
- `statistics_aggregation` 增量聚合。
|
||||
- `commerce_reconciliation` 接真实 provider bill downloader。
|
||||
- `tenant_domain_recheck` 周期调度和告警联动。
|
||||
|
||||
### 4. 教师教学闭环
|
||||
|
||||
- 作业、考试、班级发布、批阅和教学报告。
|
||||
- 学生断点续答、自动交卷、主观题复核和成绩分析。
|
||||
- 教学导出通过 Worker 和对象存储交付。
|
||||
|
||||
### 5. 后台运营细化
|
||||
|
||||
- 租户 secrets 通用后台管理。
|
||||
- 租户监督规则、跟进报表和洞察报表。
|
||||
- 更细粒度 RBAC 权限点。
|
||||
- 操作审计覆盖率补齐。
|
||||
- 发票、催缴、佣金联动调账。
|
||||
|
||||
### 6. 旧路径兼容评估
|
||||
|
||||
只有前端明确依赖且重写成本高时,才增加薄兼容 Controller。兼容层不得恢复:
|
||||
|
||||
- Supabase 相关路径;
|
||||
- 旧 grant/adoption 公共题库授权模型;
|
||||
- 旧 `QuestionIds` JSON;
|
||||
- 旧 Provider 专用配置表。
|
||||
|
||||
已由新接口替代的旧行为:
|
||||
|
||||
- `/api/profile/activity-tasks` -> `/api/points/tasks`
|
||||
- `/api/profile/exchange-items`、`/api/profile/exchange-items/redeem` -> `/api/points/exchange-items`、`/api/points/exchange-orders`
|
||||
- 公共题库 adopt/sync/grant -> 平台公共题库所有权 + `TenantQuestionReference`
|
||||
|
||||
## 每批验收
|
||||
|
||||
```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
|
||||
dotnet ef migrations has-pending-model-changes \
|
||||
--project Tiku.Infrastructure \
|
||||
--startup-project Tiku.DbMigrator
|
||||
git diff --check
|
||||
git status --short --branch
|
||||
```
|
||||
@@ -1,20 +0,0 @@
|
||||
# API 契约基线
|
||||
|
||||
`operation-inventory.csv` 是旧 NestJS 与当前 .NET 运行时 OpenAPI 的机械比较结果。
|
||||
|
||||
状态:
|
||||
|
||||
- `exact_match`:HTTP 方法和路径完全一致,仍需核对 DTO、响应、权限和业务错误。
|
||||
- `legacy_only`:只存在于旧 NestJS,后续决定迁移、替代或删除。
|
||||
- `target_only`:只存在于 .NET,通常是新 REST 设计、诊断接口或路径调整。
|
||||
|
||||
重新生成:
|
||||
|
||||
```bash
|
||||
python3 scripts/compare_openapi.py \
|
||||
--legacy /path/to/nest-openapi.json \
|
||||
--target /path/to/dotnet-openapi.json \
|
||||
--output docs/migration/contracts/operation-inventory.csv
|
||||
```
|
||||
|
||||
原始 OpenAPI 文件较大且变化频繁,不提交仓库;只提交归一化后的 CSV 基线。
|
||||
@@ -1,415 +0,0 @@
|
||||
method,path,status,legacy_operation_id,legacy_summary,target_operation_id,target_summary
|
||||
GET,/api/ai/school-recommendations,legacy_only,AiController_list,查询院校推荐报告,,AI 延后到 Semantic Kernel + 租户自带 API Key 独立阶段
|
||||
GET,/api/ai/school-recommendations/detail,legacy_only,AiController_detail,获取院校推荐报告详情,,AI 延后到 Semantic Kernel + 租户自带 API Key 独立阶段
|
||||
GET,/api/ai/school-recommendations/export,legacy_only,AiController_exportReport,导出院校推荐报告,,AI 延后到 Semantic Kernel + 租户自带 API Key 独立阶段
|
||||
POST,/api/ai/school-recommendations/generate,legacy_only,AiController_generate,生成院校推荐报告,,AI 延后到 Semantic Kernel + 租户自带 API Key 独立阶段
|
||||
GET,/api/assets/{assetId}/download,target_only,,,,获取资源下载地址
|
||||
GET,/api/assets/{assetId}/preview,target_only,,,,获取资源预览地址
|
||||
POST,/api/auth/login/password,target_only,,,,手机号密码登录
|
||||
POST,/api/auth/login/sms,target_only,,,,短信验证码登录
|
||||
POST,/api/auth/logout,exact_match,AuthController_logout,退出登录,,退出登录
|
||||
GET,/api/auth/me,legacy_only,AuthController_me,获取当前登录用户,,
|
||||
POST,/api/auth/oauth/qq,legacy_only,AuthController_qq,QQ OAuth 登录,,
|
||||
POST,/api/auth/oauth/wechat,exact_match,AuthController_wechat,微信网页 OAuth 登录,,微信网页 OAuth 登录
|
||||
POST,/api/auth/oauth/wechat-miniapp,exact_match,AuthController_miniapp,微信小程序登录,,微信小程序登录
|
||||
POST,/api/auth/phone/bind,legacy_only,AuthController_bindPhone,绑定手机号,,
|
||||
POST,/api/auth/refresh,target_only,,,,刷新登录会话
|
||||
POST,/api/auth/sms/send,legacy_only,AuthController_sendSms,发送短信验证码,,
|
||||
POST,/api/auth/sms/verify,legacy_only,AuthController_verifySms,校验短信验证码并登录,,
|
||||
GET,/api/catalog/announcements,exact_match,CatalogController_announcements,查询公告,,查询公告
|
||||
GET,/api/catalog/app-assets,target_only,,,,查询应用资源
|
||||
GET,/api/catalog/assets,legacy_only,CatalogController_assets,查询内容资源,,
|
||||
GET,/api/catalog/assets/download,legacy_only,CatalogController_download,获取资源下载地址,,
|
||||
GET,/api/catalog/assets/preview,legacy_only,CatalogController_preview,获取资源预览地址,,
|
||||
GET,/api/catalog/banners,exact_match,CatalogController_banners,查询首页横幅,,查询首页横幅
|
||||
GET,/api/catalog/categories,exact_match,CatalogController_categories,查询题目分类,,查询题目分类
|
||||
GET,/api/catalog/content-assets,target_only,,,,查询内容资源
|
||||
GET,/api/catalog/content-entries,exact_match,CatalogController_contentEntries,查询内容入口,,查询内容入口
|
||||
GET,/api/catalog/content-nodes,exact_match,CatalogController_contentNodes,查询内容导航节点,,查询内容导航节点
|
||||
GET,/api/catalog/exam-dates,exact_match,CatalogController_examDates,查询考试日期,,查询考试日期
|
||||
GET,/api/catalog/faqs,exact_match,CatalogController_faqs,查询常见问题,,查询常见问题
|
||||
GET,/api/catalog/handbook-chapters,exact_match,CatalogController_handbookChapters,查询知识手册章节,,查询知识手册章节
|
||||
GET,/api/catalog/handbook-entries,exact_match,CatalogController_handbookEntries,查询知识手册条目,,查询知识手册条目
|
||||
GET,/api/catalog/handbook-subjects,exact_match,CatalogController_handbookSubjects,查询知识手册科目,,查询知识手册科目
|
||||
GET,/api/catalog/images,target_only,,,,查询图片资源
|
||||
GET,/api/catalog/majors,exact_match,CatalogController_majors,查询专业目录,,查询专业目录
|
||||
GET,/api/catalog/module-nodes,exact_match,CatalogController_moduleNodes,查询模块导航节点,,查询模块导航节点
|
||||
GET,/api/catalog/practice-blueprints,exact_match,CatalogController_blueprints,查询练习蓝图,,查询练习蓝图
|
||||
GET,/api/catalog/products,exact_match,CatalogController_products,查询可购买产品,,查询可购买产品
|
||||
GET,/api/catalog/question-banks,target_only,,,,查询题库列表
|
||||
GET,/api/catalog/question-categories,target_only,,,,查询题目分类
|
||||
GET,/api/catalog/question-collections,exact_match,CatalogController_collections,查询可用题集,,查询可用题集
|
||||
GET,/api/catalog/question-collections/questions,exact_match,CatalogController_collectionQuestions,查询题集内题目,,查询题集内题目
|
||||
GET,/api/catalog/question-videos,target_only,,,,查询题目关联视频
|
||||
GET,/api/catalog/questions,exact_match,CatalogController_questions,查询已发布题目,,查询已发布题目
|
||||
GET,/api/catalog/questions/{questionId},target_only,,,,查询题目详情
|
||||
GET,/api/catalog/questions/{questionId}/versions,target_only,,,,查询题目版本
|
||||
GET,/api/catalog/region-modules,exact_match,CatalogController_regionModules,查询地区功能模块,,查询地区功能模块
|
||||
GET,/api/catalog/regions,exact_match,CatalogController_regions,查询可用地区,,查询可用地区
|
||||
GET,/api/catalog/schools,exact_match,CatalogController_schools,查询院校目录,,查询院校目录
|
||||
GET,/api/catalog/subjects,exact_match,CatalogController_subjects,查询科目目录,,查询科目目录
|
||||
GET,/api/catalog/svip-plans,exact_match,CatalogController_svipPlans,查询 SVIP 套餐,,查询 SVIP 套餐
|
||||
GET,/api/catalog/timelines,legacy_only,CatalogController_timelines,查询考试时间线,,
|
||||
GET,/api/catalog/video-explanations,target_only,,,,查询视频讲解
|
||||
GET,/api/catalog/vocabulary-units,exact_match,CatalogController_vocabularyUnits,查询词汇单元,,查询词汇单元
|
||||
GET,/api/catalog/vocabulary-words,exact_match,CatalogController_vocabularyWords,查询词汇单词,,查询词汇单词
|
||||
POST,/api/commerce/activation-codes/check,legacy_only,CommerceOrdersController_checkCode,检查激活码是否可兑换,,
|
||||
POST,/api/commerce/activation-codes/redeem,legacy_only,CommerceOrdersController_redeemCode,兑换激活码并发放权益,,
|
||||
GET,/api/commerce/adjustment-vouchers,legacy_only,CommerceAdjustmentsController_list,查询调账凭证,,
|
||||
POST,/api/commerce/adjustment-vouchers,legacy_only,CommerceAdjustmentsController_create,创建调账凭证,,
|
||||
GET,/api/commerce/adjustment-vouchers/events,legacy_only,CommerceAdjustmentsController_events,查询调账凭证事件,,
|
||||
GET,/api/commerce/adjustment-vouchers/report,legacy_only,CommerceAdjustmentsController_report,查询调账统计报告,,
|
||||
POST,/api/commerce/adjustment-vouchers/status,legacy_only,CommerceAdjustmentsController_status,审核或关闭调账凭证,,
|
||||
GET,/api/commerce/coupons,target_only,,,,查询当前用户优惠券
|
||||
POST,/api/commerce/coupons/check,target_only,,,,校验优惠券并预览订单金额
|
||||
POST,/api/commerce/coupons/claim,exact_match,CommerceOrdersController_claimCoupon,领取优惠券,,领取优惠券
|
||||
GET,/api/commerce/entitlements,legacy_only,CommerceOrdersController_entitlements,查询当前用户有效权益,,
|
||||
GET,/api/commerce/entitlements/check,legacy_only,CommerceOrdersController_entitlementCheck,检查当前用户是否拥有指定权益,,
|
||||
GET,/api/commerce/entitlements/current,target_only,,,,查询当前用户权益
|
||||
GET,/api/commerce/operations/anomalies,legacy_only,CommerceAdjustmentsController_operations,查询支付运营异常总览,,
|
||||
GET,/api/commerce/orders,exact_match,CommerceOrdersController_list,查询当前用户订单,,查询当前用户订单
|
||||
POST,/api/commerce/orders,exact_match,CommerceOrdersController_create,创建商城订单,,创建学生端订单
|
||||
GET,/api/commerce/orders/detail,legacy_only,CommerceOrdersController_detail,查询订单详情,,
|
||||
GET,/api/commerce/orders/status,legacy_only,CommerceOrdersController_status,查询订单及最新支付状态,,
|
||||
GET,/api/commerce/orders/{orderNo},target_only,,,,查询当前用户订单详情
|
||||
POST,/api/commerce/payments,target_only,,,,创建订单支付
|
||||
POST,/api/commerce/payments/create,legacy_only,CommercePaymentsController_createPayment,创建第三方支付参数,,
|
||||
POST,/api/commerce/payments/manual-confirm,legacy_only,CommercePaymentsController_manualConfirm,人工确认线下支付,,
|
||||
POST,/api/commerce/payments/notify/alipay,exact_match,CommercePaymentsController_alipayPayment,接收支付宝支付结果通知,,支付宝支付回调
|
||||
POST,/api/commerce/payments/notify/wechat-pay,target_only,,,,微信支付回调
|
||||
POST,/api/commerce/payments/notify/wechat_pay,legacy_only,CommercePaymentsController_wechatPayment,接收微信支付成功通知,,
|
||||
GET,/api/commerce/reconciliation/anomalies,legacy_only,CommerceReconciliationController_anomalies,查询对账异常汇总,,
|
||||
GET,/api/commerce/reconciliation/batches,legacy_only,CommerceReconciliationController_batches,查询对账批次,,
|
||||
POST,/api/commerce/reconciliation/import,legacy_only,CommerceReconciliationController_import,导入支付渠道账单并创建对账批次,,
|
||||
GET,/api/commerce/reconciliation/issues,legacy_only,CommerceReconciliationController_issues,查询对账处理工单,,
|
||||
POST,/api/commerce/reconciliation/issues/create,legacy_only,CommerceReconciliationController_createIssue,从对账异常创建处理工单,,
|
||||
GET,/api/commerce/reconciliation/issues/events,legacy_only,CommerceReconciliationController_issueEvents,查询对账工单事件,,
|
||||
POST,/api/commerce/reconciliation/issues/status,legacy_only,CommerceReconciliationController_updateIssue,更新对账工单状态,,
|
||||
GET,/api/commerce/reconciliation/items,legacy_only,CommerceReconciliationController_items,查询对账明细,,
|
||||
POST,/api/commerce/reconciliation/preview,legacy_only,CommerceReconciliationController_preview,预览支付渠道账单对账,,
|
||||
GET,/api/commerce/reconciliation/provider-bills/jobs,legacy_only,CommerceReconciliationController_billJobs,查询渠道账单下载任务,,
|
||||
POST,/api/commerce/reconciliation/provider-bills/request,legacy_only,CommerceReconciliationController_requestBill,创建渠道官方账单下载任务,,
|
||||
GET,/api/commerce/refunds,legacy_only,CommercePaymentsController_refunds,查询租户退款申请,,
|
||||
POST,/api/commerce/refunds,legacy_only,CommercePaymentsController_createRefund,创建退款申请,,
|
||||
POST,/api/commerce/refunds/notify/alipay,legacy_only,CommercePaymentsController_alipayRefund,接收支付宝退款结果通知,,
|
||||
POST,/api/commerce/refunds/notify/wechat_pay,legacy_only,CommercePaymentsController_wechatRefund,接收微信退款结果通知,,
|
||||
POST,/api/commerce/refunds/status,legacy_only,CommercePaymentsController_updateRefund,审核或处理退款,,
|
||||
PUT,/api/commission/member-rate,exact_match,ReferralCommissionController_memberRate,设置成员专属佣金率,,
|
||||
GET,/api/commission/orders,exact_match,ReferralCommissionController_orders,查询佣金来源明细,,
|
||||
GET,/api/commission/settings,exact_match,ReferralCommissionController_settings,查询租户佣金配置,,
|
||||
PUT,/api/commission/settings,exact_match,ReferralCommissionController_updateSettings,保存租户佣金配置,,
|
||||
GET,/api/commission/settlements,exact_match,ReferralCommissionController_settlements,查询佣金结算单,,
|
||||
GET,/api/commission/settlements/export,exact_match,ReferralCommissionController_export,导出佣金结算明细,,
|
||||
POST,/api/commission/settlements/generate,exact_match,ReferralCommissionController_generate,生成佣金结算单,,
|
||||
GET,/api/commission/settlements/proofs,exact_match,ReferralCommissionController_proofs,查询佣金结算凭证,,
|
||||
POST,/api/commission/settlements/proofs,exact_match,ReferralCommissionController_createProof,提交佣金打款或票据凭证,,
|
||||
POST,/api/commission/settlements/proofs/status,exact_match,ReferralCommissionController_updateProof,审核佣金结算凭证,,
|
||||
POST,/api/commission/settlements/status,exact_match,ReferralCommissionController_updateStatus,更新佣金结算状态,,
|
||||
GET,/api/commission/summary,exact_match,ReferralCommissionController_summary,查询佣金汇总,,
|
||||
GET,/api/crm/config,exact_match,ReferralCrmController_config,查询 CRM 推送配置,,查询 CRM 推送配置
|
||||
PUT,/api/crm/config,exact_match,ReferralCrmController_upsertConfig,保存 CRM 推送与线索分配配置,,保存 CRM 推送配置
|
||||
GET,/api/crm/dead-letters,exact_match,ReferralCrmController_deadLetters,查询 CRM 死信任务与汇总,,查询 CRM 死信任务与汇总
|
||||
GET,/api/crm/queue,exact_match,ReferralCrmController_queue,查询 CRM webhook 队列,,查询 CRM webhook 队列
|
||||
POST,/api/crm/queue/action,exact_match,ReferralCrmController_action,重试或忽略 CRM 死信任务,,重试或忽略 CRM 死信任务
|
||||
GET,/api/crm/queue/logs,exact_match,ReferralCrmController_logs,查询 CRM 队列执行日志,,查询 CRM 队列执行日志
|
||||
GET,/api/health,target_only,,,,健康检查
|
||||
POST,/api/learning/answers,exact_match,LearningController_answer,提交题目答案,,提交题目答案
|
||||
GET,/api/learning/favorites/questions,exact_match,LearningController_favoriteQuestions,查询收藏题目,,查询收藏题目
|
||||
POST,/api/learning/favorites/questions,exact_match,LearningController_toggleFavoriteQuestion,收藏或取消收藏题目,,收藏或取消收藏题目
|
||||
GET,/api/learning/leaderboard,exact_match,LearningController_leaderboard,查询学习排行榜,,查询学习排行榜
|
||||
GET,/api/learning/practice-reports,exact_match,LearningController_reports,查询练习报告列表,,查询练习报告列表
|
||||
POST,/api/learning/practice-sessions,exact_match,LearningController_createSession,创建练习会话,,创建练习会话
|
||||
GET,/api/learning/practice-sessions/detail,exact_match,LearningController_sessionDetail,获取练习会话详情,,获取练习会话详情
|
||||
GET,/api/learning/practice-sessions/history,exact_match,LearningController_history,查询练习历史,,查询练习历史
|
||||
GET,/api/learning/practice-sessions/report,exact_match,LearningController_sessionReport,获取练习会话报告,,获取练习会话报告
|
||||
POST,/api/learning/practice-sessions/submit,exact_match,LearningController_submitSession,提交练习会话,,提交练习会话并生成报告
|
||||
GET,/api/learning/stats,exact_match,LearningController_stats,查询学习统计,,查询学习统计
|
||||
GET,/api/learning/trend,exact_match,LearningController_trend,查询学习趋势,,查询学习趋势
|
||||
GET,/api/learning/vocabulary/favorites,exact_match,LearningController_favoriteWords,查询收藏单词,,查询收藏单词
|
||||
POST,/api/learning/vocabulary/favorites,exact_match,LearningController_toggleFavoriteWord,收藏或取消收藏单词,,收藏或取消收藏单词
|
||||
GET,/api/learning/vocabulary/progress,exact_match,LearningController_wordProgress,查询单词学习进度,,查询单词学习进度
|
||||
POST,/api/learning/vocabulary/progress,exact_match,LearningController_updateWordProgress,更新单词学习进度,,更新单词学习进度
|
||||
POST,/api/learning/vocabulary/review,exact_match,LearningController_reviewWord,提交单词复习结果,,提交单词复习结果
|
||||
GET,/api/learning/vocabulary/review-plan,exact_match,LearningController_wordPlan,生成单词复习计划,,生成单词复习计划
|
||||
GET,/api/learning/vocabulary/stats,exact_match,LearningController_wordStats,查询单词学习统计,,查询单词学习统计
|
||||
GET,/api/learning/wrong-questions,exact_match,LearningController_wrongQuestions,查询错题列表,,查询错题列表
|
||||
POST,/api/learning/wrong-questions/resolve,exact_match,LearningController_resolveWrong,将错题标记为已解决,,将错题标记为已解决
|
||||
GET,/api/learning/wrong-questions/review-plan,exact_match,LearningController_wrongPlan,生成错题复习计划,,生成错题复习计划
|
||||
GET,/api/me,target_only,,,,
|
||||
GET,/api/platform-admin/audit-alert-rules,legacy_only,PlatformAdminAuditController_rules,查询平台审计告警规则,,
|
||||
GET,/api/platform-admin/audit-alerts,legacy_only,PlatformAdminAuditController_alerts,查询平台审计告警,,
|
||||
POST,/api/platform-admin/audit-alerts/status,legacy_only,PlatformAdminAuditController_updateAlert,更新平台审计告警状态,,
|
||||
GET,/api/platform-admin/audit-logs,legacy_only,PlatformAdminAuditController_logs,查询平台审计日志,,
|
||||
GET,/api/platform-admin/audit-logs/export,legacy_only,PlatformAdminAuditController_exportLogs,导出平台审计日志,,
|
||||
GET,/api/platform-admin/audit-notification-channels,legacy_only,PlatformAdminAuditController_channels,查询审计告警通知渠道,,
|
||||
PUT,/api/platform-admin/audit-notification-channels,legacy_only,PlatformAdminAuditController_upsertChannel,创建或更新审计告警通知渠道,,
|
||||
GET,/api/platform-admin/audit-notification-events,legacy_only,PlatformAdminAuditController_events,查询审计告警通知事件,,
|
||||
GET,/api/platform-admin/dunning-notification-channels,legacy_only,PlatformAdminDunningChannelsController_channels,查询平台催缴通知渠道,,
|
||||
PUT,/api/platform-admin/dunning-notification-channels,legacy_only,PlatformAdminDunningChannelsController_upsertChannel,创建或更新平台催缴通知渠道,,
|
||||
GET,/api/platform-admin/dunning-notification-events,legacy_only,PlatformAdminDunningEventsController_events,查询平台催缴通知事件,,
|
||||
GET,/api/platform-admin/invoices,legacy_only,PlatformAdminBillingController_invoices,查询平台租户账单,,
|
||||
POST,/api/platform-admin/invoices,legacy_only,PlatformAdminBillingController_createInvoice,手工创建租户账单,,
|
||||
POST,/api/platform-admin/invoices/from-subscription,legacy_only,PlatformAdminBillingController_fromSubscription,为单个订阅生成账单,,
|
||||
POST,/api/platform-admin/invoices/from-subscriptions-batch,legacy_only,PlatformAdminBillingController_fromSubscriptionsBatch,批量生成订阅账单,,
|
||||
POST,/api/platform-admin/invoices/from-usage-overage,legacy_only,PlatformAdminBillingController_fromUsage,批量生成用量超额账单,,
|
||||
POST,/api/platform-admin/invoices/payments/manual-confirm,legacy_only,PlatformAdminBillingController_confirmPayment,人工确认平台服务费收款,,
|
||||
POST,/api/platform-admin/invoices/process-overdue,legacy_only,PlatformAdminBillingController_processOverdue,处理逾期账单并创建催缴记录,,
|
||||
GET,/api/platform-admin/invoices/reminders,legacy_only,PlatformAdminBillingController_reminders,查询账单催缴记录,,
|
||||
GET,/api/platform-admin/invoices/subscription-candidates,legacy_only,PlatformAdminBillingController_subscriptionCandidates,预览订阅账单候选,,
|
||||
GET,/api/platform-admin/invoices/usage-overage-candidates,legacy_only,PlatformAdminBillingController_usageCandidates,预览用量超额账单候选,,
|
||||
GET,/api/platform-admin/overview,legacy_only,PlatformAdminOverviewController_overview,查询平台经营概览,,
|
||||
GET,/api/platform-admin/permissions,legacy_only,PlatformAdminOverviewController_permissions,查询当前平台管理员权限,,
|
||||
GET,/api/platform-admin/plans,legacy_only,PlatformAdminOverviewController_plans,查询平台 SaaS 套餐,,
|
||||
GET,/api/platform-admin/question-bank-grants,legacy_only,PlatformAdminQuestionBanksController_grants,查询公共题库授权规则,,
|
||||
PUT,/api/platform-admin/question-bank-grants,legacy_only,PlatformAdminQuestionBanksController_upsertGrant,创建或更新公共题库授权,,
|
||||
GET,/api/platform-admin/question-bank-sync-status,legacy_only,PlatformAdminQuestionBanksController_syncStatus,查询公共题库采用与同步状态,,
|
||||
GET,/api/platform-admin/question-banks,legacy_only,PlatformAdminQuestionBanksController_banks,查询平台公共题库,,
|
||||
GET,/api/platform-admin/staff,legacy_only,PlatformAdminOverviewController_staff,查询平台员工列表,,
|
||||
PUT,/api/platform-admin/staff,legacy_only,PlatformAdminOverviewController_upsertStaff,创建或更新平台员工,,
|
||||
PATCH,/api/platform-admin/staff/status,legacy_only,PlatformAdminOverviewController_updateStaffStatus,启用或禁用平台员工,,
|
||||
POST,/api/platform-admin/subscriptions,legacy_only,PlatformAdminBillingController_createSubscription,创建租户订阅,,
|
||||
GET,/api/platform-admin/tenants,legacy_only,PlatformAdminTenantsController_list,查询平台租户列表,,
|
||||
POST,/api/platform-admin/tenants,legacy_only,PlatformAdminTenantsController_create,创建平台租户,,
|
||||
PUT,/api/platform-admin/tenants/billing-profile,legacy_only,PlatformAdminTenantsController_billingProfile,保存租户账务与开票资料,,
|
||||
GET,/api/platform-admin/tenants/detail,legacy_only,PlatformAdminTenantsController_detail,查询平台租户详情,,
|
||||
PATCH,/api/platform-admin/tenants/status,legacy_only,PlatformAdminTenantsController_status,更新租户业务与账务状态,,
|
||||
GET,/api/platform-admin/usage,legacy_only,PlatformAdminBillingController_usage,查询租户平台用量记录,,
|
||||
POST,/api/platform-admin/usage,legacy_only,PlatformAdminBillingController_recordUsage,记录租户平台用量,,
|
||||
GET,/api/points/exchange-items,target_only,,,,查询积分兑换项
|
||||
GET,/api/points/exchange-orders,target_only,,,,查询当前用户积分兑换订单
|
||||
POST,/api/points/exchange-orders,target_only,,,,创建积分兑换订单
|
||||
GET,/api/points/summary,target_only,,,,查询当前用户积分摘要
|
||||
GET,/api/points/tasks,target_only,,,,查询当前可领取积分任务
|
||||
POST,/api/points/tasks/claim,target_only,,,,领取积分任务奖励
|
||||
GET,/api/profile/activity-tasks,legacy_only,ProfileController_tasks,查询积分活动任务,,由 /api/points/tasks 替代
|
||||
POST,/api/profile/activity-tasks/claim,legacy_only,ProfileController_claimTask,领取活动任务奖励,,由 /api/points/tasks/claim 替代
|
||||
GET,/api/profile/badges,exact_match,ProfileController_badges,查询徽章列表,,查询徽章列表
|
||||
POST,/api/profile/check-in,exact_match,ProfileController_checkIn,每日签到,,每日签到
|
||||
GET,/api/profile/exam-countdowns,exact_match,ProfileController_countdowns,查询考试倒计时,,查询考试倒计时
|
||||
GET,/api/profile/exchange-items,legacy_only,ProfileController_exchangeItems,查询积分兑换商品,,由 /api/points/exchange-items 替代
|
||||
POST,/api/profile/exchange-items/redeem,legacy_only,ProfileController_redeem,兑换积分商品,,由 /api/points/exchange-orders 替代
|
||||
GET,/api/profile/feedbacks,exact_match,ProfileController_feedbacks,查询反馈记录,,查询反馈记录
|
||||
POST,/api/profile/feedbacks,exact_match,ProfileController_submitFeedback,提交意见反馈,,提交意见反馈
|
||||
GET,/api/profile/me,exact_match,ProfileController_me,获取当前学生资料,,获取当前学生资料
|
||||
PATCH,/api/profile/me,exact_match,ProfileController_updateMe,更新当前学生资料,,更新当前学生资料
|
||||
GET,/api/profile/notifications,exact_match,ProfileController_notificationList,查询用户通知,,查询用户通知
|
||||
POST,/api/profile/notifications/status,exact_match,ProfileController_notificationStatus,更新通知状态,,更新通知状态
|
||||
GET,/api/profile/score-events,exact_match,ProfileController_scoreEvents,查询积分流水,,查询当前用户积分流水
|
||||
GET,/api/questions/videos,exact_match,QuestionVideoController_list,查询单道题目的解析视频,,查询单道题目的解析视频
|
||||
POST,/api/questions/videos/batch,exact_match,QuestionVideoController_batch,批量查询题目解析视频,,批量查询题目解析视频
|
||||
POST,/api/referral/bind,exact_match,ReferralPublicController_bind,绑定当前用户的推荐归属,,绑定当前用户推荐归属
|
||||
GET,/api/referral/conversion-report,exact_match,ReferralManagementController_conversion,查询推荐转化与佣金报告,,查询推荐转化报告
|
||||
POST,/api/referral/invite-code,exact_match,ReferralPublicController_invite,生成或查询当前成员邀请码,,生成或查询当前成员邀请码
|
||||
POST,/api/referral/manual-bind,exact_match,ReferralManagementController_manualBind,人工调整学生推荐归属,,人工调整学生推荐归属
|
||||
POST,/api/referral/qrcode,exact_match,ReferralPublicController_qrcode,生成或查询推广二维码,,生成或查询推广二维码
|
||||
POST,/api/referral/resolve,exact_match,ReferralPublicController_resolve,解析推荐邀请码,,解析推荐邀请码
|
||||
GET,/api/referral/sales-clients,exact_match,ReferralManagementController_clients,查询推荐人名下客户,,查询推荐人名下客户
|
||||
GET,/api/referral/sales-stats,exact_match,ReferralManagementController_salesStats,查询销售推荐统计排行,,查询销售推荐统计排行
|
||||
GET,/api/referral/stats,exact_match,ReferralManagementController_stats,查询推荐人个人统计,,查询推荐人个人统计
|
||||
GET,/api/referral/team,exact_match,ReferralManagementController_team,查询推荐团队成员,,查询推荐团队成员
|
||||
PUT,/api/referral/team,exact_match,ReferralManagementController_upsertTeam,新增或更新推荐团队关系,,新增或更新推荐团队关系
|
||||
POST,/api/referral/track-event,exact_match,ReferralPublicController_track,记录推荐行为并按规则创建线索,,记录推荐行为
|
||||
GET,/api/scoreline/fields,exact_match,ScorelineController_fields,查询分数线字段配置,,查询分数线字段配置
|
||||
GET,/api/scoreline/majors,legacy_only,ScorelineController_majors,查询分数线专业,,
|
||||
GET,/api/scoreline/records,exact_match,ScorelineController_records,分页查询分数线记录,,分页查询分数线记录
|
||||
GET,/api/scoreline/schools,legacy_only,ScorelineController_schools,查询分数线院校,,
|
||||
GET,/api/scoreline/trend,exact_match,ScorelineController_trend,查询历年分数线趋势,,查询历年分数线趋势
|
||||
GET,/api/scoreline/years,exact_match,ScorelineController_years,查询分数线可用年份,,查询分数线可用年份
|
||||
GET,/api/tenant-admin/activation-codes,legacy_only,TenantCodesController_codes,查询激活码,,
|
||||
PUT,/api/tenant-admin/activation-codes,legacy_only,TenantCodesController_upsertCode,新增或更新激活码,,
|
||||
POST,/api/tenant-admin/activation-codes/generate,legacy_only,TenantCodesController_generate,批量生成激活码,,
|
||||
GET,/api/tenant-admin/announcements,legacy_only,TenantMarketingOperationsController_announcements,查询租户公告,,
|
||||
PUT,/api/tenant-admin/announcements,legacy_only,TenantMarketingOperationsController_upsertAnnouncement,新增或更新租户公告,,
|
||||
GET,/api/tenant-admin/audit-logs,exact_match,TenantGovernanceController_audit,查询租户审计日志,,查询租户审计日志
|
||||
GET,/api/tenant-admin/auth-providers,exact_match,TenantIntegrationConfigController_authProviders,查询租户登录 Provider 公开配置,,查询租户登录 Provider 公开配置
|
||||
PUT,/api/tenant-admin/auth-providers,exact_match,TenantIntegrationConfigController_upsertAuthProvider,新增或更新租户登录 Provider,,新增或更新租户登录 Provider
|
||||
GET,/api/tenant-admin/badge-grants,exact_match,TenantBadgeManagementController_grants,查询勋章发放记录,,查询勋章发放记录
|
||||
POST,/api/tenant-admin/badge-grants,exact_match,TenantBadgeManagementController_grant,向租户成员发放勋章,,向租户成员发放勋章
|
||||
GET,/api/tenant-admin/badges,exact_match,TenantBadgeManagementController_badges,查询租户勋章,,查询租户勋章
|
||||
PUT,/api/tenant-admin/badges,exact_match,TenantBadgeManagementController_upsertBadge,新增或更新租户勋章,,新增或更新租户勋章
|
||||
GET,/api/tenant-admin/banners,legacy_only,TenantMarketingOperationsController_banners,查询租户 Banner,,
|
||||
PUT,/api/tenant-admin/banners,legacy_only,TenantMarketingOperationsController_upsertBanner,新增或更新租户 Banner,,
|
||||
PUT,/api/tenant-admin/branding,exact_match,TenantAppearanceController_branding,更新租户品牌信息,,更新租户品牌信息
|
||||
GET,/api/tenant-admin/classes,exact_match,TenantAdminClassesController_list,查询可管理的班级,,查询租户班级
|
||||
PUT,/api/tenant-admin/classes,exact_match,TenantAdminClassesController_upsert,新增或更新班级,,新增或更新租户班级
|
||||
POST,/api/tenant-admin/classes/disable,exact_match,TenantAdminClassesController_disable,停用班级,,停用租户班级
|
||||
GET,/api/tenant-admin/classes/members,exact_match,TenantAdminClassesController_members,查询班级成员,,查询班级成员
|
||||
PUT,/api/tenant-admin/classes/members,exact_match,TenantAdminClassesController_upsertMember,新增或更新班级成员,,新增或更新班级成员
|
||||
POST,/api/tenant-admin/classes/members/bulk-assign,legacy_only,TenantAdminClassesController_bulkAssign,批量分配班级成员,,
|
||||
POST,/api/tenant-admin/classes/members/remove,exact_match,TenantAdminClassesController_removeMember,移除班级成员,,移除班级成员
|
||||
GET,/api/tenant-admin/code-batches,legacy_only,TenantCodesController_batches,查询兑换码批次,,
|
||||
PUT,/api/tenant-admin/code-batches,legacy_only,TenantCodesController_upsertBatch,新增或更新兑换码批次,,
|
||||
GET,/api/tenant-admin/coupons,legacy_only,TenantCodesController_coupons,查询优惠券,,
|
||||
PUT,/api/tenant-admin/coupons,legacy_only,TenantCodesController_upsertCoupon,新增或更新优惠券,,
|
||||
GET,/api/tenant-admin/coupons/redemptions,legacy_only,TenantCodesController_redemptions,查询优惠券核销明细,,
|
||||
GET,/api/tenant-admin/coupons/report,legacy_only,TenantCodesController_report,查询优惠券核销报表,,
|
||||
GET,/api/tenant-admin/dashboard,legacy_only,TenantAdminInsightsController_dashboard,查询租户运营管理看板,,
|
||||
GET,/api/tenant-admin/domains,exact_match,TenantIntegrationConfigController_domains,查询租户域名,,查询租户域名
|
||||
POST,/api/tenant-admin/domains,exact_match,TenantIntegrationConfigController_createDomain,添加租户域名,,添加租户域名
|
||||
GET,/api/tenant-admin/exam-dates,legacy_only,TenantMarketingOperationsController_examDates,查询租户考试日期,,
|
||||
PUT,/api/tenant-admin/exam-dates,legacy_only,TenantMarketingOperationsController_upsertExamDate,新增或更新考试日期,,
|
||||
GET,/api/tenant-admin/faqs,legacy_only,TenantMarketingOperationsController_faqs,查询租户常见问题,,
|
||||
PUT,/api/tenant-admin/faqs,legacy_only,TenantMarketingOperationsController_upsertFaq,新增或更新租户常见问题,,
|
||||
GET,/api/tenant-admin/feedbacks,exact_match,TenantMarketingOperationsController_feedbacks,查询用户反馈,,查询用户反馈
|
||||
GET,/api/tenant-admin/feedbacks/events,legacy_only,TenantMarketingOperationsController_feedbackEvents,查询单条反馈处理事件,,
|
||||
GET,/api/tenant-admin/feedbacks/report,legacy_only,TenantMarketingOperationsController_feedbackReport,查询反馈处理统计报表,,
|
||||
POST,/api/tenant-admin/feedbacks/status,exact_match,TenantMarketingOperationsController_updateFeedback,更新反馈处理状态并可发放奖励,,处理用户反馈
|
||||
GET,/api/tenant-admin/members,exact_match,TenantGovernanceController_members,查询租户成员,,查询租户成员
|
||||
PUT,/api/tenant-admin/members,exact_match,TenantGovernanceController_upsert,新增或更新租户成员,,新增或更新租户成员
|
||||
POST,/api/tenant-admin/members/disable,exact_match,TenantGovernanceController_disable,停用租户成员并撤销会话,,停用租户成员并撤销会话
|
||||
GET,/api/tenant-admin/notifications,target_only,,,,查询用户站内通知
|
||||
PUT,/api/tenant-admin/notifications,target_only,,,,新增或更新用户站内通知
|
||||
GET,/api/tenant-admin/overview,legacy_only,TenantAdminInsightsController_overview,查询租户基础信息与公开配置,,
|
||||
GET,/api/tenant-admin/payment-accounts,legacy_only,TenantIntegrationConfigController_payments,查询租户支付账号公开配置,,
|
||||
PUT,/api/tenant-admin/payment-accounts,legacy_only,TenantIntegrationConfigController_upsertPayment,新增或更新租户支付账号,,
|
||||
GET,/api/tenant-admin/permissions,exact_match,TenantAdminRolesController_permissions,查询当前管理员权限矩阵,,查询租户后台权限矩阵
|
||||
GET,/api/tenant-admin/point-activity-claims,legacy_only,TenantPointsController_claims,查询积分任务领取记录,,
|
||||
GET,/api/tenant-admin/point-activity-tasks,legacy_only,TenantPointsController_tasks,查询积分活动任务,,
|
||||
PUT,/api/tenant-admin/point-activity-tasks,legacy_only,TenantPointsController_upsertTask,新增或更新积分活动任务,,
|
||||
GET,/api/tenant-admin/point-exchange-items,legacy_only,TenantPointsController_items,查询积分兑换项,,
|
||||
PUT,/api/tenant-admin/point-exchange-items,legacy_only,TenantPointsController_upsertItem,新增或更新积分兑换项,,
|
||||
GET,/api/tenant-admin/point-exchange-orders,legacy_only,TenantPointsController_orders,查询积分兑换订单,,
|
||||
GET,/api/tenant-admin/points-risk-report,legacy_only,TenantPointsController_risk,查询积分风险报表,,
|
||||
GET,/api/tenant-admin/role-templates,exact_match,TenantAdminRolesController_templates,查询租户角色模板,,查询租户角色模板
|
||||
PUT,/api/tenant-admin/role-templates,exact_match,TenantAdminRolesController_upsert,新增或更新租户角色模板,,新增或更新租户角色模板
|
||||
POST,/api/tenant-admin/role-templates/disable,exact_match,TenantAdminRolesController_disable,停用租户角色模板,,停用租户角色模板
|
||||
GET,/api/tenant-admin/secrets,legacy_only,TenantSecretVaultController_list,查询租户密钥掩码状态,,
|
||||
PUT,/api/tenant-admin/secrets,legacy_only,TenantSecretVaultController_upsert,写入或轮换租户密钥,,
|
||||
PUT,/api/tenant-admin/settings,exact_match,TenantAppearanceController_settings,更新租户公开设置与功能开关,,更新租户公开设置与功能开关
|
||||
GET,/api/tenant-admin/student-followups,target_only,,,,查询学生跟进
|
||||
PUT,/api/tenant-admin/student-followups,target_only,,,,新增或更新学生跟进
|
||||
GET,/api/tenant-admin/student-notes,target_only,,,,查询学生备注
|
||||
PUT,/api/tenant-admin/student-notes,target_only,,,,新增或更新学生备注
|
||||
GET,/api/tenant-admin/students,exact_match,TenantAdminStudentsController_list,游标分页查询租户学生,,查询租户学生
|
||||
PUT,/api/tenant-admin/students,exact_match,TenantAdminStudentsController_upsert,新增或更新租户学生档案,,新增或更新租户学生档案
|
||||
POST,/api/tenant-admin/students/bulk-upsert,legacy_only,TenantAdminStudentsController_bulkUpsert,批量新增或更新租户学生,,
|
||||
POST,/api/tenant-admin/students/crm-push,legacy_only,TenantAdminStudentsController_crmPush,批量创建学生跟进并推送 CRM,,
|
||||
GET,/api/tenant-admin/students/followups,legacy_only,TenantAdminEngagementController_followups,查询学生跟进任务,,
|
||||
PUT,/api/tenant-admin/students/followups,legacy_only,TenantAdminEngagementController_upsertFollowup,新增或更新学生跟进任务,,
|
||||
GET,/api/tenant-admin/students/followups/report,legacy_only,TenantAdminEngagementController_report,查询学生跟进统计报表,,
|
||||
GET,/api/tenant-admin/students/notes,legacy_only,TenantAdminEngagementController_notes,查询学生备注,,
|
||||
PUT,/api/tenant-admin/students/notes,legacy_only,TenantAdminEngagementController_upsertNote,新增或更新学生备注,,
|
||||
POST,/api/tenant-admin/students/status,exact_match,TenantAdminStudentsController_status,禁用、邀请或恢复租户学生,,更新租户学生状态
|
||||
POST,/api/tenant-admin/students/supervision/generate,legacy_only,TenantAdminSupervisionController_generate,批量生成学习督导跟进任务,,
|
||||
GET,/api/tenant-admin/students/supervision/preview,legacy_only,TenantAdminSupervisionController_preview,预览学习风险学生与督导原因,,
|
||||
GET,/api/tenant-admin/students/supervision/rules,legacy_only,TenantAdminSupervisionController_rules,查询学习督导规则,,
|
||||
PUT,/api/tenant-admin/students/supervision/rules,legacy_only,TenantAdminSupervisionController_upsertRule,新增或更新学习督导规则,,
|
||||
GET,/api/tenant-admin/teachers,legacy_only,TenantAdminInsightsController_teachers,查询租户教师,,
|
||||
GET,/api/tenant-admin/theme,exact_match,TenantAppearanceController_theme,查询租户当前主题与草稿,,查询租户当前主题与草稿
|
||||
GET,/api/tenant-admin/theme-templates,exact_match,TenantAppearanceController_templates,查询可用的租户主题模板,,查询可用租户主题模板
|
||||
POST,/api/tenant-admin/theme/preview,exact_match,TenantAppearanceController_preview,生成并保存租户主题草稿,,生成租户主题草稿
|
||||
POST,/api/tenant-admin/theme/publish,exact_match,TenantAppearanceController_publish,发布租户主题,,发布租户主题
|
||||
GET,/api/tenant-admin/user-notifications,legacy_only,TenantAdminInsightsController_notifications,查询租户用户通知,,
|
||||
GET,/api/tenant-commerce/activation-codes,target_only,,,,查询兑换码
|
||||
POST,/api/tenant-commerce/activation-codes/redeem,target_only,,,,后台核销兑换码
|
||||
POST,/api/tenant-commerce/code-batches,target_only,,,,创建兑换码批次
|
||||
GET,/api/tenant-commerce/coupons,target_only,,,,查询租户优惠券
|
||||
PUT,/api/tenant-commerce/coupons,target_only,,,,新增或更新租户优惠券
|
||||
GET,/api/tenant-commerce/coupons/redemptions,target_only,,,,查询优惠券领取和核销记录
|
||||
GET,/api/tenant-commerce/coupons/report,target_only,,,,查询优惠券基础报表
|
||||
GET,/api/tenant-commerce/orders,target_only,,,,查询租户订单
|
||||
GET,/api/tenant-commerce/payment-accounts,target_only,,,,查询租户支付账号
|
||||
PUT,/api/tenant-commerce/payment-accounts,target_only,,,,新增或更新租户支付账号
|
||||
GET,/api/tenant-commerce/payments,target_only,,,,查询租户支付记录
|
||||
GET,/api/tenant-commerce/point-activity-claims,target_only,,,,查询积分任务领取记录
|
||||
GET,/api/tenant-commerce/point-activity-tasks,target_only,,,,查询积分活动任务
|
||||
PUT,/api/tenant-commerce/point-activity-tasks,target_only,,,,新增或更新积分活动任务
|
||||
GET,/api/tenant-commerce/point-exchange-items,target_only,,,,查询积分兑换项
|
||||
PUT,/api/tenant-commerce/point-exchange-items,target_only,,,,新增或更新积分兑换项
|
||||
GET,/api/tenant-commerce/point-exchange-orders,target_only,,,,查询积分兑换订单
|
||||
POST,/api/tenant-commerce/point-exchange-orders/status,target_only,,,,更新积分兑换订单状态
|
||||
PUT,/api/tenant-commerce/secrets,target_only,,,,写入或轮换租户密钥
|
||||
GET,/api/tenant-content/assets,exact_match,TenantContentAssetsController_list,查询租户内容资产,,查询租户内容资产
|
||||
PUT,/api/tenant-content/assets,exact_match,TenantContentAssetsController_upsert,新增或更新内容资产,,新增或更新内容资产
|
||||
GET,/api/tenant-content/assets/access-events,exact_match,TenantContentAssetsController_accessEvents,查询资产访问审计事件,,查询资产访问审计事件
|
||||
POST,/api/tenant-content/assets/confirm-upload,legacy_only,TenantContentAssetsController_confirmUpload,确认并验证内容资产上传,,
|
||||
GET,/api/tenant-content/assets/security-scan-events,exact_match,TenantContentAssetsController_scanEvents,查询资产安全扫描事件,,查询资产安全扫描事件
|
||||
POST,/api/tenant-content/assets/sign-download,exact_match,TenantContentAssetsController_signDownload,签发管理侧资产下载地址,,签发管理侧资产下载地址
|
||||
POST,/api/tenant-content/assets/sign-preview,exact_match,TenantContentAssetsController_signPreview,签发管理侧资产预览地址,,签发管理侧资产预览地址
|
||||
POST,/api/tenant-content/assets/sign-upload,legacy_only,TenantContentAssetsController_signUpload,签发内容资产上传凭证,,
|
||||
POST,/api/tenant-content/assets/uploads/confirm,target_only,,,,确认资产上传完成
|
||||
POST,/api/tenant-content/assets/uploads/sign,target_only,,,,创建资产上传签名
|
||||
GET,/api/tenant-content/content-entries,legacy_only,ContentNavigationController_entries,查询租户内容入口,,
|
||||
PUT,/api/tenant-content/content-entries,legacy_only,ContentNavigationController_upsertEntry,新增或更新内容入口,,
|
||||
GET,/api/tenant-content/content-nodes,legacy_only,ContentNavigationController_nodes,查询内容导航节点,,
|
||||
PUT,/api/tenant-content/content-nodes,legacy_only,ContentNavigationController_upsertNode,新增或更新内容导航节点,,
|
||||
GET,/api/tenant-content/entries,target_only,,,,查询租户内容入口
|
||||
POST,/api/tenant-content/entries,target_only,,,,创建或更新内容入口
|
||||
GET,/api/tenant-content/exports/jobs,legacy_only,TenantContentExportsController_jobs,查询题库导出任务,,
|
||||
POST,/api/tenant-content/exports/questions,legacy_only,TenantContentExportsController_create,创建题库导出或立即生成 JSON,,
|
||||
GET,/api/tenant-content/handbook-chapters,exact_match,HandbookManagementController_chapters,查询管理侧知识手册章节,,查询管理侧知识手册章节
|
||||
PUT,/api/tenant-content/handbook-chapters,exact_match,HandbookManagementController_upsertChapter,新增或更新知识手册章节,,新增或更新知识手册章节
|
||||
GET,/api/tenant-content/handbook-entries,exact_match,HandbookManagementController_entries,查询管理侧知识手册条目,,查询管理侧知识手册条目
|
||||
PUT,/api/tenant-content/handbook-entries,exact_match,HandbookManagementController_upsertEntry,新增或更新知识手册条目,,新增或更新知识手册条目
|
||||
GET,/api/tenant-content/handbook-subjects,exact_match,HandbookManagementController_subjects,查询管理侧知识手册科目,,查询管理侧知识手册科目
|
||||
PUT,/api/tenant-content/handbook-subjects,exact_match,HandbookManagementController_upsertSubject,新增或更新知识手册科目,,新增或更新知识手册科目
|
||||
GET,/api/tenant-content/import-jobs,target_only,,,,查询内容导入任务
|
||||
GET,/api/tenant-content/import-jobs/{jobId},target_only,,,,查询内容导入任务详情
|
||||
GET,/api/tenant-content/imports,legacy_only,TenantContentImportsController_jobs,查询内容导入任务,,
|
||||
GET,/api/tenant-content/imports/detail,exact_match,TenantContentImportsController_detail,查询内容导入任务详情,,查询内容导入任务详情
|
||||
GET,/api/tenant-content/imports/field-mapping,exact_match,TenantContentImportsController_fieldMapping,查询导入字段映射说明,,查询导入字段映射
|
||||
POST,/api/tenant-content/imports/handbook,legacy_only,TenantContentImportsController_importHandbook,执行或排队知识手册导入,,由 /api/tenant-content/imports/{importType} 替代
|
||||
GET,/api/tenant-content/imports/issues,exact_match,TenantContentImportsController_issues,查询内容导入问题明细,,查询内容导入问题明细
|
||||
GET,/api/tenant-content/imports/post-check,exact_match,TenantContentImportsController_postCheckStatus,查询内容导入后检查状态,,查询内容导入后检查状态
|
||||
POST,/api/tenant-content/imports/post-check,exact_match,TenantContentImportsController_runPostCheck,执行内容导入后完整性检查,,执行内容导入后完整性检查
|
||||
POST,/api/tenant-content/imports/preview/handbook,legacy_only,TenantContentImportsController_previewHandbook,预览知识手册导入数据,,由 /api/tenant-content/imports/preview/{importType} 替代
|
||||
POST,/api/tenant-content/imports/preview/questions,legacy_only,TenantContentImportsController_previewQuestions,预览题目导入数据,,由 /api/tenant-content/imports/preview/{importType} 替代
|
||||
POST,/api/tenant-content/imports/preview/scoreline,legacy_only,TenantContentImportsController_previewScoreline,预览分数线导入数据,,由 /api/tenant-content/imports/preview/{importType} 替代
|
||||
POST,/api/tenant-content/imports/preview/videos,legacy_only,TenantContentImportsController_previewVideos,预览视频解析导入数据,,由 /api/tenant-content/imports/preview/{importType} 替代
|
||||
POST,/api/tenant-content/imports/preview/vocabulary,legacy_only,TenantContentImportsController_previewVocabulary,预览词汇导入数据,,由 /api/tenant-content/imports/preview/{importType} 替代
|
||||
POST,/api/tenant-content/imports/preview/{importType},target_only,,,,预览内容导入数据
|
||||
POST,/api/tenant-content/imports/questions,legacy_only,TenantContentImportsController_importQuestions,执行或排队题目导入,,由 /api/tenant-content/imports/{importType} 替代
|
||||
POST,/api/tenant-content/imports/scoreline,legacy_only,TenantContentImportsController_importScoreline,执行或排队分数线导入,,由 /api/tenant-content/imports/{importType} 替代
|
||||
GET,/api/tenant-content/imports/templates,exact_match,TenantContentImportsController_template,获取内容导入模板,,获取内容导入模板
|
||||
POST,/api/tenant-content/imports/videos,legacy_only,TenantContentImportsController_importVideos,执行或排队视频解析导入,,由 /api/tenant-content/imports/{importType} 替代
|
||||
POST,/api/tenant-content/imports/vocabulary,legacy_only,TenantContentImportsController_importVocabulary,执行或排队词汇导入,,由 /api/tenant-content/imports/{importType} 替代
|
||||
POST,/api/tenant-content/imports/{importType},target_only,,,,执行同步或异步内容导入
|
||||
GET,/api/tenant-content/media-analytics/asset-events,legacy_only,TenantContentAssetsController_assetAnalytics,查询媒体资产访问明细,,
|
||||
GET,/api/tenant-content/media-analytics/summary,legacy_only,TenantContentAssetsController_summary,查询媒体访问分析汇总,,
|
||||
GET,/api/tenant-content/media-analytics/video-events,legacy_only,TenantContentAssetsController_videoAnalytics,查询媒体视频播放明细,,
|
||||
GET,/api/tenant-content/nodes,target_only,,,,查询租户内容节点
|
||||
POST,/api/tenant-content/nodes,target_only,,,,创建或更新内容节点
|
||||
GET,/api/tenant-content/notifications,legacy_only,TenantContentPublicBanksController_notifications,查询租户内容通知,,
|
||||
POST,/api/tenant-content/notifications/status,legacy_only,TenantContentPublicBanksController_updateNotifications,批量更新租户内容通知状态,,
|
||||
GET,/api/tenant-content/operations/{kind},target_only,,,,查询运营内容
|
||||
PUT,/api/tenant-content/operations/{kind},target_only,,,,新增或更新运营内容
|
||||
GET,/api/tenant-content/practice-blueprints,exact_match,ContentNavigationController_blueprints,查询练习蓝图,,查询练习蓝图
|
||||
POST,/api/tenant-content/practice-blueprints,target_only,,,,创建或更新练习蓝图
|
||||
PUT,/api/tenant-content/practice-blueprints,legacy_only,ContentNavigationController_upsertBlueprint,新增或更新练习蓝图,,
|
||||
GET,/api/tenant-content/public-question-banks,legacy_only,TenantContentPublicBanksController_list,查询可采用的公共题库,,
|
||||
POST,/api/tenant-content/public-question-banks/adopt,legacy_only,TenantContentPublicBanksController_adopt,采用公共题库并复制内容,,
|
||||
GET,/api/tenant-content/public-question-banks/conflicts,legacy_only,TenantContentPublicBanksController_conflicts,查询公共题库同步冲突,,
|
||||
POST,/api/tenant-content/public-question-banks/conflicts/resolve,legacy_only,TenantContentPublicBanksController_resolve,解决单条公共题库同步冲突,,
|
||||
POST,/api/tenant-content/public-question-banks/conflicts/resolve-batch,legacy_only,TenantContentPublicBanksController_resolveBatch,批量解决公共题库同步冲突,,
|
||||
POST,/api/tenant-content/public-question-banks/sync,legacy_only,TenantContentPublicBanksController_sync,同步已采用的公共题库,,
|
||||
GET,/api/tenant-content/question-collections,exact_match,ContentNavigationController_collections,查询题目集合,,查询租户题集
|
||||
POST,/api/tenant-content/question-collections,target_only,,,,创建或更新题集
|
||||
PUT,/api/tenant-content/question-collections,legacy_only,ContentNavigationController_upsertCollection,新增或更新题目集合,,
|
||||
PUT,/api/tenant-content/question-collections/items,legacy_only,ContentNavigationController_replaceItems,整体替换题集中的题目,,
|
||||
POST,/api/tenant-content/question-collections/items/replace,target_only,,,,替换题集题目
|
||||
POST,/api/tenant-content/question-videos,exact_match,VideoManagementController_bind,绑定题目与解析视频,,绑定题目与解析视频
|
||||
PATCH,/api/tenant-content/questions,exact_match,QuestionManagementController_update,更新题目并可选择创建新版本,,更新题目并可选择创建新版本
|
||||
POST,/api/tenant-content/questions,exact_match,QuestionManagementController_create,创建题目及首个版本,,创建题目及首个版本
|
||||
GET,/api/tenant-content/scoreline/fields,exact_match,ScorelineManagementController_fields,查询管理侧分数线字段,,查询管理侧分数线字段
|
||||
PUT,/api/tenant-content/scoreline/fields,exact_match,ScorelineManagementController_upsertField,新增或更新动态分数线字段,,新增或更新动态分数线字段
|
||||
GET,/api/tenant-content/scoreline/majors,exact_match,ScorelineManagementController_majors,查询管理侧分数线专业,,查询管理侧分数线专业
|
||||
PUT,/api/tenant-content/scoreline/majors,exact_match,ScorelineManagementController_upsertMajor,新增或更新分数线专业,,新增或更新分数线专业
|
||||
GET,/api/tenant-content/scoreline/records,exact_match,ScorelineManagementController_records,查询管理侧分数线记录,,查询管理侧分数线记录
|
||||
PUT,/api/tenant-content/scoreline/records,exact_match,ScorelineManagementController_upsertRecord,新增或更新分数线记录,,新增或更新分数线记录
|
||||
GET,/api/tenant-content/scoreline/schools,exact_match,ScorelineManagementController_schools,查询管理侧分数线院校,,查询管理侧分数线院校
|
||||
PUT,/api/tenant-content/scoreline/schools,exact_match,ScorelineManagementController_upsertSchool,新增或更新分数线院校,,新增或更新分数线院校
|
||||
GET,/api/tenant-content/scoreline/trend,target_only,,,,查询分数线趋势摘要
|
||||
GET,/api/tenant-content/scoreline/years,target_only,,,,查询分数线年份
|
||||
GET,/api/tenant-content/videos,exact_match,VideoManagementController_list,查询租户视频解析,,查询租户视频解析
|
||||
PUT,/api/tenant-content/videos,exact_match,VideoManagementController_upsert,新增或更新视频解析,,新增或更新视频解析
|
||||
GET,/api/tenant-content/vocabulary-units,exact_match,VocabularyManagementController_units,查询管理侧词汇单元,,查询管理侧词汇单元
|
||||
PUT,/api/tenant-content/vocabulary-units,exact_match,VocabularyManagementController_upsertUnit,新增或更新词汇单元,,新增或更新词汇单元
|
||||
GET,/api/tenant-content/vocabulary-words,exact_match,VocabularyManagementController_words,查询管理侧词汇,,查询管理侧词汇
|
||||
PUT,/api/tenant-content/vocabulary-words,exact_match,VocabularyManagementController_upsertWord,新增或更新词汇,,新增或更新词汇
|
||||
GET,/api/tenant/current-public,target_only,,,,获取公开租户配置
|
||||
GET,/api/tenant/resolve,exact_match,TenantController_resolve,解析当前租户,,解析当前租户
|
||||
GET,/api/tenants/current,target_only,,,,
|
||||
POST,/api/videos/play,exact_match,VideoController_play,申请视频播放地址,,申请视频播放地址
|
||||
POST,/api/videos/progress,exact_match,VideoController_progress,上报视频播放进度,,上报视频播放进度
|
||||
GET,/api/videos/search,exact_match,VideoController_search,搜索通用解析视频,,搜索通用解析视频
|
||||
GET,/health,legacy_only,HealthController_check,检查 API 与数据库健康状态,,
|
||||
|
@@ -1,35 +0,0 @@
|
||||
# 第一阶段:仓库转正基线
|
||||
|
||||
状态:已完成。
|
||||
|
||||
## 目标
|
||||
|
||||
- 确认 ASP.NET Core + EF Core + PostgreSQL 仓库为唯一目标后端。
|
||||
- 建立旧 NestJS OpenAPI 与当前 .NET OpenAPI 的机械比较基线。
|
||||
- 接入自建 Git 上游。
|
||||
|
||||
## 结果
|
||||
|
||||
- 旧 NestJS 仅作为行为、接口和迁移参考。
|
||||
- 新功能在 .NET 仓库开发。
|
||||
- 旧 URL 不要求逐字兼容。
|
||||
- API 差距记录在 `docs/migration/contracts/operation-inventory.csv`。
|
||||
|
||||
基线快照:
|
||||
|
||||
| 项 | 数量 |
|
||||
| --- | ---: |
|
||||
| 旧 NestJS OpenAPI 操作 | 342 |
|
||||
| 当前 .NET OpenAPI 操作 | 237 |
|
||||
| 仅旧 NestJS 存在 | 177 |
|
||||
| 仅当前 .NET 存在 | 72 |
|
||||
|
||||
## 验收
|
||||
|
||||
```bash
|
||||
dotnet build TIKU-BACKEND.slnx --no-restore
|
||||
dotnet test Tiku.UnitTests/Tiku.UnitTests.csproj --no-restore
|
||||
dotnet test Tiku.IntegrationTests/Tiku.IntegrationTests.csproj --no-restore
|
||||
dotnet ef migrations script --project Tiku.Infrastructure --startup-project Tiku.DbMigrator
|
||||
git diff --check
|
||||
```
|
||||
@@ -1,27 +0,0 @@
|
||||
# 第二阶段:.NET 工程底座
|
||||
|
||||
状态:已完成。
|
||||
|
||||
## 目标
|
||||
|
||||
- 建立 ASP.NET Core / EF Core / PostgreSQL 工程底座。
|
||||
- 固定数据库迁移边界:API 不自动改库,迁移由 `Tiku.DbMigrator` 执行。
|
||||
- 使用真实 PostgreSQL 验证 schema、事务、JSONB、约束和扩展。
|
||||
|
||||
## 结果
|
||||
|
||||
- Development 未配置连接串时默认连接本机 `tiku` 数据库并使用当前系统用户。
|
||||
- EF Core 使用 Npgsql 与 PostgreSQL 扩展。
|
||||
- Secret payload 进入加密字段;不提交本地连接串和密钥。
|
||||
- 真实 PostgreSQL 集成测试成为数据库能力验收入口。
|
||||
|
||||
## 验收
|
||||
|
||||
```bash
|
||||
dotnet build TIKU-BACKEND.slnx --no-restore
|
||||
dotnet test Tiku.UnitTests/Tiku.UnitTests.csproj --no-restore
|
||||
dotnet test Tiku.IntegrationTests/Tiku.IntegrationTests.csproj --no-restore
|
||||
dotnet ef migrations script --project Tiku.Infrastructure --startup-project Tiku.DbMigrator
|
||||
dotnet run --project Tiku.DbMigrator
|
||||
git diff --check
|
||||
```
|
||||
@@ -1,52 +0,0 @@
|
||||
# 第三阶段:租户隔离、共享题库与前端运行时
|
||||
|
||||
状态:已完成主线设计和实现。
|
||||
|
||||
## 目标
|
||||
|
||||
- 普通业务代码默认只能读取和写入当前租户数据。
|
||||
- 公共题库由平台主体拥有,有效租户可访问。
|
||||
- 租户私题只属于本租户。
|
||||
- 公共题和私题可混合组卷、答题、收藏、错题和统计。
|
||||
- 租户自定义域名安全解析到统一前端运行时配置。
|
||||
|
||||
## 结果
|
||||
|
||||
- `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
|
||||
```
|
||||
@@ -1,42 +0,0 @@
|
||||
# 第四阶段:外部服务解耦
|
||||
|
||||
状态:已完成。
|
||||
|
||||
## 目标
|
||||
|
||||
- 完全移除 Supabase Auth / Storage 兼容层。
|
||||
- 业务层只依赖身份、短信、对象存储、支付和通知抽象。
|
||||
- 第三方 SDK、账号、bucket、密钥和 claim 结构只出现在 Infrastructure provider 边界。
|
||||
|
||||
## 结果
|
||||
|
||||
- Provider 配置统一为 `TenantExternalProvider` + `TenantSecret`。
|
||||
- `Capability` 覆盖 Identity、ObjectStorage、Sms、Payment、Notification。
|
||||
- 同一租户内 `Capability + Provider` 唯一。
|
||||
- `ConfigPublic` 只保存公开配置;敏感字段必须进入 `TenantSecret`。
|
||||
- 删除旧 `TenantAuthProvider`、`TenantPaymentAccount` 和 Supabase storage provider 路径。
|
||||
- 阿里云 OSS、阿里云短信、微信、支付宝 SDK 只允许在 Infrastructure 使用。
|
||||
|
||||
## 接口边界
|
||||
|
||||
- `IIdentityProvider`:封装 password、sms、wechat_web、wechat_miniapp 身份解析,不签发 JWT。
|
||||
- `ISmsProvider`:只负责发送,验证码生成、哈希、频控和校验归业务服务。
|
||||
- `IObjectStorageService`:bucket/provider 从租户配置解析,业务输入不得任意覆盖。
|
||||
- `IPaymentProvider`:支付账户和密钥从统一 Provider 配置加载。
|
||||
- `INotificationProvider`:默认站内通知持久化,后续外发通道按 provider 扩展。
|
||||
|
||||
## 验收
|
||||
|
||||
- 租户 A/B Provider 配置和密钥互不读取。
|
||||
- `ConfigPublic` 拒绝 `secret`、`token`、`key`、`privateKey` 等敏感字段。
|
||||
- 资产上传不能伪造 bucket/provider。
|
||||
- 业务层不引用第三方 SDK namespace。
|
||||
- 生产代码不回流 Supabase provider 或旧专用配置表。
|
||||
|
||||
```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
|
||||
```
|
||||
@@ -1,47 +0,0 @@
|
||||
# 第五阶段:后台能力与 Worker 基座
|
||||
|
||||
状态:已完成第一轮底座实现。
|
||||
|
||||
## 目标
|
||||
|
||||
- 参考 yudao 后台能力,重建权限、菜单、审计、交易运营和 Worker 基座。
|
||||
- 微信生态统一使用 `Senparc.Weixin.*`。
|
||||
- 业务层继续只依赖 Application 接口,不直接引用第三方 SDK。
|
||||
|
||||
## 结果
|
||||
|
||||
- 微信支付切换到 `Senparc.Weixin.TenPayV3`。
|
||||
- 生产代码禁止 `SKIT.FlurlHttpClient.Wechat.*`。
|
||||
- 新增平台/租户后台权限、菜单、角色和用户角色绑定模型。
|
||||
- 菜单只控制 UI 展示,不作为 API 鉴权依据。
|
||||
- 角色、权限、菜单和用户角色绑定写操作落 `AuditLog`。
|
||||
- 租户交易运营补齐退款、对账批次、对账 issue 和事件记录。
|
||||
- 新增 `BackgroundJob` 统一任务模型。
|
||||
- Worker 基于 `Microsoft.Extensions.Hosting` + `BackgroundService`。
|
||||
- 任务处理器骨架覆盖 `content_export`、`content_import`、`asset_security_scan`、`statistics_aggregation`、`commerce_reconciliation`、`tenant_domain_recheck`。
|
||||
- PostgreSQL tenant guard 统一放入集中 SQL helper,由 migration 调用。
|
||||
|
||||
## 固定 SDK
|
||||
|
||||
- OSS:`AlibabaCloud.OSS.V2`
|
||||
- 阿里云短信:`AlibabaCloud.SDK.Dysmsapi20170525`
|
||||
- 支付宝:`AlipaySDKNet.Standard`
|
||||
- 微信公众号:`Senparc.Weixin.MP`
|
||||
- 微信小程序:`Senparc.Weixin.WxOpen`
|
||||
- 微信支付 V3:`Senparc.Weixin.TenPayV3`
|
||||
|
||||
## 验收
|
||||
|
||||
- platform token / tenant token 后台权限不能串用。
|
||||
- 高风险写操作都有审计。
|
||||
- 租户 A 不能查询或处理租户 B 交易数据。
|
||||
- Worker 必须通过 `ITenantExecutionScope` 初始化租户或 System Scope。
|
||||
- 架构扫描禁止 Supabase、SKIT 微信支付、业务层第三方 SDK、`IgnoreQueryFilters`、`FromSql`、`ExecuteSql`、直接 `NpgsqlCommand`。
|
||||
|
||||
```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
|
||||
```
|
||||
@@ -1,54 +0,0 @@
|
||||
# 第七阶段:学生端体验与内容消费闭环
|
||||
|
||||
状态:已完成。
|
||||
|
||||
## 目标
|
||||
|
||||
- 补齐学生端视频消费、Profile 签到、积分流水和内容导入异步化。
|
||||
- 不实现 AI 业务功能;AI 进入第八阶段。
|
||||
|
||||
## 结果
|
||||
|
||||
学生端视频接口:
|
||||
|
||||
- `GET /api/videos/search`
|
||||
- `POST /api/videos/play`
|
||||
- `POST /api/videos/progress`
|
||||
- `GET /api/questions/videos`
|
||||
- `POST /api/questions/videos/batch`
|
||||
|
||||
Profile 与积分接口:
|
||||
|
||||
- `POST /api/profile/check-in`
|
||||
- `GET /api/profile/score-events`
|
||||
|
||||
内容导入入口:
|
||||
|
||||
- `POST /api/tenant-content/imports/preview/{importType}`
|
||||
- `POST /api/tenant-content/imports/{importType}`
|
||||
- `GET /api/tenant-content/imports/detail`
|
||||
|
||||
## 边界
|
||||
|
||||
- 播放接口不暴露 OSS bucket、真实 object key 或 provider 细节。
|
||||
- 公共题和租户私题关联视频都必须按当前租户可见性校验。
|
||||
- 播放进度按租户、用户、视频、题目维度幂等更新。
|
||||
- 签到复用积分任务和积分流水;同一用户、同一租户、同一天只能成功一次。
|
||||
- `questions`、`vocabulary`、`handbook`、`scoreline`、`videos` 导入语义统一映射为 `importType`。
|
||||
- 大批量导入创建 `content_import` 后台任务,Worker 使用 `ITenantExecutionScope` 执行。
|
||||
- 本阶段未实现 `/api/ai/**`。SK 包和 AI provider 边界在第八阶段引入。
|
||||
|
||||
## 旧接口替代
|
||||
|
||||
- `/api/profile/activity-tasks` -> `/api/points/tasks`
|
||||
- `/api/profile/exchange-items` -> `/api/points/exchange-items`
|
||||
- `/api/profile/exchange-items/redeem` -> `/api/points/exchange-orders`
|
||||
|
||||
## 验收
|
||||
|
||||
- 租户 A 不能播放租户 B 视频。
|
||||
- 公共题和租户私题解析视频都按权限返回。
|
||||
- 播放进度重复上报不产生重复记录。
|
||||
- 每日签到同一天只能成功一次。
|
||||
- 签到、积分任务和兑换产生可查询积分流水。
|
||||
- 异步导入创建 `content_import` job,Worker 成功写入结果。
|
||||
@@ -1,61 +0,0 @@
|
||||
# 第八阶段:AI 底座与教师端对话
|
||||
|
||||
状态:基础包和边界已引入,业务接口待实现。
|
||||
|
||||
## 使用场景
|
||||
|
||||
### 租户教师 AI 对话
|
||||
|
||||
- 教师在租户后台发起对话。
|
||||
- AI 根据当前租户 Provider 配置调用模型。
|
||||
- 对话历史按租户和教师隔离保存。
|
||||
- 后续预留 function calling,但只能调用受审计的后端业务函数。
|
||||
- function 有写操作时必须复用 RBAC、DataScope、Tenant Scope 和 AuditLog。
|
||||
|
||||
### AI 审核题目反馈
|
||||
|
||||
- 输入:题目反馈、题目摘要、反馈类型、提交用户上下文摘要。
|
||||
- 输出:审核建议、风险等级、归类标签、是否建议人工复核。
|
||||
- 不做 function calling。
|
||||
- 不直接修改题目、反馈状态或用户数据。
|
||||
|
||||
## 存储模型
|
||||
|
||||
不要把 Semantic Kernel 的 `ChatHistory`、`ChatMessageContent`、`KernelContent`、tool call object graph 或 provider 原始 response 作为 EF Core 持久化模型。
|
||||
|
||||
数据库保存 provider-neutral 消息:
|
||||
|
||||
- `AiConversation`:租户、教师、标题、场景、Provider、模型、状态、metadata。
|
||||
- `AiConversationMessage`:租户、会话、序号、role、文本、tool call id/name/arguments/result summary、token、metadata。
|
||||
- `AiToolCallLog`:租户、会话、消息、函数名、输入摘要、结果、耗时、错误码。
|
||||
- `AiFeedbackReview`:租户、题目反馈、建议、风险、标签、人工复核标记、metadata。
|
||||
|
||||
运行时由 Infrastructure adapter 把规范化消息转换为 SK / OpenAI-compatible 请求。DeepSeek 等 provider 只接收目标接口允许的 `role`、`content`、`tool_calls`、`tool_call_id` 字段;SK metadata 不得原样回放给模型 API。
|
||||
|
||||
## Provider 与密钥边界
|
||||
|
||||
- AI Provider 使用 `TenantExternalProvider(capability=ai)`。
|
||||
- 租户 API Key 存 `TenantSecret`,通过 `SecretRef` 关联。
|
||||
- `ConfigPublic` 只允许 provider、model、endpoint、deployment、temperature、max token 等公开配置。
|
||||
- `ConfigPublic` 禁止 `secret`、`token`、`apiKey`、`key`、`privateKey` 等敏感字段。
|
||||
- `Microsoft.SemanticKernel` 只引用在 `Tiku.Infrastructure`。
|
||||
- `Tiku.Api`、`Tiku.Application`、`Tiku.Domain` 不直接引用 Semantic Kernel namespace。
|
||||
|
||||
## 第一批接口
|
||||
|
||||
- `POST /api/tenant-admin/ai/conversations`
|
||||
- `GET /api/tenant-admin/ai/conversations`
|
||||
- `GET /api/tenant-admin/ai/conversations/{conversationId}`
|
||||
- `POST /api/tenant-admin/ai/conversations/{conversationId}/messages`
|
||||
- `POST /api/tenant-admin/ai/question-feedback/review`
|
||||
|
||||
第一批先接 fake/local AI provider 跑通对话、日志和隔离,再接真实模型。
|
||||
|
||||
## 暂不做
|
||||
|
||||
- 学生端 AI。
|
||||
- 复杂 RAG。
|
||||
- 自动改题或自动发布题目。
|
||||
- 自动处理反馈状态。
|
||||
- 让客户端指定任意 function call。
|
||||
- 明文 API Key 配置。
|
||||
@@ -1,50 +0,0 @@
|
||||
# 第九阶段:SaaS 模块商城与租户交付闭环
|
||||
|
||||
## 目标
|
||||
|
||||
- 套餐决定租户购买的业务 Feature。
|
||||
- 角色决定员工可执行的 Permission,菜单只用于导航展示。
|
||||
- 平台 SaaS 收费与租户学生商城完全分离。
|
||||
- 平台创建租户和 Owner 后,租户可自助购买、开通并完成学生端初始化。
|
||||
|
||||
## 已完成
|
||||
|
||||
- `SaasFeature`、`PermissionModule`、`SaasOffering` 与不可变 `SaasOfferingVersion`。
|
||||
- 基础套餐、附加包、模块清单、额度定义、租户覆盖和原子用量记录。
|
||||
- `IFeatureAccessService` 统一读写状态、Feature、权限过滤和额度判断。
|
||||
- 题库、词汇、手册、视频、分数线和站点内容使用独立 `PermissionModule` 与后台权限点,单独购买、授权和生成菜单。
|
||||
- `/api/platform-admin/saas/**` 商品、订单、支付、订阅、发票、催缴和人工收款管理。
|
||||
- `/api/tenant-billing/**` 目录、幂等报价、幂等下单、支付、续费、变更、取消、用量和发票。
|
||||
- `/api/platform-billing/callbacks/{provider}` 独立平台收款回调。
|
||||
- 平台创建租户与 Owner 的事务化开户,以及 `/api/tenant-onboarding/status`。
|
||||
- runtime/UI bootstrap 返回有效 Feature、登录方式、权限、菜单、订阅和额度摘要。
|
||||
- 员工、学生、私有题、存储、导入、导出和短信额度已接入真实写路径;当前量由 Worker 定期按业务事实校准。
|
||||
- Worker 自动应用周期末降级、取消、Trial 到期、PastDue 和 Expired 状态转换,多 Worker 通过乐观并发保证幂等。
|
||||
- 旧 `ProductModule`、可变套餐、旧订阅和重复平台发票模型已删除。
|
||||
|
||||
## 数据库边界
|
||||
|
||||
- 当前仓库只有一个 greenfield `InitialSchema`。
|
||||
- 发布套餐版本、Feature 清单和额度清单不可修改。
|
||||
- 订阅基础项必须引用基础套餐;订阅项类型必须匹配 Offering 类型。
|
||||
- 订阅项可绑定产生它的 `PlatformBillingOrderItem` 价格快照。
|
||||
- 平台支付事件按租户、Provider 和 Provider Event ID 幂等。
|
||||
- 每个订阅最多一个 Active 基础套餐项和一个 Scheduled 基础套餐项,周期末原子切换。
|
||||
- 报价、订单和支付均保存租户内唯一幂等键,客户端不能提交最终价格。
|
||||
|
||||
## 验收
|
||||
|
||||
```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
|
||||
dotnet ef migrations has-pending-model-changes --project Tiku.Infrastructure --startup-project Tiku.DbMigrator
|
||||
git diff --check
|
||||
```
|
||||
|
||||
真实 PostgreSQL 测试覆盖套餐发布不可变、报价与订单幂等、人工/微信/支付宝收款、签名与金额拒绝、重复回调、跨租户订单隔离、未购模块权限拒绝、额度写路径与事实校准、订阅周期转换和 onboarding ready。
|
||||
|
||||
## 后续
|
||||
|
||||
第十阶段实现教师作业、考试、批阅和教学报告。教师 AI 对话与题目反馈审核继续独立排期。
|
||||
160
docs/operations.md
Normal file
160
docs/operations.md
Normal file
@@ -0,0 +1,160 @@
|
||||
# 配置与后台任务
|
||||
|
||||
本文列出 API、DbMigrator 和 Worker 当前实际读取的配置。敏感值应通过环境变量、Secret Manager 或部署平台密钥注入,不能提交到仓库。
|
||||
|
||||
## 进程与依赖
|
||||
|
||||
| 进程 | PostgreSQL | Redis | RabbitMQ | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `Tiku.Api` | 必需 | Development 可选;Production 必需 | Development 可选;Production 必需 | 提供 HTTP API、静态管理端、认证和 Outbox 发布 |
|
||||
| `Tiku.Worker` | 必需 | Development 可选;Production 必需 | Development 可选;Production 必需 | 消费消息并轮询后台任务 |
|
||||
| `Tiku.DbMigrator` | 必需 | 不需要 | 不需要 | 执行 Migration、内置目录 seed 和管理员引导 |
|
||||
|
||||
Development 未配置 Redis 时,安全服务使用进程内/数据库防线;未配置 RabbitMQ 时,Worker 从 PostgreSQL 处理即时和延时任务。Production 不允许这两个降级模式。
|
||||
|
||||
## 数据库
|
||||
|
||||
解析顺序:
|
||||
|
||||
1. `ConnectionStrings:Database`
|
||||
2. `DATABASE_URL`
|
||||
3. 仅 Development:`Host=localhost;Database=tiku;Username=<当前系统用户>`
|
||||
|
||||
DbMigrator 命令:
|
||||
|
||||
```bash
|
||||
dotnet run --project Tiku.DbMigrator
|
||||
|
||||
dotnet ef migrations script \
|
||||
--project Tiku.Infrastructure \
|
||||
--startup-project Tiku.DbMigrator
|
||||
|
||||
dotnet ef migrations has-pending-model-changes \
|
||||
--project Tiku.Infrastructure \
|
||||
--startup-project Tiku.DbMigrator
|
||||
```
|
||||
|
||||
Production 首次创建平台管理员必须显式执行:
|
||||
|
||||
```bash
|
||||
export TIKU_BOOTSTRAP_PLATFORM_ADMIN_EMAIL='admin@example.com'
|
||||
export TIKU_BOOTSTRAP_PLATFORM_ADMIN_PASSWORD='use-a-strong-temporary-password'
|
||||
export TIKU_BOOTSTRAP_PLATFORM_ADMIN_NAME='Platform Administrator'
|
||||
dotnet run --project Tiku.DbMigrator -- --bootstrap-platform-admin
|
||||
```
|
||||
|
||||
该命令只允许在不存在平台角色用户绑定时执行。管理员首次登录后必须改密。
|
||||
|
||||
## Redis
|
||||
|
||||
连接串读取 `ConnectionStrings:Redis` 或 `REDIS_URL`。当前用途:
|
||||
|
||||
- 密码、短信发送和短信校验的跨实例安全窗口计数;
|
||||
- 安全状态和租户 Feature 缓存失效;
|
||||
- Production 的 ASP.NET Core Output Cache。
|
||||
|
||||
Redis key 使用环境前缀;配置解析会强制 `AbortOnConnectFail=false`。Redis 不是用户、Session、权限、套餐或用量的权威数据源。
|
||||
|
||||
## RabbitMQ 与 Outbox
|
||||
|
||||
配置节:
|
||||
|
||||
```json
|
||||
{
|
||||
"RabbitMq": {
|
||||
"Host": "rabbitmq://localhost",
|
||||
"VirtualHost": "/",
|
||||
"Username": "guest",
|
||||
"Password": "guest",
|
||||
"OutboxBacklogAlertCount": 1000,
|
||||
"OutboxOldestMessageAlertSeconds": 300
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
本地可用环境变量形式覆盖,例如 `RabbitMq__Host`。Production 必须同时提供有效 Host、Username 和 Password。
|
||||
|
||||
当前消息配置:
|
||||
|
||||
- kebab-case endpoint 名称;
|
||||
- PostgreSQL EF Bus Outbox,1 秒查询间隔;
|
||||
- Consumer 端 EF inbox/outbox;
|
||||
- Consumer 单并发、prefetch 1;
|
||||
- 1、5、15 秒有限即时重试;
|
||||
- 不使用 RabbitMQ delayed-message 插件,延时任务保留在 PostgreSQL。
|
||||
|
||||
API 只发布消息,不注册 Consumer;Worker 注册 `SecurityStateChangedConsumer` 和 `BackgroundJobRequestedConsumer`。
|
||||
|
||||
## Worker 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"TenantDomains": {
|
||||
"Enabled": true,
|
||||
"PollSeconds": 60,
|
||||
"BatchSize": 50,
|
||||
"DnsJsonEndpoint": "https://cloudflare-dns.com/dns-query",
|
||||
"VerificationRecordPrefix": "_tiku-verification",
|
||||
"AllowedCnameTargets": [],
|
||||
"GatewayBaseUrl": null,
|
||||
"GatewayApiKey": null
|
||||
},
|
||||
"SaasSubscriptions": {
|
||||
"Enabled": true,
|
||||
"BatchSize": 100,
|
||||
"PastDueGraceDays": 7
|
||||
},
|
||||
"FeatureUsageReconciliation": {
|
||||
"Enabled": true,
|
||||
"BatchSize": 100,
|
||||
"IntervalMinutes": 60
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
域名只有在 `AllowedCnameTargets`、DNS JSON endpoint、Gateway URL 和 API key 配置完成后,才可能从 Pending/Failed 进入 Active。仅 DNS 验证成功不代表 TLS 已就绪。
|
||||
|
||||
后台任务状态和 `RunAfter` 存在 PostgreSQL。Worker 使用租约并发处理;同一即时任务在启用 RabbitMQ 后不会同时进入消息 Consumer 和数据库即时轮询路径。
|
||||
|
||||
## 安全与网络配置
|
||||
|
||||
Production 启动至少需要核对:
|
||||
|
||||
| 配置 | 作用 |
|
||||
| --- | --- |
|
||||
| `Security:Jwt` | issuer、audience、当前 key ID、RSA 私钥和验证公钥 |
|
||||
| `Security:DataProtection` | application name、X509 证书路径和密码 |
|
||||
| `Security:TenantSecrets` | key ID 和 Base64 编码的 32 字节 master key |
|
||||
| `Authentication:Sms` | 至少 32 字符的验证码 pepper 和频控阈值 |
|
||||
| `Tenancy:Resolution` | 正式平台 Host、可信代理、tenant code 允许路径 |
|
||||
| `AllowedHosts` | 非通配 Host allowlist |
|
||||
| `Cors` | 明确的 Origin、Header、Method 和凭据策略 |
|
||||
| `BrowserAuth:AllowedOrigins` | 允许使用 Browser Auth 的 HTTP(S) Origin |
|
||||
| `RateLimiting` | 全局和认证端点限流 |
|
||||
|
||||
可用环境变量覆盖包括:
|
||||
|
||||
- `TIKU_DATA_PROTECTION_APPLICATION_NAME`
|
||||
- `TIKU_DATA_PROTECTION_CERTIFICATE_PATH`
|
||||
- `TIKU_DATA_PROTECTION_CERTIFICATE_PASSWORD`
|
||||
- `TIKU_TENANT_SECRET_KEY_ID`
|
||||
- `TIKU_TENANT_SECRET_MASTER_KEY`
|
||||
- `TIKU_SMS_CODE_PEPPER`
|
||||
|
||||
不要在命令输出、文档、Git diff 或错误报告中粘贴这些值。
|
||||
|
||||
## 对象存储与外部 Provider
|
||||
|
||||
对象存储读取 `Storage` / `Storage:AliyunOss`,也支持 `STORAGE_*` 与 `ALIYUN_OSS_*` 环境变量。当前默认实现是阿里云 OSS,并强制租户 key 前缀、上传大小和 MIME allowlist。
|
||||
|
||||
身份、短信、支付、通知和 AI 的租户配置由业务后台写入 `TenantExternalProvider`;敏感值写入加密的 `TenantSecret`。全局默认配置不能绕过租户 Provider 状态和 Secret 边界。
|
||||
|
||||
## 健康检查与观测
|
||||
|
||||
- `GET /api/health`:轻量 liveness,只说明 API 进程可响应。
|
||||
- `GET /api/health/ready`:检查 PostgreSQL、已配置 Redis、已配置 RabbitMQ,并报告 Outbox pending、最老消息年龄和告警阈值;依赖未就绪时返回 503。
|
||||
- API 每 30 秒采样一次 Outbox backlog,并暴露 `tiku.outbox.pending` 与 `tiku.outbox.oldest_age` meter。
|
||||
- 设置 `OpenTelemetry:OtlpEndpoint` 后导出 ASP.NET Core、HTTP client 和数据库观测数据。
|
||||
- Serilog 输出结构化请求日志;数据库性能拦截器记录慢查询指标。
|
||||
|
||||
Readiness 为绿色不等于认证授权、跨租户隔离或 Broker 恢复演练已通过,发布仍需执行对应集成测试。
|
||||
@@ -1,40 +1,19 @@
|
||||
# 本地开发快速开始
|
||||
# 本地开发与运行
|
||||
|
||||
## 可选分布式依赖
|
||||
|
||||
本地单实例开发可以不配置 Redis/RabbitMQ,认证频控仍保留 PostgreSQL/进程内防线;Production 两者均为启动必填项。
|
||||
|
||||
```bash
|
||||
export ConnectionStrings__Redis='localhost:6379,abortConnect=false'
|
||||
export RabbitMq__Host='rabbitmq://localhost'
|
||||
export RabbitMq__Username='guest'
|
||||
export RabbitMq__Password='guest'
|
||||
```
|
||||
|
||||
RabbitMQ 使用 MassTransit 8.5.10 和 PostgreSQL EF Bus/Consumer Outbox。`GET /api/health` 是 liveness,`GET /api/health/ready` 检查 PostgreSQL、已配置的 Redis、RabbitMQ bus health,并返回 outbox pending、最老消息时长和阈值告警;服务健康不等于认证授权验收完成。
|
||||
官方 RabbitMQ 4.x 镜像无需安装 delayed-message 插件;不要配置 `UseDelayedRedelivery`,延时后台任务由 PostgreSQL `RunAfter` 调度。
|
||||
|
||||
本地 Broker 重启/outbox 恢复演练(仅对明确指定的测试容器执行 stop/start):
|
||||
|
||||
```bash
|
||||
TIKU_TEST_RABBITMQ=rabbitmq://localhost \
|
||||
TIKU_TEST_RABBITMQ_RESTART=1 \
|
||||
TIKU_TEST_RABBITMQ_CONTAINER=tiku-rabbitmq \
|
||||
dotnet test Tiku.IntegrationTests/Tiku.IntegrationTests.csproj \
|
||||
--filter 'FullyQualifiedName~Bus_outbox_drains_after_real_broker_restart'
|
||||
```
|
||||
|
||||
这份文档用于从全新开发环境启动 TIKU Backend、初始化 PostgreSQL,并完成平台管理员的首次登录。
|
||||
本页用于从全新开发环境启动当前 TIKU Backend。数据库迁移由 DbMigrator 执行,API 不会自动创建或更新 schema。
|
||||
|
||||
## 1. 准备环境
|
||||
|
||||
需要安装:
|
||||
必需:
|
||||
|
||||
- .NET 10 SDK;
|
||||
- PostgreSQL(当前本地开发已验证 PostgreSQL 18);
|
||||
- PostgreSQL;
|
||||
- `psql`、`createdb` 等 PostgreSQL 命令行工具。
|
||||
|
||||
确认工具可用:
|
||||
可选:
|
||||
|
||||
- Redis 7;
|
||||
- RabbitMQ 4。
|
||||
|
||||
```bash
|
||||
dotnet --version
|
||||
@@ -42,7 +21,7 @@ pg_isready -h 127.0.0.1 -p 5432
|
||||
psql --version
|
||||
```
|
||||
|
||||
## 2. 获取并还原项目
|
||||
## 2. 还原并构建
|
||||
|
||||
```bash
|
||||
git clone <repository-url> TIKU-BACKEND
|
||||
@@ -51,78 +30,95 @@ dotnet restore TIKU-BACKEND.slnx
|
||||
dotnet build TIKU-BACKEND.slnx --no-restore
|
||||
```
|
||||
|
||||
## 3. 创建本地数据库
|
||||
## 3. 创建 PostgreSQL 数据库
|
||||
|
||||
如果本机 PostgreSQL 允许当前系统用户无密码登录,可以直接执行:
|
||||
当前系统用户能本地登录 PostgreSQL 时:
|
||||
|
||||
```bash
|
||||
createdb -h 127.0.0.1 -U "$(whoami)" tiku
|
||||
```
|
||||
|
||||
Development 环境未显式配置连接串时,API 和 DbMigrator 默认使用:
|
||||
Development 未显式配置连接串时,API、DbMigrator 和设计时 EF 工具默认使用:
|
||||
|
||||
```text
|
||||
Host=localhost;Database=tiku;Username=<当前系统用户>
|
||||
```
|
||||
|
||||
如果数据库用户名、端口或认证方式不同,通过环境变量传入连接串:
|
||||
其他用户、端口或认证方式使用环境变量:
|
||||
|
||||
```bash
|
||||
export DATABASE_URL='Host=127.0.0.1;Port=5432;Database=tiku;Username=<数据库用户>;Password=<本地密码>'
|
||||
```
|
||||
|
||||
不要把包含密码的连接串写进 README、`appsettings*.json` 或提交到 Git。团队成员应各自使用环境变量、.NET Secret Manager 或受控密钥存储。
|
||||
不要把含密码的连接串写入 `appsettings*.json`、README 或 Git。
|
||||
|
||||
## 4. 执行迁移并初始化管理员
|
||||
## 4. 执行迁移和 seed
|
||||
|
||||
```bash
|
||||
ASPNETCORE_ENVIRONMENT=Development dotnet run --project Tiku.DbMigrator
|
||||
```
|
||||
|
||||
DbMigrator 会执行全部 EF Core Migration,并在全新 Development 数据库中自动创建平台超级管理员:
|
||||
DbMigrator 会:
|
||||
|
||||
1. 执行所有 EF Core Migration;
|
||||
2. seed 内置 Feature、Permission、菜单和额度目录;
|
||||
3. 在全新 Development 数据库创建平台超级管理员。
|
||||
|
||||
```text
|
||||
账号:admin@tiku.local
|
||||
密码:首次初始化时安全随机生成,只在当前终端输出一次
|
||||
密码:首次创建时随机生成,只在当前终端输出一次
|
||||
```
|
||||
|
||||
请立即保存终端显示的临时密码。重复执行 DbMigrator 是幂等的,不会重复创建管理员、重置密码或再次显示密码。
|
||||
重复运行是幂等的,不会重置密码或再次显示临时密码。首次登录必须改密;不要为了找回密码删除已有业务数据的数据库。
|
||||
|
||||
管理员首次登录后必须修改临时密码。正式密码至少 8 位,并同时包含字母和数字;平台管理员当前使用账号和密码登录,不要求绑定认证器。
|
||||
Migration 需要 `citext`、`ltree` 和 `pg_trgm` 扩展。执行迁移的 PostgreSQL 用户必须有创建扩展的权限,或由管理员预先安装。
|
||||
|
||||
普通租户用户以手机号作为账号,可以使用手机号和密码登录,也可以使用手机号和短信验证码登录。
|
||||
|
||||
如果数据库已经包含平台管理员,自动初始化会跳过。不要为了重新获取密码删除包含业务数据的数据库。
|
||||
|
||||
## 5. 启动 API 和平台后台
|
||||
## 5. 启动 API
|
||||
|
||||
```bash
|
||||
dotnet run --project Tiku.Api
|
||||
```
|
||||
|
||||
默认开发地址:
|
||||
默认 Development 入口:
|
||||
|
||||
- 平台后台:<http://localhost:5090/platform-admin/>
|
||||
- Scalar API 文档:<http://localhost:5090/scalar/v1>
|
||||
- 平台管理端:<http://localhost:5090/platform-admin/>
|
||||
- Scalar:<http://localhost:5090/scalar/v1>
|
||||
- OpenAPI JSON:<http://localhost:5090/openapi/v1.json>
|
||||
- 健康检查:<http://localhost:5090/api/health>
|
||||
- Liveness:<http://localhost:5090/api/health>
|
||||
- Readiness:<http://localhost:5090/api/health/ready>
|
||||
|
||||
平台后台默认连接同源真实 API,不会回退到 Mock 数据。当前开放的是已有后端契约的概览、租户、员工、审计和告警等页面;尚未接入真实接口的模块暂不开放。
|
||||
OpenAPI 和 Scalar 仅在 Development 映射。接口路径、输入字段、响应模型和授权要求以这里生成的文档为准。
|
||||
|
||||
## 6. 可选:启动 Worker
|
||||
## 6. 可选:启动 Redis 和 RabbitMQ
|
||||
|
||||
需要调试后台任务时,另开终端并使用相同数据库连接:
|
||||
本地单实例开发可以不配置这两个依赖。需要验证分布式安全频控、Output Cache、消息和 Outbox 时,先启动本地服务,再设置:
|
||||
|
||||
```bash
|
||||
export ConnectionStrings__Redis='localhost:6379,abortConnect=false'
|
||||
export RabbitMq__Host='rabbitmq://localhost'
|
||||
export RabbitMq__VirtualHost='/'
|
||||
export RabbitMq__Username='guest'
|
||||
export RabbitMq__Password='guest'
|
||||
```
|
||||
|
||||
RabbitMQ 使用 4.x,当前代码不依赖 delayed-message 插件。延时任务由 PostgreSQL `RunAfter` 调度。
|
||||
|
||||
## 7. 可选:启动 Worker
|
||||
|
||||
需要处理域名、订阅、用量或后台任务时,在另一个终端使用相同配置启动:
|
||||
|
||||
```bash
|
||||
dotnet run --project Tiku.Worker
|
||||
```
|
||||
|
||||
普通 API 开发不要求同时启动 Worker。
|
||||
Worker 会立即开始轮询。域名 DNS/TLS 流程只有在 `TenantDomains` 的 CNAME target 和 Gateway 配置完整后才能激活自定义域名。
|
||||
|
||||
## 7. 开发前验证
|
||||
## 8. 开发验证
|
||||
|
||||
```bash
|
||||
curl --fail http://localhost:5090/api/health
|
||||
curl --fail http://localhost:5090/api/health/ready
|
||||
|
||||
dotnet test TIKU-BACKEND.slnx --no-build
|
||||
dotnet format TIKU-BACKEND.slnx --verify-no-changes --no-restore
|
||||
dotnet ef migrations has-pending-model-changes \
|
||||
@@ -132,29 +128,33 @@ dotnet ef migrations has-pending-model-changes \
|
||||
git diff --check
|
||||
```
|
||||
|
||||
PostgreSQL 特有的 Migration、约束、事务和租户隔离行为必须使用真实 PostgreSQL 验证,不能只依赖 EF InMemory 测试。
|
||||
`Tiku.IntegrationTests` 会创建临时 PostgreSQL 数据库,验证 API、授权、迁移和租户隔离。测试账户和测试数据库只用于自动化验证。
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 连接 PostgreSQL 失败
|
||||
|
||||
先检查服务和实际登录信息:
|
||||
|
||||
```bash
|
||||
pg_isready -h 127.0.0.1 -p 5432
|
||||
psql -h 127.0.0.1 -U <数据库用户> -d postgres -c 'select current_user;'
|
||||
```
|
||||
|
||||
然后确认当前终端中的 `DATABASE_URL` 指向正确的主机、端口、数据库和用户。
|
||||
确认当前终端的 `DATABASE_URL` 指向真实存在的数据库,并且 API、DbMigrator 和 Worker 使用同一连接配置。
|
||||
|
||||
### 首次迁移无法创建扩展
|
||||
### 无法创建 PostgreSQL 扩展
|
||||
|
||||
Migration 会创建 `citext` 和 `ltree` 扩展。初始化数据库的 PostgreSQL 用户必须有安装这些扩展所需的权限;请让本地数据库管理员预先安装扩展或授予对应权限。
|
||||
请让数据库管理员安装 `citext`、`ltree`、`pg_trgm`,或授予迁移用户创建这些扩展所需的权限。
|
||||
|
||||
### 没看到管理员临时密码
|
||||
|
||||
临时密码只在全新 Development 数据库首次创建管理员时显示。如果管理员绑定已经存在,迁移会安全跳过。请使用已有管理员账号的密码恢复流程,不要在源码或文档中添加固定密码。
|
||||
临时密码只在全新 Development 数据库第一次创建管理员时显示。已有管理员时 DbMigrator 会跳过;应使用正常密码恢复流程。
|
||||
|
||||
### API 启动后出现 HTTPS 重定向警告
|
||||
### Readiness 返回 503
|
||||
|
||||
本地仅使用 HTTP profile 时可能看到无法确定 HTTPS 端口的警告,不影响 `http://localhost:5090` 的开发访问。需要验证 HTTPS 时使用项目的 `https` launch profile。
|
||||
检查响应中的 `database`、`redis.ready` 和 `rabbitMq.ready`。只配置了 Redis/RabbitMQ 连接串但服务未启动时,readiness 会按已配置依赖检查并返回 503。
|
||||
|
||||
### API 出现 HTTPS 重定向警告
|
||||
|
||||
仅使用 HTTP launch profile 时可能无法确定 HTTPS 端口,不影响 `http://localhost:5090` 的本地访问。需要验证 HTTPS 时使用项目的 `https` profile。
|
||||
|
||||
更多配置见[配置与后台任务](operations.md),安全边界见[认证、授权与租户隔离](architecture/security-and-tenancy.md)。
|
||||
|
||||
Reference in New Issue
Block a user