简介
本文件面向DouPHP积分系统的开发者,系统化梳理积分获取、消耗、账户管理、流水记录、过期处理、兑换规则、商城对接与统计分析等能力。重点说明订单支付后按金额比例发放积分的触发点、防重与并发保护机制、钱包快照一致性保障,以及前后端/小程序端的积分展示与查询接口。文档提供流程图与时序图,帮助快速理解数据流与控制流,并给出可落地的实现建议与排错要点。
项目结构
积分系统由“业务触发层(订单)+ 钱包服务(统一写入)+ 模块控制器/服务(前后端/小程序)+ 数据模型(流水表)”构成。关键目录与职责如下:
- 订单触发:订单状态变更时计算并发安全地发放积分
- 钱包服务:统一的积分写入入口,负责行锁、负数校验、流水插入与钱包快照同步
- 后台模块:积分流水查询、批量操作、参数种子初始化
- 前台模块:积分商城商品列表、详情、用户积分流水
- API模块:小程序端积分商城列表接口
graph TB
subgraph "订单域"
O1["OrderStatusTransition<br/>订单状态流转"]
end
subgraph "钱包域"
W1["WalletService<br/>createPoint()"]
end
subgraph "积分模块"
A1["后台 PointController/PointService"]
F1["前台 PointService(PointLog)"]
M1["API PointController"]
end
D1["数据库: point/user_wallet"]
O1 --> W1
W1 --> D1
A1 --> D1
F1 --> D1
M1 --> F1
核心组件
- 订单触发器:在订单已支付且非VIP模块时,按订单金额与配置比例计算积分,并在同一事务内以行锁+去重检查避免重复发放
- 钱包服务:统一的积分写入入口,负责读取最新total、加算、负数校验、插入流水、更新用户钱包快照
- 后台积分管理:积分流水分页查询、删除、批量操作、参数种子初始化(如point_scale)
- 前台积分商城:积分商品列表与详情、用户积分流水查看
- 小程序API:返回积分商城商品列表及分页信息
架构总览
积分系统采用“事件驱动 + 统一钱包服务”的架构。订单支付成功后触发积分发放;所有积分变动通过钱包服务写入,保证并发安全与数据一致性;前后端通过各自的服务层聚合数据并渲染或返回JSON。
sequenceDiagram
participant 订单 as "订单服务"
participant 钱包 as "钱包服务(WalletService)"
participant 数据库 as "数据库(point/user_wallet)"
participant 前端 as "前台/小程序"
订单->>钱包 : createPoint(user_id, action=shopping, point, from=order_sn)
钱包->>数据库 : SELECT total FROM point WHERE user_id FOR UPDATE
数据库-->>钱包 : total
钱包->>钱包 : 校验 total + point >= 0
钱包->>数据库 : INSERT INTO point(...)
钱包->>数据库 : UPSERT user_wallet.point_balance = total
钱包-->>订单 : 成功/失败
前端->>数据库 : 查询积分流水/商城商品
数据库-->>前端 : 列表/详情
详细组件分析
订单支付发放积分(含并发与防重)
- 触发时机:订单状态流转至已支付阶段
- 并发保护:对订单行加FOR UPDATE锁,确保回调与后台重跑不会并发重复发放
- 防重策略:先检查是否已存在该订单号对应的action=shopping记录,存在则跳过
- 积分计算:按订单金额乘以配置比例(point_scale),结果为整数积分
- 调用入口:通过钱包服务的createPoint写入流水并同步钱包快照
flowchart TD
Start(["订单已支付"]) --> Lock["锁定订单行 FOR UPDATE"]
Lock --> CheckDup{"是否存在同单号 shopping 记录?"}
CheckDup -- 是 --> Skip["跳过发放"]
CheckDup -- 否 --> Calc["计算积分 = (订单金额/100) * point_scale"]
Calc --> CallWP["调用 WalletService.createPoint"]
CallWP --> WPWrite["插入 point 流水 + 更新 user_wallet.point_balance"]
WPWrite --> End(["完成"])
Skip --> End
钱包服务:积分写入与一致性
- 功能要点
- 开关控制:仅在开启features.point时生效
- 并发安全:对point表按user_id加FOR UPDATE读最新total,防止竞态
- 负数保护:若total+point<0直接拒绝
- 原子写入:插入point流水,随后更新user_wallet.point_balance快照
- 扩展性:支持operator_type/source_type/source_id/remark/ip等审计字段,便于追踪来源
classDiagram
class WalletService {
+createPoint(user_id, action, point, from, operator_type, operator_id, source_type, source_id, remark, ip) bool
-upsertWallet(user_id, fields) void
}
class PointModel {
<<table : point>>
}
class UserWallet {
<<table : user_wallet>>
}
WalletService --> PointModel : "INSERT 流水"
WalletService --> UserWallet : "UPSERT 快照"
后台积分管理
- 功能范围
- 积分流水分页查询:支持按用户名、时间范围筛选
- 删除与批量删除:带二次确认与审计日志
- 参数种子:首次安装时创建point_scale参数项,引导到参数设置页
- 数据模型:使用后台Point模型封装过滤、排序与批量删除
sequenceDiagram
participant 管理员 as "管理员"
participant 后台控 as "PointController"
participant 后台服 as "PointService"
participant 模型 as "Point(模型)"
participant 数据库 as "数据库"
管理员->>后台控 : GET /admin/point
后台控->>后台服 : buildPointListData(username,time_start,time_end,page)
后台服->>模型 : filterByUserId/filterByTimeStart/filterByTimeEnd
模型->>数据库 : 分页查询 point
数据库-->>模型 : 结果集
模型-->>后台服 : list + pager
后台服-->>后台控 : 组装视图数据
后台控-->>管理员 : 渲染页面
前台积分商城与流水
- 积分商城列表:查询point>0的商品,分页展示,附带缩略图、描述、价格/积分等信息
- 商品详情:加载图片集、规格、收藏状态等
- 用户积分流水:按当前用户过滤,格式化action名称,分页展示
sequenceDiagram
participant 用户 as "用户"
participant 前台控 as "前台PointService"
participant 产品 as "Product(模型)"
participant 流水 as "PointLog(模型)"
participant 数据库 as "数据库"
用户->>前台控 : 请求积分商城列表
前台控->>产品 : where(point>0) -> paginate
产品->>数据库 : 查询商品
数据库-->>产品 : 商品列表
产品-->>前台控 : 列表
前台控-->>用户 : 返回商品列表
用户->>前台控 : 请求我的积分流水
前台控->>流水 : filterByUserId -> paginate
流水->>数据库 : 查询流水
数据库-->>流水 : 流水列表
流水-->>前台控 : 列表
前台控-->>用户 : 返回流水
小程序积分商城API
- 接口职责:返回积分商城商品列表与分页信息,链接转换为小程序URL
- 数据来源:复用前台PointService构建列表数据
sequenceDiagram
participant 小程序 as "小程序"
participant API控 as "API PointController"
participant 前台服 as "前台PointService"
participant 数据库 as "数据库"
小程序->>API控 : GET /api/point?page=1
API控->>前台服 : buildPointListData(page, route('point'))
前台服->>数据库 : 查询商品列表
数据库-->>前台服 : 列表
前台服-->>API控 : 列表 + pager
API控-->>小程序 : 返回JSON(标题、列表、分页、总数)
依赖关系分析
- 订单模块依赖钱包服务进行积分写入
- 钱包服务依赖数据库持久化point流水与user_wallet快照
- 后台/前台/小程序均依赖各自的PointService进行数据聚合与展示
- 模型层提供过滤、排序、分页等通用能力
graph LR
Order["订单模块"] --> Wallet["钱包服务"]
Wallet --> DB["数据库(point/user_wallet)"]
Admin["后台模块"] --> DB
Front["前台模块"] --> DB
Api["小程序API"] --> Front
性能与并发
- 并发安全
- 订单行级锁:在订单状态流转中对订单行加FOR UPDATE,避免回调与后台重跑的并发问题
- 用户行级锁:钱包服务对point表按user_id加FOR UPDATE读取最新total,避免并发写入导致的不一致
- 防刷与幂等
- 基于from(order_sn)的去重检查,确保同一订单仅发放一次
- 钱包服务对负余额进行拦截,防止超额消费
- 读写分离与缓存
- 积分流水为写多读少场景,建议对高频查询(如用户余额)引入缓存层,但需保证与钱包快照的一致性
- 索引建议
- point.user_id、point.from、point.created_at建立合适索引以提升分页与去重查询效率
- 事务边界
- 订单相关联动(积分、分销奖励)在同一事务中执行,失败回滚,保证一致性
故障排查指南
- 未发放积分
- 检查features.point是否开启
- 核对订单是否满足条件(非VIP模块、金额比例>0)
- 确认from(order_sn)是否已存在对应action=shopping记录
- 余额为负
- 检查积分扣减逻辑是否正确,确保total+point>=0
- 并发异常
- 观察是否出现重复发放或丢失,确认订单行锁与用户行锁是否生效
- 后台无法查询
- 检查用户名、时间范围过滤条件是否正确
- 确认分页参数与路由参数拼接无误
- 小程序列表为空
- 确认是否有point>0的商品,以及分页参数传递正确
结论
DouPHP积分系统通过订单触发与统一钱包服务实现了高可靠、可扩展的积分发放与消费流程。借助行锁与去重机制,系统在并发与幂等方面具备良好保障;前后端与小程序端通过清晰的服务分层提供一致的积分展示与查询能力。建议在后续迭代中完善积分过期策略、积分兑换规则引擎与报表统计能力,进一步提升运营灵活性。
附录:配置与示例路径
- 配置开关与比例
- features.point:控制积分功能开关
- param.point_scale:积分比例配置(后台参数种子会引导创建)
- 典型调用路径
- 订单支付发放积分:OrderStatusTransition.php:241-264
- 钱包写入积分:WalletService.php:69-111
- 后台积分流水查询:PointController(后台).php:60-81、PointService(后台).php:41-84
- 前台积分商城列表:PointService(前台).php:51-84
- 小程序积分商城API:PointController(API).php:43-58