简介
本文件面向电商开发者,系统化梳理 DouPHP 系统中“用户钱包”相关的数据模型与业务流程,重点覆盖:
- 钱包账户表(dou_user_wallet):用户ID、可用余额、冻结金额、累计消费、累计推广收益等核心字段设计。
- 余额流水表(dou_money):作为权威账本,记录所有余额变动明细,包含来源/去向、动作类型、关联业务单号等。
- 充值记录(recharge):通过 dou_money.source_type/source_id 与订单/充值包入账关联,形成可追溯的充值链路。
- 提现记录表(dou_withdraw):字段设计与申请、审核、打款、驳回全流程。
- 资金安全保障、余额一致性校验、并发控制等技术方案。
项目结构与范围
围绕钱包功能,涉及的核心模块与文件包括:
- 用户钱包快照表定义与初始化逻辑
- 余额/积分流水写入与快照更新
- 提现申请、审核、打款流程
- 余额流水表的扩展字段与索引优化
- 提现状态机与历史数据迁移
graph TB
A["用户中心<br/>dou_user_wallet"] --> B["余额流水<br/>dou_money"]
C["提现记录<br/>dou_withdraw"] --> B
D["订单/充值包<br/>order / money_package"] --> B
E["支付完成事件<br/>order.paid.money"] --> B
F["管理员审核<br/>admin.withdraw.*"] --> C
核心数据模型
本节聚焦钱包相关的三张核心表:用户钱包快照表、余额流水表、提现记录表。
用户钱包快照表(dou_user_wallet)
- 作用:以 user_id 为主键的账户视图聚合缓存,提供快速读取的余额、冻结、积分、累计消费、累计推广收益等指标。
- 关键字段说明:
- id:会员ID(主键)
- money_balance:可用余额(decimal(12,2))
- money_freeze:冻结金额(decimal(12,2))
- point_balance:可用积分(int)
- point_freeze:冻结积分(int)
- total_consumption:累计消费(decimal(12,2))
- total_promote:累计推广收益(decimal(12,2))
- updated_at:更新时间
- 设计要点:
- 该表为“快照/视图”,权威账本在 dou_money/dou_point;当余额/积分发生变动时,由服务层同步更新此表,保证最终一致。
- 首次写入采用 upsert 策略,缺省值兜底,避免空行导致查询异常。
余额流水表(dou_money)
- 作用:不可篡改的权威账本,记录每一笔余额增减,支持按用户、动作、来源等多维度审计与对账。
- 关键扩展字段(来自升级脚本):
- operator_type/operator_id:操作者类型与ID(admin/user/work/system),替代旧 work_id/admin_id,便于多角色记账。
- source_type/source_id:业务来源类型与主键(如 order_pay/order_refund/recharge/withdraw),用于跨模块溯源。
- from_user_id:来源用户ID(用于分销、返利等场景)。
- ip:来源IP(兼容IPv6)。
- created_at:统一时间字段(替代 create_time)。
- 索引优化:
- idx_user(user_id, action):提升按用户+动作查询效率。
- idx_from_user(from_user_id):提升来源用户维度统计。
- idx_operator(operator_type, operator_id):提升操作者维度审计。
- idx_source(source_type, source_id):提升业务来源溯源查询。
提现记录表(dou_withdraw)
- 作用:记录用户提现申请及处理过程,支撑审核、打款、驳回等流程。
- 关键字段说明:
- withdraw_sn:提现单号(唯一)
- user_id:会员ID
- money:提现金额
- card_number/card_bank/card_name:收款银行卡信息
- card_status:收款记录标记(0未到账/1已到账)
- handle_record:处理记录
- handled_at:处理时间
- status:提现状态(字符串化:pending/approved/paid/rejected)
- created_at:申请时间
- 索引:
- 主键 id
- 唯一索引 withdraw_sn
- 复合索引 idx_user(user_id, status):提升按用户+状态的查询
架构总览
钱包系统以“流水优先、快照辅助”的设计原则构建:
- 所有余额变动必须落库到 dou_money,确保可审计、可回溯。
- dou_user_wallet 作为高性能读侧快照,由服务层在写侧同步更新。
- 提现流程通过 dou_withdraw 记录申请与处理,结合状态机管理生命周期。
- 充值入账通过 dou_money.source_type/source_id 与订单/充值包关联,形成完整资金链路。
sequenceDiagram
participant U as "用户"
participant WS as "提现服务(WalletService)"
participant M as "余额流水(dou_money)"
participant W as "提现记录(dou_withdraw)"
participant S as "用户钱包快照(dou_user_wallet)"
U->>WS : 提交提现申请
WS->>W : 创建提现记录(状态=pending)
WS->>M : 扣减余额(负数),计算新total
M-->>WS : 写入成功
WS->>S : 更新money_balance快照
WS-->>U : 返回申请成功
详细组件分析
用户钱包快照(dou_user_wallet)
- 设计目标:提供 O(1) 读取的余额/积分/累计指标,降低高并发读压力。
- 更新策略:
- 每次余额/积分变动后,调用 upsertWallet 更新快照,缺失字段使用默认值。
- 针对推广收益(direct_reward/indirect_reward)正增长时,同步累加 total_promote。
- 一致性保障:
- 以 dou_money/dou_point 为准,快照仅做加速读;若出现不一致,可通过重算任务修复。
余额流水(dou_money)
- 写入流程:
- 先以 FOR UPDATE 锁定最新 total,再计算新 total 并插入流水。
- 若 total < 0 则拒绝写入,防止透支。
- 溯源能力:
- source_type/source_id 将余额变动与具体业务单绑定(如 recharge、withdraw、order_pay)。
- operator_type/operator_id 区分操作主体(管理员、用户、系统、员工)。
- 索引优化:
- 按 user_id/action、from_user_id、operator、source 建立复合索引,提升常见查询性能。
提现流程(dou_withdraw)
- 申请阶段:
- 生成唯一提现单号,写入 dou_withdraw(status=pending)。
- 立即扣减用户余额(通过 WalletService.createMoney),并记录流水。
- 审核阶段:
- 后台服务负责列表筛选、详情组装、处理记录、卡信息解锁等。
- 状态机:
- pending → approved → paid(终态)
- pending → rejected(终态)
- 历史 tinyint 状态已迁移为字符串,保证可读性与可扩展性。
flowchart TD
Start(["开始"]) --> Apply["创建提现记录<br/>status=pending"]
Apply --> Deduct["扣减余额<br/>写入dou_money"]
Deduct --> UpdateSnapshot["更新用户钱包快照"]
UpdateSnapshot --> Review{"管理员审核"}
Review --> |通过| Approve["状态=approved"]
Review --> |驳回| Reject["状态=rejected"]
Approve --> Pay["线下打款完成"]
Pay --> Paid["状态=paid"]
Reject --> End(["结束"])
Paid --> End
充值入账(recharge)
- 充值入账通过 dou_money.source_type='recharge' 与 source_id 关联订单或充值包,形成可追溯的充值链路。
- 订单支付成功后,系统通过事件触发充值包入账,避免重复入账(基于订单号防重)。
- 余额快照同步更新,确保前端展示与后台统计一致。
依赖关系分析
- 提现服务依赖:
- WalletService:负责余额流水写入与快照更新。
- UserStatsService:获取用户总余额用于校验。
- Withdraw 模型:持久化提现记录。
- 状态机依赖:
- WithdrawStatus:定义合法状态与迁移规则,贯穿前后端展示与处理逻辑。
- 索引与查询:
- 通过复合索引提升按用户、状态、来源等维度的查询性能。
classDiagram
class WalletService {
+createMoney(...)
+upsertWallet(...)
}
class WithdrawService_Front {
+applyWithdraw(...)
+buildWithdrawApplyData(...)
}
class WithdrawService_Admin {
+showDetail(...)
}
class WithdrawStatus {
+PENDING
+APPROVED
+PAID
+REJECTED
}
WithdrawService_Front --> WalletService : "扣款/记账"
WithdrawService_Admin --> WithdrawStatus : "状态判断"
WithdrawService_Front --> WithdrawStatus : "状态展示"
性能与并发控制
- 并发安全:
- 余额写入使用 FOR UPDATE 行锁,确保在高并发下 total 计算正确,避免竞态条件。
- 索引优化:
- 针对高频查询建立复合索引(user_id/action、from_user_id、operator、source),减少全表扫描。
- 读写分离建议:
- 读侧优先使用 dou_user_wallet 快照;写侧严格走 dou_money 流水。
- 幂等与去重:
- 充值包入账基于订单号防重,避免重复入账。
- 提现单号唯一约束,防止重复申请。
资金安全与一致性校验
- 余额一致性:
- 任何余额变动必须写入 dou_money,并通过 upsertWallet 更新快照,保证最终一致。
- 若发现不一致,可通过重算任务基于流水重新计算快照。
- 提现风控:
- 申请前校验待审核笔数、金额上限、银行卡必填等。
- 申请成立即扣款,状态为 pending,等待审核。
- 状态机约束:
- 使用 WithdrawStatus 定义合法迁移,避免非法状态跳转。
- 审计与溯源:
- operator_type/operator_id、source_type/source_id 提供完整审计线索。
- 提现记录包含处理记录与处理时间,便于问题定位。
故障排查指南
- 常见问题:
- 提现申请失败:检查是否已有待审核记录、金额是否为正、是否超过可用余额。
- 余额不一致:核对 dou_money 最新流水与 dou_user_wallet 快照,必要时执行重算。
- 充值重复入账:确认 source_type/source_id 是否唯一,检查订单号防重逻辑。
- 定位方法:
- 通过 source_type/source_id 追溯到具体业务单。
- 通过 operator_type/operator_id 定位操作主体。
- 查看提现记录的 handle_record 与 handled_at,确认处理轨迹。
结论
DouPHP 的钱包体系以“流水优先、快照辅助”为核心,通过严谨的状态机与索引优化,实现了高并发下的资金安全与一致性。开发者在扩展充值、提现、分销等功能时,应遵循以下原则:
- 所有余额变动必须写入 dou_money,并同步更新 dou_user_wallet。
- 使用 source_type/source_id 进行业务溯源,确保可审计。
- 借助 WithdrawStatus 管理提现状态,避免非法跳转。
- 利用复合索引提升查询性能,结合行锁保障并发安全。