Files
olons_admin/docs/admin_reference_prd/第12章状态机与数据模型补强设计.md
T

485 lines
17 KiB
Markdown
Raw 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.
# 第 12 章状态机与数据模型补强设计
> 文档版本:V1.0
> 编制日期:2026-10-08
> 依据:[后台管理系统需求文档](./后台管理系统需求文档.md)第 12 章
> 关联设计:[后台管理系统概要设计](./后台管理系统概要设计.md)
> 适用项目:`olons_admin`
## 1. 文档目标
本文档用于补齐需求文档第 12 章中尚未闭环的状态机、资金冲正、内容归档与历史版本回滚能力,作为数据库迁移、后端接口、前端操作、权限配置和测试验收的实施依据。
本次设计遵循以下原则:
1. 复用现有 HotGo 分层、字典、审批、审计、权限和多租户能力。
2. 不修改已经投入使用的状态编号,只追加新状态或转换规则。
3. 客户端只提交业务意图,最终状态由服务端状态机决定。
4. 已生效资金不得修改或删除历史台账,只能生成反向流水冲正。
5. 历史内容和配置不得原地覆盖,回滚必须生成新的草稿版本。
6. 所有高风险执行都必须重新检查业务状态、审批状态和当前数据版本。
## 2. 实施范围
### 2.1 本期范围
- 会员状态合法转换和注销不可逆。
- 充值入账后的审批冲正。
- 任务佣金结算后的审批冲正。
- 文本内容归档和历史版本生成回滚草稿。
- 状态转换、重复执行、并发执行和资金一致性测试。
- 新增操作对应的菜单权限、业务审计和幂等保护。
### 2.2 不在本期范围
- 管理员 MFA 的真实认证流程。
- 支付渠道退款接口和外部清算实现。
- 新建独立的状态机管理页面。
- 修改 HotGo 原有管理员、角色、字典和个人设置模块。
- 直接修改 GoFrame CLI 生成的 `entity`、`do`、`dao/internal` 文件。
MFA 数据字段继续保留,但真实验证码、再认证凭证和会话升级机制后续单独设计开发。
## 3. 现状评估
| 领域 | 已有能力 | 本次缺口 |
| --- | --- | --- |
| 会员 | 正常、禁用、注销;风险状态;双人审批;状态日志 | 未限制全部非法转换,注销后仍可能被旧审批恢复 |
| 钱包 | 可用余额、冻结余额、乐观锁、不可变台账、幂等键、`reversal_of` | 充值和任务佣金尚未提供完整冲正入口 |
| 充值 | 待支付、待审核、成功、驳回、过期、取消;审核入账 | 成功后无冲正状态、审批和执行流程 |
| 任务审核 | 待审核、通过、驳回、已结算、已冲正;佣金台账字段 | 已冲正仅有字典和字段,缺少业务实现 |
| 内容 | 草稿、待审、已发布、已下线、已归档;内容版本 | 缺少归档和历史版本生成回滚草稿接口 |
| 配置 | 草稿、审批、发布、版本、回滚草稿 | 已满足本章要求,本期不重做 |
| 审计 | Trace ID、前后摘要、哈希链、审批单号 | 新增流程需要补充结构化差异和冲正关联信息 |
## 4. 状态机设计
### 4.1 通用规则
每个状态变更必须依次执行:
1. 查询目标记录并加行锁。
2. 校验记录存在且属于当前租户。
3. 校验当前状态与申请时的 `fromStatus` 一致。
4. 校验 `fromStatus → toStatus` 在允许转换表中。
5. 校验审批已通过、尚未执行,且申请人与执行人符合职责分离要求。
6. 在同一事务内完成业务状态、台账、审批执行时间和审计记录更新。
7. 事务提交后再触发缓存清理、通知或 Outbox 事件。
状态转换判断统一放在后端,不接受前端直接指定任意结果状态。
### 4.2 会员状态
现有会员状态编号保持不变:
| 常量 | 值 | 含义 |
| --- | ---: | --- |
| `AppMemberStatusNormal` | 1 | 正常 |
| `AppMemberStatusDisabled` | 2 | 禁用 |
| `AppMemberStatusCancelled` | 3 | 已注销 |
允许转换:
| 当前状态 | 目标状态 | 是否允许 | 说明 |
| --- | --- | --- | --- |
| 正常 | 禁用 | 是 | 必须审批 |
| 正常 | 注销 | 是 | 必须审批,执行后不可恢复 |
| 禁用 | 正常 | 是 | 必须审批 |
| 禁用 | 注销 | 是 | 必须审批 |
| 注销 | 任意状态 | 否 | 注销不可逆 |
| 任意状态 | 相同状态 | 否 | 防止无意义重复申请 |
需求中的“受限”继续由现有 `risk_status` 表达,不新增或重排会员主状态:
- `AppMemberRiskNormal`:正常。
- `AppMemberRiskWatch`:观察或限制部分能力。
- `AppMemberRiskBlocked`:拦截高风险能力。
发起状态审批时保存 `fromStatus`;执行时必须重新比对。如果会员状态已变化,则旧审批失效,不得覆盖最新状态。
### 4.3 充值状态
保留现有 1~6 状态,追加:
| 常量 | 值 | 含义 |
| --- | ---: | --- |
| `RechargeReversed` | 7 | 已冲正 |
核心转换:
| 当前状态 | 目标状态 | 触发方式 |
| --- | --- | --- |
| 用户未支付 | 手动审核 | 支付凭证或支付结果进入审核 |
| 用户未支付 | 已过期/已取消 | 超时任务或用户取消 |
| 手动审核 | 已入账 | 审批通过并写入入账台账 |
| 手动审核 | 手动驳回 | 审批驳回 |
| 已入账 | 已冲正 | 冲正审批通过并生成反向台账 |
`已入账 → 已冲正` 是成功充值唯一允许的后续资金状态转换。已冲正记录不得再次冲正。
### 4.4 任务审核状态
保留现有状态编号:
| 常量 | 值 | 含义 |
| --- | ---: | --- |
| `TaskSubmissionPending` | 1 | 待审核 |
| `TaskSubmissionApproved` | 2 | 已通过 |
| `TaskSubmissionRejected` | 3 | 已驳回 |
| `TaskSubmissionSettled` | 4 | 已结算 |
| `TaskSubmissionReversed` | 5 | 已冲正 |
当前佣金为同步结算时,可在同一事务中完成“待审核 → 已通过 → 已结算”,数据库最终状态为“已结算”;若以后改为异步结算,“已通过”可作为中间状态。
允许转换:
| 当前状态 | 目标状态 | 说明 |
| --- | --- | --- |
| 待审核 | 已驳回 | 保存驳回原因和是否允许重提 |
| 待审核 | 已结算 | 审核通过并成功写入佣金台账 |
| 已结算 | 已冲正 | 冲正审批通过并写入反向台账 |
| 已驳回 | 其他状态 | 禁止;重新提交需生成新提交记录 |
| 已冲正 | 其他状态 | 禁止 |
### 4.5 内容状态
使用现有 `BizPublishStatusOptions`:
```text
草稿 → 待审 → 已发布 → 已下线 → 已归档
```
规则:
- 只有草稿可以提交审核。
- 只有审批通过的版本可以发布。
- 只有已发布内容可以下线。
- 只有已下线内容可以归档。
- 已归档内容不可直接编辑、发布或恢复。
- 历史版本回滚不修改历史版本,而是复制为一个版本号更大的新草稿。
站点配置继续使用现有“历史版本 → 新回滚草稿 → 审批 → 发布”流程,不增加直接覆盖线上配置的接口。
## 5. 数据库变更
新增迁移文件:
```text
server/storage/data/migrations/20261008_009_state_machine_hardening.sql
```
### 5.1 `hg_recharge_order`
| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `reversal_approval_id` | bigint unsigned | 0 | 冲正审批单 ID |
| `reversal_ledger_id` | bigint unsigned | 0 | 冲正台账 ID |
| `reversed_at` | datetime(3) | NULL | 冲正执行时间 |
建议增加索引:
```sql
KEY `idx_recharge_reversal_approval` (`tenant_id`,`reversal_approval_id`)
```
### 5.2 `hg_task_submission`
保留已有 `reversal_approval_id`,新增:
| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `reversal_ledger_id` | bigint unsigned | 0 | 佣金冲正台账 ID |
| `reversed_at` | datetime(3) | NULL | 冲正执行时间 |
建议增加索引:
```sql
KEY `idx_task_submission_reversal` (`tenant_id`,`reversal_approval_id`)
```
### 5.3 `hg_content_version`
为了保留回滚来源,新增:
| 字段 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `base_version_id` | bigint unsigned | 0 | 新版本来源的历史版本 ID |
正常新增版本可为 0;通过回滚生成的新草稿必须保存来源版本 ID。
### 5.4 生成代码
迁移执行后,使用 GoFrame CLI 重新生成下列表对应的代码:
- `hg_recharge_order`
- `hg_task_submission`
- `hg_content_version`
生成范围包括 `entity`、`do`、`dao/internal` 和 `dao`,禁止手工修改生成文件。
## 6. 后端接口设计
### 6.1 会员状态
复用现有接口:
- `POST /appMember/statusRequest`
- `POST /appMember/statusExecute`
补强内容:
- `StatusRequest` 校验允许转换并写入申请时状态。
- `StatusExecute` 锁定会员记录后再次校验允许转换。
- 已注销会员禁止发起和执行任何恢复状态请求。
- 业务审计必须记录 `before.memberStatus` 和 `after.memberStatus`。
### 6.2 充值冲正
新增接口:
| 接口 | Method | 权限 | 说明 |
| --- | --- | --- | --- |
| `/rechargeOrder/reversalRequest` | POST | `/rechargeOrder/reversalRequest` | 发起充值冲正审批 |
| `/rechargeOrder/reversalExecute` | POST | `/rechargeOrder/reversalExecute` | 执行已通过的充值冲正 |
申请参数至少包括:
- 充值单 ID。
- 标准原因编码。
- 补充说明。
- 幂等键。
- 证据附件或证据摘要,按现有 JSON 附件格式保存。
申请前置条件:
- 充值单状态为“已入账”。
- 存在原入账台账。
- 尚未发起或执行冲正。
- 原审批和入账结果有效。
执行必须在同一事务中:
1. 锁定审批单、充值单、钱包账户和原台账。
2. 校验审批已通过且未执行。
3. 校验充值单仍为“已入账”。
4. 校验原台账属于该充值单和该钱包。
5. 生成金额相同、方向相反的新台账,并设置 `reversal_of=原台账ID`。
6. 更新钱包余额和乐观锁版本,余额不得为负数。
7. 更新充值单为“已冲正”,保存冲正审批、台账和时间。
8. 标记审批已执行并写入业务审计。
### 6.3 任务佣金冲正
新增接口:
| 接口 | Method | 权限 | 说明 |
| --- | --- | --- | --- |
| `/taskSubmission/reversalRequest` | POST | `/taskSubmission/reversalRequest` | 发起佣金冲正审批 |
| `/taskSubmission/reversalExecute` | POST | `/taskSubmission/reversalExecute` | 执行已通过的佣金冲正 |
申请前置条件:
- 提交记录状态为“已结算”。
- `ledger_id` 有效。
- 尚未发起或执行冲正。
- 必须填写标准原因和补充说明。
执行逻辑与充值冲正一致,但业务状态更新为 `TaskSubmissionReversed`,冲正流水指向佣金原流水。
### 6.4 内容归档与回滚
新增接口:
| 接口 | Method | 权限 | 说明 |
| --- | --- | --- | --- |
| `/content/archive` | POST | `/content/archive` | 将已下线内容归档 |
| `/content/rollback` | POST | `/content/rollback` | 根据历史版本生成新草稿 |
归档规则:
- 当前内容必须为“已下线”。
- 归档后不可编辑或直接重新发布。
- 保存操作人、原因和审计记录。
回滚规则:
- 指定内容 ID 和历史版本 ID。
- 历史版本必须属于该内容。
- 新版本号为当前最大版本号加 1。
- 复制历史版本正文、摘要、封面和附件信息。
- 新版本状态为“草稿”,`base_version_id` 指向来源版本。
- 不修改当前线上版本和历史版本。
## 7. 后端代码影响范围
按 HotGo 分层补充以下内容:
| 层级 | 主要文件 |
| --- | --- |
| 常量与字典 | `server/internal/consts/trade.go`、`product_task.go`、`portal.go` |
| API 契约 | `server/api/admin/rechargeorder/`、`tasksubmission/`、`portal/` |
| Controller | `server/internal/controller/admin/sys/` 对应薄控制器 |
| Service | 在 `server/internal/service/sys.go` 的现有接口中追加方法 |
| Input | `server/internal/model/input/sysin/` 对应模块文件 |
| Logic | `app_member.go`、`recharge_order.go`、`task_submission.go`、`portal.go` |
| 路由 | 复用现有模块控制器注册,无需新增一级菜单 |
| 迁移 | `20261008_009_state_machine_hardening.sql` |
状态值合法性通过后端字典校验;字段名使用 `dao.Xxx.Columns()`,禁止在 Logic 中硬编码数据库列名。
## 8. 前端设计
不增加一级菜单,只在现有页面增加操作入口。
### 8.1 充值管理
- 状态为“已入账”且拥有申请权限时显示“申请冲正”。
- 已存在冲正审批时显示审批状态,不允许重复申请。
- 审批通过且拥有执行权限时显示“执行冲正”。
- 已冲正记录显示原台账和冲正台账关联信息。
### 8.2 会员任务审核
- 状态为“已结算”时显示“申请冲正”。
- 申请弹窗包含原因字典、补充说明和证据。
- 审批通过后由有执行权限的管理员执行。
- 状态为“已冲正”后不再显示审核或冲正按钮。
### 8.3 文本管理
- 已下线内容显示“归档”。
- 历史版本列表显示“生成回滚草稿”。
- 回滚前明确提示“不会覆盖当前线上版本”。
- 已归档内容的编辑和发布按钮禁用。
前端继续使用 Naive UI、`BasicTable`、`BasicForm`、`BasicModal` 和 `http.request`,状态标签通过后端字典渲染。
## 9. 权限设计
新增操作权限:
```text
/rechargeOrder/reversalRequest
/rechargeOrder/reversalExecute
/taskSubmission/reversalRequest
/taskSubmission/reversalExecute
/content/archive
/content/rollback
```
权限规则:
- 申请权限和执行权限分离。
- 申请人不得审批或执行自己的高风险申请。
- 执行接口必须独立校验权限,不能只依赖前端按钮隐藏。
- 新权限通过 `hg_admin_menu` 的按钮类型记录配置,并授权给管理角色和业务角色。
## 10. 台账、幂等与审计
### 10.1 台账要求
- 原台账不可更新和删除。
- 冲正台账金额必须等于原台账金额。
- 冲正台账方向必须与原台账相反。
- `reversal_of` 必须指向原台账 ID。
- 同一原台账只能存在一条有效冲正台账。
- 钱包变更后必须满足 `after_amount = before_amount ± amount`。
- 可用余额和冻结余额不得小于 0。
### 10.2 幂等要求
- 申请接口使用调用方传入的幂等键。
- 执行接口以审批单 ID 和业务单 ID 共同保证幂等。
- 重复执行已完成审批时返回清晰业务错误,不重复写钱包和台账。
- 数据库唯一索引冲突必须转换为用户可理解的业务错误。
### 10.3 审计要求
业务审计至少记录:
- 模块和动作。
- 操作人、目标类型和目标 ID。
- 原状态和目标状态。
- 原台账 ID 和冲正台账 ID。
- 冲正金额、原因和证据摘要。
- 审批单号和 Trace ID。
- 变更前后摘要与结构化差异。
## 11. 异常与并发处理
| 场景 | 处理要求 |
| --- | --- |
| 重复点击执行 | 只允许第一次成功,后续返回“审批已执行” |
| 两个管理员同时执行 | 通过事务行锁保证只有一个事务成功 |
| 审批后业务状态变化 | 拒绝执行,要求重新发起审批 |
| 钱包余额不足以冲正 | 整体回滚,不产生台账和状态变化 |
| 原台账不存在或归属不符 | 拒绝执行并记录 Warning 日志 |
| 审批通过但事务失败 | 审批保持未执行,可排查后重试 |
| 审计写入失败 | 核心事务整体回滚 |
| 通知发送失败 | 不回滚核心事务,记录 Warning 并通过队列重试 |
## 12. 测试方案
### 12.1 状态机测试
每个状态机都要覆盖:
- 所有允许转换成功。
- 所有禁止转换失败。
- 相同状态重复申请失败。
- 注销会员恢复失败。
- 申请后状态变化导致执行失败。
- 已执行审批重复执行失败。
### 12.2 资金测试
- 充值入账冲正后余额正确。
- 任务佣金冲正后余额正确。
- 原台账保持不变。
- 冲正台账方向、金额和 `reversal_of` 正确。
- 幂等键重复提交不产生重复台账。
- 两个并发执行请求只生成一条冲正台账。
- 余额不足时业务状态、钱包和台账均不变化。
- 审计日志、审批单和台账可以相互追溯。
### 12.3 内容测试
- 只有已下线内容可以归档。
- 已归档内容不可编辑和发布。
- 历史版本回滚生成新草稿。
- 回滚不改变当前发布版本。
- 新草稿的 `base_version_id`、版本号和内容正确。
### 12.4 回归测试
- 原充值审核、提现审核和任务审核流程不受影响。
- 原会员禁用和恢复流程仍可使用。
- 原站点配置版本与回滚流程不受影响。
- 字典加载、权限菜单和审计列表正常。
## 13. 验收标准
满足以下条件后视为第 12 章补强完成:
1. 注销会员无法通过任何后台状态接口恢复。
2. 充值成功后只能通过审批冲正,不允许直接修改余额或原台账。
3. 已结算任务佣金可以通过审批冲正,且不可重复冲正。
4. 每次冲正都能从业务单追溯到原台账、冲正台账、审批单和审计记录。
5. 已下线内容可以归档,历史版本可以生成新草稿且不覆盖线上数据。
6. 所有新增接口具有后端权限校验和明确的中文业务错误。
7. 非法转换、重复执行、并发执行和余额不足测试全部通过。
8. 后端编译、前端类型检查和现有模块回归测试通过。
## 14. 推荐实施顺序
1. 新增数据库迁移并用 GoFrame CLI 重新生成模型和 DAO。
2. 补充状态字典和统一转换判断。
3. 修复会员状态申请与执行校验。
4. 实现充值冲正申请与执行。
5. 实现任务佣金冲正申请与执行。
6. 实现内容归档和历史版本回滚草稿。
7. 增加按钮权限和角色授权。
8. 增加前端操作按钮、弹窗和状态展示。
9. 完成状态机、资金并发和回归测试。