485 lines
17 KiB
Markdown
485 lines
17 KiB
Markdown
# 第 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. 完成状态机、资金并发和回归测试。
|
||
|