Documentation
¶
Overview ¶
Package wallet 提供虚拟货币/积分/库存的账本式钱包: 采用"不可变事务日志(append-only ledger) + 当前余额派生"双模型, 通过 changeset 差值更新原子校验,避免超扣/超发。
设计要点:
- users.wallet 存当前余额(快读),wallet_ledger 存只追加账本(可审计);
- 每次 ApplyWallets 在同一锁内读余额→应用 changeset→校验非负→写余额+追加账本;
- changeset 存差值而非绝对值,天然支持 "<0 即超扣" 的原子检查。
与直接维护余额的区别:账本不可变,可完整审计、支持余额回溯、防篡改; 余额是账本的派生快照,读路径 O(1)。
适用场景:游戏货币、积分、库存数量、任何"高并发增减 + 不可超扣 + 可审计"的账户。
零值不可用,用 New 构造。Wallet 并发安全。
Index ¶
- Variables
- type Account
- type Ledger
- type Wallet
- func (w *Wallet) Accounts() []string
- func (w *Wallet) Apply(ownerID string, changeset WalletMap, metadata string, now int64) (WalletMap, *Ledger, error)
- func (w *Wallet) ApplyTx(txID, ownerID string, changeset WalletMap, metadata string, now int64) (affected WalletMap, ledger *Ledger, replayed bool, err error)
- func (w *Wallet) Balance(ownerID string) WalletMap
- func (w *Wallet) LedgerByID(ownerID string, id int64) *Ledger
- func (w *Wallet) Ledgers(ownerID string) []Ledger
- func (w *Wallet) SetBalance(ownerID string, bal WalletMap)
- type WalletMap
Constants ¶
This section is empty.
Variables ¶
var ErrEmptyTxID = errors.New("wallet: empty txID")
ErrEmptyTxID ApplyTx 传入空 txID。
var ErrInsufficientBalance = errors.New("wallet: insufficient balance")
ErrInsufficientBalance 余额不足(超扣)。Changeset 不会被应用。
Functions ¶
This section is empty.
Types ¶
type Account ¶
type Account struct {
Balance WalletMap // 当前余额(只读快照)
// contains filtered or unexported fields
}
Account 是一个账户的运行时状态:当前余额(账本派生)+ 历史账本引用。
type Ledger ¶
type Ledger struct {
ID int64 // 单调递增 ID(由 Wallet 分配)
OwnerID string // 所属账户
Changeset WalletMap // 差值:正数=入账,负数=出账
Metadata string // 业务自定义备注(JSON 等任意编码)
CreateTime int64 // unix nano
}
Ledger 是一条不可变的事务记录。创建后永不修改,仅追加。
type Wallet ¶
type Wallet struct {
// contains filtered or unexported fields
}
Wallet 管理所有账户的余额与账本。
func (*Wallet) Apply ¶
func (w *Wallet) Apply(ownerID string, changeset WalletMap, metadata string, now int64) (WalletMap, *Ledger, error)
Apply 对 owner 应用一个 changeset,原子校验非负后写入余额并追加账本。 返回应用后的新余额(该 changeset 涉及的货币)。失败时余额与账本不变。
流程(参考 ApplyWallets 语义):
- 读当前余额
- 逐项应用 changeset 计算新值
- 任一项 <0 → ErrInsufficientBalance,回滚
- 写余额 + 追加账本(同一锁内原子完成)
func (*Wallet) ApplyTx ¶
func (w *Wallet) ApplyTx(txID, ownerID string, changeset WalletMap, metadata string, now int64) (affected WalletMap, ledger *Ledger, replayed bool, err error)
ApplyTx 是带幂等键的 Apply:同一 txID 重复调用只应用一次,后续调用返回首次 成功的结果(相同 affected + 同一账本)而不再改动余额。用于防止网络重试 / 客户端重发导致的重复扣款、重复发奖。
语义:
- txID 为空 → ErrEmptyTxID;
- txID 首次出现 → 等价 Apply,成功后记录结果,返回 (affected, ledger, false);
- txID 已成功过 → 不执行,返回缓存的 (affected, ledger, true);
- 首次执行失败(如余额不足)→ 不记录 txID,同 txID 下次仍可重试。
返回值 replayed 表示本次是否为重放(true=未真正扣款,复用首次结果)。 幂等索引随 Wallet 常驻内存(append-only,与账本同生命周期);若需按 TTL 淘汰,请在业务层用 pkg/idempotency 包住 ApplyTx。
func (*Wallet) LedgerByID ¶
LedgerByID 按 ID 查单条账本。不存在返回 nil。
func (*Wallet) SetBalance ¶
SetBalance 直接覆盖 owner 的余额(用于从 DB 全量加载初始快照)。 不产生账本——仅用于启动时恢复,运行时请用 Apply。