- 实现账户管理改进设计文档中的所有核心功能 - 三科目余额管理 (个人余额、劳动报酬、冻结余额) - 交易状态机 (created → pending → bank_submitted → success/failed/timeout → reversed) - 三键幂等体系 (JZTxId/BankTxId/SourceKey) - 优先级扣款规则 (先个人后劳动) - 在途资金管理 (可用→在途→结转/回退) - 三账对账闭环 (总账 = 银行账 + 在途净额) - 补偿服务域 (超时检测、重试、死信队列) - 虚拟银行模拟器用于业务测试 - 完整的集成测试套件 (133 个测试全部通过) - Docker 容器化部署配置 - 前端 Vue3 + TypeScript 项目结构
15 KiB
15 KiB
账户管理逻辑问题分析
一、业务逻辑汇总表
| 业务类型 | 操作步骤 | 账户变动 | 交易成功处理 | 交易失败处理 | 存在问题 |
|---|---|---|---|---|---|
| 订单 | 1. 实时计算劳动报酬和扣费金额 2. 预先扣减账户余额 3. 发起交易 |
预先扣减:个人余额↓ | 不做调整(余额已扣) | 恢复账户余额 | ⚠️ 先扣款后交易,存在数据不一致风险 |
| 代发 | 1. 发起交易 | 无预先变动 | 个人余额↑、劳动报酬↑、冻结余额↑ | 不做任何操作 | ⚠️ 失败不处理可能导致数据丢失 |
| 代扣 | 1. 发起交易 | 无预先变动 | 个人余额↓、劳动报酬可能↓ | 未说明 | ⚠️ 缺少失败处理逻辑 |
| 购药订单 | 1. 先发起冻结 2. 审批流程 3. 根据审批结果处理 |
冻结时:劳动报酬↓、个人余额↓、冻结余额↑ | 审批通过:恢复冻结金额→发起订单交易 | 审批不通过:恢复冻结金额 | ✅ 逻辑明确 |
| 罚金 | 1. 先发起冻结 2. 审批流程 3. 根据审批结果处理 |
冻结时:劳动报酬↓、个人余额↓、冻结余额↑ | 审批通过:恢复冻结金额→发起罚金交易 | 审批不通过:恢复冻结金额 | ✅ 逻辑完整 |
| 银行余额同步-增加 | 1. 直接增加个人余额 | 个人余额↑ = 银行余额↑ | 完成 | - | ✅ 逻辑简单清晰 |
| 银行余额同步-减少 | 1. 计算需减少的金额 2. 分配减少劳动报酬和冻结余额 3. 发起交易 |
预先计算: 劳动报酬↓ = 银行余额- - 个人余额- - 冻结余额- 冻结余额↓ = 银行余额- - 个人余额- - 劳动报酬- |
更新:个人余额↓、劳动报酬↓、冻结余额↓ | 恢复:个人余额、劳动报酬、冻结余额 | ⚠️ 计算公式可能存在逻辑问题 |
二、账户余额关系
核心约束条件:
个人余额 + 劳动报酬 + 冻结余额 = 银行余额
三、业务流程图
3.1 通用交易流程图
flowchart TD
A[业务对象] --> B{需要预先扣款?}
B -->|是| C[计算扣款金额]
B -->|否| D[创建交易对象]
C --> E[扣减账户余额]
E --> D
D --> F[发起交易]
F --> G{交易结果}
G -->|成功| H{是否有预先扣款?}
G -->|失败| I{是否有预先扣款?}
H -->|是| J[余额不变化/增加]
H -->|否| K[更新账户余额]
I -->|是| L[恢复账户余额]
I -->|否| M[不做处理]
style A fill:#e1f5ff
style D fill:#fff4e1
style F fill:#ffe1f5
style G fill:#f0f0f0
3.2 订单业务流程图
flowchart TD
A[订单请求] --> B[计算劳动报酬和扣费金额]
B --> C[预先扣减账户余额]
C --> D[发起订单交易]
D --> E{交易结果}
E -->|成功| F[余额不变化已扣减]
E -->|失败| G[恢复账户余额]
style C fill:#ffe1e1
style E fill:#f0f0f0
style G fill:#ffe1e1
3.3 购药订单业务流程图
flowchart TD
A[购药订单请求] --> B[发起冻结]
B --> C[劳动报酬↓ 个人余额↓ 冻结余额↑]
C --> D[审批流程]
D --> E{审批结果}
E -->|通过| F[恢复冻结金额]
F --> G[发起订单交易]
G --> H{交易结果}
H -->|成功| I[更新账户]
H -->|失败| J[处理失败]
E -->|不通过| K[恢复冻结金额]
style K fill:#ff0000,color:#fff
style E fill:#f0f0f0
3.4 罚金业务流程图
flowchart TD
A[罚金请求] --> B[发起冻结]
B --> C[劳动报酬↓ 个人余额↓ 冻结余额↑]
C --> D[审批流程]
D --> E{审批结果}
E -->|通过| F[恢复冻结金额]
F --> G[发起罚金交易]
G --> H{交易结果}
H -->|成功| I[更新账户]
H -->|失败| J[处理失败]
E -->|不通过| K[恢复冻结金额]
style E fill:#f0f0f0
3.5 银行余额同步-减少流程图
flowchart TD
A[银行余额减少] --> B[直接减少个人余额]
B --> C[计算需减少的金额]
C --> D[劳动报酬减少 = 银行余额- - 个人余额- - 冻结余额-]
D --> E[冻结余额减少 = 银行余额- - 个人余额- - 劳动报酬-]
E --> F[发起交易]
F --> G{交易结果}
G -->|成功| H[更新: 个人余额↓ 劳动报酬↓ 冻结余额↓]
G -->|失败| I[恢复: 个人余额 劳动报酬 冻结余额]
style C fill:#ffe1e1
style D fill:#ffe1e1
style E fill:#ffe1e1
style G fill:#f0f0f0
四、系统交互时序图
sequenceDiagram
participant BO as 业务对象
participant TO as 交易对象
participant AO as 账户对象
participant ExtSys as 外部交易系统
Note over BO: 业务开始
BO->>BO: 计算扣款金额(如需)
alt 需要预先扣款
BO->>AO: 预先扣减账户余额
AO-->>BO: 扣减成功
end
BO->>TO: 创建交易对象
TO-->>BO: 交易对象创建成功
BO->>TO: 发起交易
TO->>ExtSys: 调用外部交易接口
ExtSys-->>TO: 返回交易结果
alt 交易成功
TO->>AO: 进行动账操作
AO-->>TO: 动账完成
alt 有预先扣款
Note over BO,AO: 余额已在预先扣款时处理
else 无预先扣款
AO->>AO: 更新账户余额
end
else 交易失败
alt 有预先扣款
BO->>AO: 恢复账户余额
AO-->>BO: 恢复成功
else 无预先扣款
Note over BO,AO: 不做处理
end
end
TO-->>BO: 返回交易结果
BO->>BO: 业务处理完成
五、发现的问题分析
5.1 说明
- 购药订单审批不通过的处理逻辑已澄清
- 结论:审批不通过时恢复冻结金额(与罚金一致),文档已更新
5.2 重要问题(⚠️ 建议修复)
-
订单业务先扣款后交易的时序问题
- 问题描述:订单业务在交易发起前就扣减余额,如果交易长时间处理或系统异常,可能导致数据不一致
- 影响:用户体验差(钱先扣了但订单可能失败)、数据一致性风险
- 建议:考虑使用冻结机制替代直接扣款,或者优化事务处理
-
代发和代扣业务缺少失败处理逻辑
- 问题描述:
- 代发失败不做任何操作,但可能已创建交易记录
- 代扣失败未说明如何处理
- 影响:数据不一致、对账困难
- 建议:明确失败场景下的回滚机制
- 问题描述:
-
银行余额同步减少的计算逻辑可能存在错误
- 问题描述:计算公式中可能存在循环依赖或逻辑错误
劳动报酬- = 银行余额- - 个人余额- - 冻结余额- 冻结余额- = 银行余额- - 个人余额- - 劳动报酬- - 分析:第二个公式依赖第一个公式的结果,但两个公式的顺序执行可能导致结果不准确
- 建议:重新设计计算公式,确保满足约束条件:
个人余额 + 劳动报酬 + 冻结余额 = 银行余额
- 问题描述:计算公式中可能存在循环依赖或逻辑错误
5.3 优化建议(💡 可优化)
-
业务对象和账户对象的职责不清
- 问题描述:文档提到"动账也由业务对象进行处理",这可能导致业务逻辑和账户管理职责混乱
- 建议:明确职责划分,账户对象的修改应该由账户对象自身管理,业务对象只负责业务逻辑协调
-
缺少统一的异常处理机制
- 问题描述:各业务场景的异常处理方式不一致
- 建议:制定统一的异常处理和回滚策略
-
缺少事务管理说明
- 问题描述:多步骤操作(如银行余额同步减少)未说明是否使用事务
- 建议:明确关键操作的事务边界,确保数据一致性
六、改进建议总结
- ✅ 补全购药订单审批不通过的处理逻辑(必须)
- ✅ 优化订单业务的扣款时机(建议使用冻结机制)
- ✅ 统一代发和代扣的失败处理逻辑
- ✅ 修正银行余额同步的计算公式
- ✅ 明确业务对象和账户对象的职责边界
- ✅ 建立统一的异常处理和事务管理机制
七、与《账户管理改进设计文档》的对齐评估
7.1 设计要点覆盖情况
- 三类账户与对照余额(个人余额/劳动报酬/冻结余额 与 银行余额):部分覆盖。
- 文档已声明约束关系,但缺少每次动账后的不变量校验与核对策略。
- 狱政交易对象(内部交易域):部分覆盖。
- 已有“交易对象”与“外部交易系统”,但未明确“狱政交易对象”的职责边界与与“在途余额”的关系。
- 扣款优先级(先个人余额,后劳动报酬,不足则失败):基本覆盖。
- 存量流程对订单类有预扣,但建议统一通过冻结实现以避免时序不一致。
- 在途余额机制:未覆盖/不清晰。
- 当前仅有“预先扣减”与“发起交易”,缺乏在途余额账面隔离、失败/超时/退回的对账闭环。
- 交易结果处理(成功/失败/超时):部分覆盖。
- 成功/失败均有描述,超时场景仅有笼统说明,缺少重试、补偿与人工干预流程。
- 银行退回(对方银行冲退):未覆盖。
- 需要明确:原路退回与业务确认后冲正的分支,以及自动化/人工处理的触发条件。
- 外部入账作为来源(无我方唯一流水):未覆盖。
- 需定义来源幂等键(如银行对账单流水+金额+时间窗)与异常对账策略。
- 冻结/解冻不发起实际银行交易:基本覆盖。
- 购药、罚金场景已有冻结/解冻,但需抽象成通用能力并统一失败回滚策略。
- 余额充足性预检查:部分覆盖。
- 有检查思想,但未统一为账户域的原子校验与幂等接口。
7.2 结论
现有《账户管理逻辑问题分析》对“改进设计”的多数目标给出了雏形,但以下关键项仍需补齐:在途余额域模型、狱政交易对象职责、超时/退回/外部入账的闭环与幂等、统一失败回滚与事务边界、不变量校验与对账机制、银行余额同步减少的正确计算方法。
八、未解决问题与修复建议(落地项)
-
在途余额与狱政交易对象的职责边界(必须)
- 建议:在账户域引入
在途余额账本,狱政交易创建即将金额从可用余额划转至在途;银行成功后从在途转出(或转回)。失败/取消则在途回退至可用。
- 建议:在账户域引入
-
超时处理与补偿策略(必须)
- 建议:定义状态机(created→pending→bank_submitted→success/failed/timeout→reversed)。超时进入
timeout并触发重试/人工审核队列;所有操作需幂等键保护(交易号/对账单流水)。
- 建议:定义状态机(created→pending→bank_submitted→success/failed/timeout→reversed)。超时进入
-
银行退回(冲退)闭环(必须)
- 建议:新增“银行退回通知/对账识别”入口,定位原交易,若已记账成功则生成逆向狱政交易进行冲正;若仍在途则直接回退在途并关闭原交易。
-
外部入账来源与幂等(必须)
- 建议:允许“无我方唯一流水”的入账以“来源幂等键”(银行流水号+金额+记账日+对方户名等)落账;发现重复则幂等返回;与银行日终对账校验差异。
-
统一失败处理与回滚(必须)
- 建议:冻结/解冻/出入账/在途划转全部纳入事务;失败分两类:银行未受理(本地回滚),银行已受理(进入在途等待对账/退回)。
-
银行余额同步减少公式修正(必须)
- 问题:现有“劳动报酬- = 银行余额- - 个人余额- - 冻结余额-;冻结余额- = 银行余额- - 个人余额- - 劳动报酬-”存在循环依赖。
- 建议:以“银行差额Δ = 银行余额变化-(负值为减少)”为驱动,按既定优先级从可用部分(个人余额→劳动报酬)与冻结余额分桶扣减,确保每一步后不变量成立;严格禁止相互引用的代数解,改为确定性分配流程。
-
账户不变量校验与对账(必须)
- 建议:每次动账后校验
个人余额 + 劳动报酬 + 冻结余额 = 银行余额(账面);引入日终对账(银行账、在途账、总账三方核对)。
- 建议:每次动账后校验
-
幂等与唯一键设计(必须)
- 建议:狱政交易号
JZTxId、银行交易号BankTxId、来源幂等键SourceKey三键体系;所有写操作均需提供幂等键。
- 建议:狱政交易号
九、建议的状态机(文字版)
- 交易状态:
created → pending → bank_submitted → success | failed | timeout → reversed(可选) - 关键转移:
- created→pending:狱政交易创建,资金从可用划至在途
- pending→bank_submitted:调用银行接口
- bank_submitted→success:银行成功,记账从在途转出
- bank_submitted→failed:银行失败,在途回退
- bank_submitted→timeout:未回执/长时卡顿,进入补偿
- success→reversed:银行退回/业务冲正,生成逆向交易
十、执行优先级建议
- 在途余额与状态机落地;2) 统一失败/超时/退回流程;3) 外部入账幂等与对账;4) 银行同步减少分配流程改造;5) 不变量与事务/幂等全链路加固。
十一、对账与异常处理落地方案
11.1 三账对齐与差异闭环
- 三账:银行账、在途账、总账。
- 目标:
总账 = 银行账 + 在途净额;所有差异可解释并可关闭。
flowchart TD
A[日终获取银行对账单] --> B[计算银行账汇总]
B --> C[计算总账汇总]
C --> D[计算在途净额]
D --> E{等式是否成立?}
E -- 是 --> F[对账通过]
E -- 否 --> G[差异分类]
G --> H[自动处理队列]
G --> I[人工复核队列]
H --> J[自动冲正/回退/补记]
I --> K[人工结论执行]
11.2 差异分类与处理策略
- 短款(银行少/本地多):多因银行未落账或我们过早确认。
- 处理:等待回执/重试;超时则回退在途或生成纠错交易。
- 长款(银行多/本地少):多因外部入账未识别或重复入账。
- 处理:按来源幂等键补记来源型交易;重复则逆向冲正。
- 在途超时:长时间无回执。
- 处理:转
timeout补偿;达到阈值转人工。
- 处理:转
11.3 异常处理分类
- 可重试:网络/5xx/通道抖动 → 指数退避+幂等。
- 不可重试:4xx/余额不足/幂等冲突 → 直接失败或回退在途。
- 半确定:银行已受理未知 → 在途等待对账确认。
flowchart TD
A[交易发起] --> B{返回}
B -- 成功 --> C[在途结转为成功]
B -- 4xx失败 --> D[回退在途/失败]
B -- 5xx失败 --> E[重试+幂等]
B -- 超时 --> F[标记timeout 进补偿]
F --> G[对账识别后转成功/失败]
11.4 退回/冲正闭环
flowchart TD
A[银行退回通知/对账发现] --> B{原交易状态}
B -- success --> C[生成逆向交易reversed]
C --> D[余额回补/恢复冻结]
B -- 在途 --> E[直接在途回退]
E --> F[关闭原交易]