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