Files
quyun-v2/backend/specs/spec01-gap-analysis.md

116 lines
7.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Spec01 vs 当前实现:功能对比与后续需求规则
本文基于 `backend/specs/spec01.md`,对照当前后端实现(数据表 / service / HTTP 路由),用于:
- 快速确认“已实现/部分实现/未实现”的范围边界;
- 固化后续需求补充时需要遵循的规则与约束,避免在多租户与资金链路上走偏。
## 1. 已实现(与 spec01 对齐)
### 1.1 多租户隔离与租户成员
- **租户上下文解析**:所有租户 API 按 `tenantCode` 解析租户并写入 ctxmiddleware
- **必须为租户成员**`/t/:tenantCode/v1/*` 默认强制登录 + 必须属于租户middleware不属于租户会直接拒绝。
- **角色模型**`tenant_users.role`member/tenant_admin存在租户管理接口有 role 校验。
- **加入租户**支持邀请码加入与申请加入tenantjoin 模块)。
### 1.2 余额体系(可用 + 冻结)与账本流水
- **账户维度**`users(id)`;字段包含 `balance``balance_frozen`(全局余额,可在已加入租户间共享消费)。
- **账本流水**`tenant_ledgers` 记录每次余额变更,含:
- `type`freeze / unfreeze / debit_purchase / credit_refund 等);
- `balance_before/after``frozen_before/after` 快照;
- `idempotency_key` 唯一约束tenant+user 维度)用于幂等落账。
- **一致性**:账本落地实现包含行锁与“余额/冻结余额不得为负”的不变量校验。
### 1.3 内容、定价与权益
- **内容模型**`contents`status/visibility/preview_seconds 等)。
- **内容定价**`content_prices`price_amount + discount_* 时间窗)。
- **订单快照**:购买时将价格/折扣/内容信息写入 `orders.snapshot`,避免改价影响历史订单。
- **权益模型**`content_access(tenant_id,user_id,content_id)`;购买授予 `active`,退款置为 `revoked`
- **试看**:区分 preview/main 资源角色;`/preview` 不要求购买,`/assets` 要求已购/免费/作者。
### 1.4 订单、购买、充值与退款
- **订单与明细**`orders` + `order_items`;支持 type=content_purchase 与状态流转。
- **购买(余额支付)**:支持冻结→扣款(消耗冻结)→授予权益;并发靠行锁+冻结方案防止透支。
- **购买幂等**`idempotency_key` 支持“至多一次”购买语义;失败会写回滚标记并稳定返回“失败+已回滚”。
- **充值**:已移除(不提供按租户充值能力)。
- **退款**租户管理员可对已支付订单退款默认时间窗paid_at + 24h可 force 绕过;退款入账 + 回收权益。
- **后台订单查询**支持管理员按条件分页查询与导出CSV
## 2. 部分实现 / 需要明确的差异点
### 2.1 “游客/公开内容”未落地
spec01 允许“游客/未加入租户用户浏览公开内容(若允许)”。当前实现中,`/t/:tenantCode/v1/*` 默认要求登录且必须是租户成员。
若要支持“公开内容给非成员/未登录用户访问”,需要单独的路由与中间件策略(至少绕过 `TenantRequireMember`,并重新定义 `visibility=public` 的含义)。
### 2.2 订单状态 `refunding` 未使用
spec01 给出 `refunding` 中间态建议;当前退款实现通常直接落到 `refunded`(事务内完成退款账本+权益回收+订单更新)。
若未来需要异步退款(例如接第三方支付、风控审核),应补齐 `refunding` 状态的状态机与重试/幂等规则。
### 2.3 操作者审计字段不完全结构化
spec01 建议在订单侧保留 `operator_user_id` 等结构化字段。当前实现:
- 退款操作者落在 `orders.refund_operator_user_id`
- 充值操作者主要在 `orders.snapshot`/ledger remark 中体现(结构化程度较弱)。
若后续需要强审计/报表,应明确哪些操作必须“结构化字段 + 快照”双写。
## 3. 未实现spec01 提到但系统暂缺)
### 3.1 MediaAsset 上传/处理全链路
已存在 `media_assets` 表及内容关联 `content_assets`,但目前缺少:
- 上传/回调/转码/处理状态流转接口;
- 存储签名 URL/防盗链/短时 token 下发机制;
- “preview 资源必须是独立转码产物”的生产链路约束与校验。
## 4. 后续需求“规则”(建议强制遵循)
### 4.1 多租户规则(硬约束)
- 所有新增业务表必须具备 `tenant_id`,或能从主实体可推导且在查询/写入时强校验租户边界。
- 每个 HTTP API 必须明确:是否需要登录、是否需要租户成员、是否需要 tenant_admin、是否允许跨租户访问默认禁止
### 4.2 资金/余额规则(硬约束)
- 任何会改变余额/冻结余额的行为,都必须:
- 走账本tenant_ledgers并记录 before/after
- 定义唯一幂等键策略(稳定、可重放、可查证);
- 明确事务边界与失败补偿(尤其是“冻结成功但后续失败”的回滚路径)。
- 余额不允许为负;冻结余额不允许为负;这是系统级不变量,需求不得破坏。
### 4.3 订单规则(硬约束)
- 订单必须有快照(至少包含:内容标题、定价、折扣、成交价、时间、请求 idempotency_key
- 任何“可重试”的下单/退款/充值动作必须给出幂等语义:重复请求返回同一结果,不重复扣款/入账。
- 需求必须明确:失败时是否保留订单、订单处于何种终态、以及客户端应如何重试。
### 4.4 权益与资源访问规则(硬约束)
- “是否可看正片”只取决于:免费/作者/权益content_access=active客户端表现不构成安全措施。
- 试看资源必须与正片资源隔离(不同 asset role + 不同存储对象),需求不得允许复用正片资源做试看。
- 退款后权益必须立即失效revoked并且该规则优先级高于缓存/前端展示。
### 4.5 状态机与审计规则(建议)
- 对所有引入状态的实体content/order/media_asset/tenant_user需求必须附带
- 允许的状态集合;
- 允许的状态迁移;
- 幂等行为(重复迁移是否允许、返回什么)。
- 对敏感操作(充值/退款/调账/封禁),需求必须明确:
- 操作者字段operator_user_id是否结构化落库
- 原因字段是否必填;
- 审计可检索性(按租户/用户/时间/单据维度)。
## 5. 参考实现位置(便于后续对齐)
- 数据库迁移:
- `backend/database/migrations/20251216011456_tenant_users.sql`
- `backend/database/migrations/20251217223000_media_contents.sql`
- `backend/database/migrations/20251218120000_orders_ledgers.sql`
- 中间件(租户上下文/成员校验):
- `backend/app/middlewares/tenant.go`
- `backend/app/http/tenant/routes.manual.go`
- `backend/app/http/tenant_join/routes.manual.go`
- 业务服务(核心资金与订单链路):
- `backend/app/services/ledger.go`
- `backend/app/services/order.go`
- `backend/app/services/content.go`
- HTTP 路由(对外能力清单):
- `backend/app/http/tenant/*.go`
- `backend/app/http/tenant_join/*.go`
- `backend/app/http/super/*.go`(可选:平台侧)