Skip to content

Ledger 余额资源 ​

存的是「还剩多少」,不是「钱怎么变成这个数」。


职责 ​

负责不负责
余额账户(当前值 + 版本)账本(分录 → 余额推导,见 Accounting)
原子扣减(不超卖)流水对账
幂等变动(movementId)多币种
防负余额分布式事务

它不是账本。 账本回答「这笔钱怎么变成这个数」(可复算、可审计); 余额资源回答「现在还剩多少」(O(1) 读、原子扣)。两者不要合并。


数据模型 ​

集合内容约束
ledger_accounts余额 + version + updatedBypath 唯一
ledger_movements变动流水movementId 唯一;pending → done / failed

路径:/ledger/{accountType}/{accountId},如 /ledger/stock/M-001。


三个保证 ​

原子扣减 ​

typescript
findOneAndUpdate(
  { path, balance: { $gte: amount } },   // 条件与自增在同一次原子操作里
  { $inc: { balance: -amount, version: 1 } },
)

并发验证:余额 50,10 个并发各扣 10 ⇒ 恰好 5 笔成功,余额 0。

幂等 ​

movementId 唯一索引先占位(pending),扣减成功后回填 done。 重复提交返回已有结果(duplicate: true),不会重复扣。

防负 ​

默认拒绝扣成负数(409);allowNegative 需显式开启。


接口 ​

MethodPath说明
GET/api/ledger/:accountType/:accountId读余额
POST/api/ledger/:accountType/:accountId/open开户(幂等)
POST/api/ledger/:accountType/:accountId/debit出账
POST/api/ledger/:accountType/:accountId/credit入账
GET/api/ledger/:accountType/:accountId/history变动流水

业务扣减务必带 movementId(幂等键),否则重试会重复扣。


边界 ​

  • 未做量测:是否真的比事件流更合适,没有真实数据支撑
  • 当前状态:已实现、未验证、默认不启用
  • 无流水归档策略;movementId 永久占用
  • 单币种整数余额

源码 ​

libs/modules/src/state/
├── schemas/ledger.schema.ts        账户 + 流水
├── services/ledger.store.ts        open / balance / credit / debit
└── ledger.controller.ts            5 个端点

Released under the MIT License.