Appearance
资金执行模块设计理念详解
一、核心问题:为什么需要资金执行模块?
1.1 财务预记账 vs 资金执行的本质区别
财务预记账(Financial Accrual Recording):
- 本质:会计记录层面的操作
- 作用:生成会计分录、冻结账户余额、记录借贷关系
- 状态:PENDING(待确认)→ POSTED(已确认)/ CANCELLED(已取消)
- 比喻:就像你在银行柜台填写转账单,钱被冻结了,但还没有真正转出去
资金执行(Fund Execution):
- 本质:真实的资金划拨操作
- 作用:调用实际的支付通道、银行接口、清算系统,完成资金转移
- 状态:PENDING → PROCESSING → SUCCESS / FAIL
- 比喻:银行柜员拿着你的转账单,真正去执行转账操作
1.2 为什么要分离这两个模块?
传统做法(不分离):
订单创建 → 直接调用支付接口 → 成功后记账
问题:
1. 支付失败了,账没记怎么办?
2. 记账成功了,支付失败了怎么办?
3. 网络超时了,到底成功没成功?
4. 如何重试?如何补偿?
LFPay做法(分离):
订单创建 → 预记账(冻结余额)→ 资金执行 → 确认记账
优点:
1. 预记账成功 = 资金已锁定,不会超支
2. 资金执行失败 = 自动取消记账,解冻余额
3. 状态清晰,可追溯,可重试
4. 解耦业务逻辑和支付通道二、资金执行模块的架构设计
2.1 整体架构图
┌─────────────────────────────────────────────────────────────┐
│ 订单服务层 │
│ (Order Service) │
│ - 创建订单 │
│ - 生成账户明细 (t_order_account_detail) │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 财务预记账服务 │
│ (Financial Accrual Recording Service) │
│ - 生成会计分录 (PENDING状态) │
│ - 冻结账户余额 (available → frozen) │
│ - 写入流水记录 │
│ - 借贷平衡校验 │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 资金执行引擎 │
│ (Fund Execution Engine) │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 1. 执行入口治理 (Entry Gateway) │ │
│ │ - 幂等控制 (idempotent_key) │ │
│ │ - 状态管理 (INIT/READY/DISPATCHED/...) │ │
│ │ - 风控挂起 (risk_hold) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 2. 策略路由引擎 (Strategy Router) │ │
│ │ - 规则匹配 (金额/场景/风险等级) │ │
│ │ - 执行模式选择 (SYNC/ASYNC/BATCH) │ │
│ │ - 执行器选择 (WALLET/BANK/CLEARING) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 3. 任务调度器 (Task Scheduler) │ │
│ │ - 同步执行 (立即返回结果) │ │
│ │ - 异步执行 (MQ/线程池) │ │
│ │ - 批量执行 (定时任务) │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 4. 执行器层 (Executor Layer) │ │
│ │ ┌────────────┬────────────┬────────────┐ │ │
│ │ │ 钱包执行器 │ 银行执行器 │ 清算执行器 │ │ │
│ │ │ (Wallet) │ (Bank) │ (Clearing) │ │ │
│ │ └────────────┴────────────┴────────────┘ │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ 5. 补偿与重试机制 (Compensate & Retry) │ │
│ │ - 失败重试 (指数退避) │ │
│ │ - 超时补偿 │ │
│ │ - 人工介入 │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 确认记账 / 取消记账 │
│ - 成功 → confirmAccounting() → POSTED │
│ - 失败 → cancelAccounting() → CANCELLED │
└─────────────────────────────────────────────────────────────┘2.2 核心设计原则
原则1:幂等性保证
java
// 每次执行都生成唯一的幂等Key
String idempotentKey = orderNo + "_" + tradeRecordId + "_" + stage;
// 数据库唯一索引保证不会重复执行
UNIQUE KEY uq_idempotent(idempotent_key)原则2:状态机驱动
INIT (初始化)
↓
READY (准备就绪,预记账成功)
↓
DISPATCHED (已分发到执行器)
↓
EXECUTING (执行中)
↓
SUCCESS (成功) / FAIL (失败)原则3:异步解耦
同步场景:小额转账、实时到账
异步场景:大额转账、批量代付
批量场景:工资发放、分润结算三、具体执行流程详解
3.1 场景1:钱包内部转账(同步执行)
用户A向用户B转账100元
步骤1:预记账
- 生成分录:
借:用户B钱包 100元
贷:用户A钱包 100元
- 冻结用户A的100元 (available: 1000 → 900, frozen: 0 → 100)
- 状态:PENDING
步骤2:资金执行(同步)
- 入口治理:生成幂等Key,检查是否重复
- 策略路由:匹配到"钱包内部转账"规则 → WALLET执行器 + SYNC模式
- 执行器调用:
```java
walletExecutor.transfer(
fromUserId: A,
toUserId: B,
amount: 100
)
```
- 执行逻辑:
1. 扣减A的冻结余额 (frozen: 100 → 0)
2. 扣减A的总余额 (total: 1000 → 900)
3. 增加B的可用余额 (available: 500 → 600)
4. 增加B的总余额 (total: 500 → 600)
- 记录执行任务:task_no, status=SUCCESS
步骤3:确认记账
- 更新分录状态:PENDING → POSTED
- 写入流水记录:t_acct_ledger
- 完成3.2 场景2:银行提现(异步执行)
用户A提现到银行卡5000元
步骤1:预记账
- 生成分录:
借:银行账户 5000元
贷:用户A钱包 5000元
- 冻结用户A的5000元
- 状态:PENDING
步骤2:资金执行(异步)
- 入口治理:生成幂等Key
- 策略路由:匹配到"银行提现"规则 → BANK执行器 + ASYNC模式
- 任务调度:
1. 创建执行任务:t_fund_execute_task
2. 发送到MQ:fund_execute_queue
3. 立即返回:status=PENDING
- 异步消费者处理:
```java
bankExecutor.withdraw(
userId: A,
bankCard: "6222...1234",
amount: 5000
)
```
- 调用银行接口:
1. 发起代付请求
2. 等待银行回调(可能需要几分钟)
3. 更新任务状态:PROCESSING → SUCCESS
步骤3:确认记账(异步回调触发)
- 收到银行成功回调
- 更新分录状态:PENDING → POSTED
- 扣减A的余额
- 完成3.3 场景3:批量代付(批量执行)
商户给100个用户发工资
步骤1:预记账(批量)
- 生成100条分录(同一个batch_no)
- 冻结商户账户总金额
- 状态:PENDING
步骤2:资金执行(批量)
- 入口治理:生成批次记录 t_fund_execute_batch
- 策略路由:匹配到"批量代付"规则 → BANK执行器 + BATCH模式
- 任务调度:
1. 创建100个执行任务
2. 分批发送到MQ(每批10条)
3. 返回批次号
- 批量处理:
1. 调用银行批量代付接口
2. 逐笔更新任务状态
3. 失败的任务自动重试
4. 超过重试次数的进入补偿流程
步骤3:确认记账(分批确认)
- 每笔成功后更新对应分录
- 批次全部完成后更新批次状态四、为什么需要这么复杂的设计?
4.1 解决的核心问题
问题1:分布式事务
传统方案:
订单服务 → 账户服务 → 支付通道
如果支付通道失败,如何回滚账户服务的操作?
LFPay方案:
预记账(冻结)→ 资金执行 → 确认记账
失败了就取消记账,解冻余额,不需要回滚问题2:幂等性
场景:用户点击两次"提现"按钮
传统方案:可能扣款两次
LFPay方案:idempotent_key保证只执行一次问题3:可追溯性
场景:用户投诉"钱扣了但没到账"
传统方案:日志分散,难以排查
LFPay方案:
- t_acct_entry:会计分录
- t_fund_execute_task:执行任务
- t_fund_execute_compensate:补偿记录
完整的链路追踪问题4:灵活扩展
新增支付通道:
1. 实现新的Executor接口
2. 配置新的策略规则
3. 不需要修改核心代码4.2 架构优势
优势1:职责分离
财务预记账:负责会计准确性
资金执行:负责支付可靠性
各司其职,互不干扰优势2:异步解耦
订单服务不需要等待银行接口返回
用户体验更好,系统吞吐量更高优势3:容错能力
- 自动重试
- 超时补偿
- 人工介入
- 状态可恢复优势4:监控告警
- 执行成功率
- 平均执行时长
- 失败原因分析
- 补偿任务数量五、与现有代码的对应关系
5.1 已实现的部分(transactionexecution模块)
java
// 财务预记账服务
IFinancialAccrualRecording
- executePreAccounting() // 预记账
- confirmAccounting() // 确认记账
- cancelAccounting() // 取消记账
// 资金执行服务(简化版)
IFundExecution
- executeFund() // 执行资金转移
- queryExecutionStatus() // 查询执行状态
- retryExecution() // 重试执行当前实现的重点:
- ✅ 会计分录的生成和状态管理
- ✅ 预记账、确认记账、取消记账的完整流程
- ⚠️ 资金执行部分标记为TODO(需要后续实现)
5.2 需要补充的部分(资金执行引擎)
java
// 1. 执行入口治理
FundExecuteEntryService
- checkIdempotent() // 幂等检查
- updateExecutionStatus() // 状态更新
// 2. 策略路由引擎
StrategyRouterService
- matchStrategy() // 策略匹配
- selectExecutor() // 执行器选择
// 3. 执行器接口
interface FundExecutor {
ExecuteResult execute(ExecuteContext context);
}
// 4. 具体执行器
WalletExecutor implements FundExecutor
BankExecutor implements FundExecutor
ClearingExecutor implements FundExecutor
// 5. 任务调度器
TaskSchedulerService
- dispatchSyncTask() // 同步调度
- dispatchAsyncTask() // 异步调度
- dispatchBatchTask() // 批量调度
// 6. 补偿服务
CompensateService
- createCompensateRecord() // 创建补偿记录
- executeCompensate() // 执行补偿六、实施建议
6.1 第一阶段:完善财务预记账(已完成)
- ✅ 会计分录生成
- ✅ 状态流转
- ✅ 借贷平衡校验
6.2 第二阶段:实现简单执行器
java
// 先实现钱包内部转账(同步)
WalletExecutor
- 调用账户服务API
- 完成余额扣减和增加
- 返回执行结果6.3 第三阶段:引入策略路由
java
// 根据订单类型选择执行器
if (orderType == "WALLET_TRANSFER") {
return walletExecutor;
} else if (orderType == "BANK_WITHDRAW") {
return bankExecutor;
}6.4 第四阶段:完善异步和批量
java
// 引入MQ
// 引入定时任务
// 引入补偿机制七、总结
核心理念
财务预记账 = 会计层面的承诺(我承诺要转这笔钱)
资金执行 = 支付层面的兑现(我真的转了这笔钱)
确认记账 = 会计层面的确认(我确认转账成功了)为什么分离?
- 职责清晰:会计归会计,支付归支付
- 容错能力:失败了可以回滚,不会乱账
- 可追溯性:每一步都有记录
- 可扩展性:新增支付通道不影响会计逻辑
类比理解
传统做法 = 你去银行转账,柜员直接操作,成功就成功,失败就失败
LFPay做法 = 你去银行转账:
1. 柜员先填单(预记账)
2. 柜员去操作(资金执行)
3. 操作成功后盖章(确认记账)
4. 操作失败就撕单(取消记账)这样设计虽然复杂,但是安全、可靠、可追溯,这是支付系统的核心要求。