简介
本文件面向DouPHP积分系统的开发者与运营人员,聚焦积分主表与流水表的表结构设计、字段语义、索引策略、以及与用户钱包快照、订单/商城兑换等模块的关联关系。文档基于仓库中实际代码与SQL脚本进行梳理,确保内容可追溯、可落地。
项目结构
积分相关能力分布在以下位置:
- 数据定义与升级脚本:模块备份SQL与升级脚本
- 核心写入服务:统一通过钱包服务写入积分流水并同步账户快照
- 后台管理:列表查询、删除、批量操作、参数初始化
- 前台展示:会员“我的积分”页面、积分商城入口
- 国际化:动作文案、字段名等
graph TB
A["业务调用方<br/>订单/活动/分享等"] --> B["钱包服务 WalletService"]
B --> C["积分流水表 point<br/>即 dou_point"]
B --> D["用户钱包快照 user_wallet<br/>point_balance"]
E["后台 PointService"] --> C
F["前台控制器/路由"] --> C
G["订单/商品模块"] --> B
核心组件
- 积分流水表(point/dou_point):记录每次积分变动明细,包含变动值、余额、来源、操作者、时间戳等。
- 用户钱包快照(user_wallet):缓存用户当前积分余额等指标,提升读取性能。
- 钱包服务(WalletService):统一的积分写入入口,负责并发安全校验、落库与快照同步。
- 后台服务(PointService):提供积分流水分页查询、删除、批量操作及参数初始化。
- 模型层(Point/PointLog):封装常用筛选、排序与类型转换。
- 前端与API路由:暴露“我的积分”与积分商城入口。
架构总览
积分写入采用“流水+快照”的双写模式:所有积分变动先写入流水表,再更新用户钱包快照中的积分余额;读取时优先使用快照以提升性能。
sequenceDiagram
participant Biz as "业务侧"
participant WS as "钱包服务 WalletService"
participant DBP as "积分流水表 point"
participant UW as "用户钱包快照 user_wallet"
Biz->>WS : createPoint(user_id, action, point, ...)
WS->>DBP : SELECT total FROM point WHERE user_id=? ORDER BY id DESC FOR UPDATE
WS->>WS : 计算新余额 = 旧total + point
WS->>DBP : INSERT INTO point(...)
WS->>UW : upsert(point_balance=新余额)
WS-->>Biz : 返回成功/失败
详细组件分析
积分主表(dou_point / point)字段设计
说明:该表为积分流水表,记录每一次积分获取或消耗明细,同时维护累计余额用于快速展示与校验。
- id:自增主键
- user_id:会员ID(unsigned)
- operator_type:操作者类型(admin/user/work/system),默认user
- operator_id:操作者ID(unsigned),默认0
- action:操作动作标识(如 exchange/shopping/share 等)
- point:本次变动积分(可为负数)
- total:变动后的积分余额
- from:关联来源单号(字符串)
- source_type:业务来源类型(如 order_pay/sign/recharge 等)
- source_id:关联业务主键(unsigned),默认0
- remark:备注
- ip:来源IP(兼容IPv6)
- created_at:创建时间(datetime)
索引与约束
- 主键:id
- 复合索引:idx_user(user_id, action),便于按用户与动作筛选
- 复合索引:idx_source(source_type, source_id),便于按业务来源溯源
字段扩容与迁移
- point、total 由 smallint 升级为 int,避免溢出
- 补齐 operator_type/operator_id/source_type/source_id/remark/ip 列
- 时间字段统一为 created_at,废弃 create_time
用户钱包快照(user_wallet)与积分余额
- point_balance:缓存用户当前积分余额,由钱包服务在写入流水后同步更新
- 初始化逻辑:从最新流水 total 回填,保证首行存在且一致
- 读取优化:前端与后台优先读取快照,减少聚合计算
积分写入流程(并发安全与一致性)
- 使用行级锁(FOR UPDATE)锁定用户最新流水,防止并发导致余额不一致
- 计算新余额并校验非负,不满足则拒绝写入
- 插入流水后,upsert 更新 user_wallet.point_balance,保持最终一致
flowchart TD
Start(["开始"]) --> Lock["锁定用户最新流水<br/>SELECT ... FOR UPDATE"]
Lock --> Calc["计算新余额 = 旧total + point"]
Calc --> Check{"新余额 >= 0 ?"}
Check -- 否 --> Fail["返回失败"]
Check -- 是 --> Insert["INSERT 积分流水"]
Insert --> Sync["UPSERT 用户钱包快照<br/>point_balance=新余额"]
Sync --> End(["结束"])
后台积分流水管理
- 列表查询:支持按用户名(解析为user_id)、时间范围筛选,默认按id倒序
- 删除与批量删除:带审计日志记录
- 参数初始化:自动注入积分比例参数项
前台与API入口
- 前台路由:积分商城与“我的积分”页面入口
- API路由:对外暴露积分查询接口(会员侧)
与订单/商城兑换的关联
- 订单商品项展示会携带积分信息,便于结算与兑换场景联动
- 业务来源字段(source_type/source_id/from)可用于将积分流水追溯到具体订单或活动
动作与文案
- 常见动作:积分兑换、商品购买、分享奖励等
- 文案来自语言包,便于多语言扩展
依赖关系分析
- 钱包服务依赖数据库访问与配置开关(features.point)
- 后台服务依赖模型与语言包
- 前台/API路由依赖控制器与服务,间接依赖钱包服务
- 订单模块通过业务来源字段与积分流水建立关联
graph LR
Order["订单/商品模块"] --> WS["钱包服务"]
WS --> P["积分流水表 point"]
WS --> UW["用户钱包快照 user_wallet"]
Admin["后台服务"] --> P
Front["前台/AI路由"] --> P
性能考虑
- 读写分离:写入走流水表,读取优先使用 user_wallet 快照,降低聚合开销
- 并发控制:对最新流水加行锁,避免竞态条件
- 索引优化:idx_user、idx_source 覆盖高频查询路径
- 字段类型:point/total 使用 int,避免溢出与精度问题
- 时间字段:统一为 datetime,便于范围查询与统计
故障排查指南
- 功能未生效:检查配置开关 features.point 是否开启
- 余额为负:确认写入前余额校验逻辑与并发锁是否生效
- 无法溯源:核对 source_type/source_id/from 是否正确填写
- 列表加载慢:确认 idx_user、idx_source 是否存在且命中
- 快照不一致:核查 upsert 逻辑与首次初始化脚本执行结果
结论
DouPHP积分系统以“流水+快照”为核心设计,通过严格的并发控制与合理的索引策略,兼顾了数据一致性与查询性能。积分主表(point/dou_point)承载全部变动明细,配合用户钱包快照实现高效读取;通过业务来源字段与订单/活动等模块解耦关联,便于追踪与分析。建议在实际使用中严格遵循钱包服务的写入接口,确保数据一致性与可追溯性。
附录
表结构与索引一览
- 表:point(即 dou_point)
- 主键:id
- 索引:idx_user(user_id, action)、idx_source(source_type, source_id)
- 快照:user_wallet
- 关键字段:point_balance(与流水保持一致)
关键业务流程参考
- 积分写入:钱包服务 createPoint
- 后台列表:PointService buildPointListData
- 前台入口:front/route/point.php
- API入口:api/route/point.php