ThinkPHP6.x积分商城闭环需统一积分账户、隔离实物与虚拟商品履约、保障兑换幂等性、事件驱动解耦;通过user_points与points_log表管理积分,SELECT...FOR UPDATE防超兑,goods表区分类型,exchange_rule配置化,状态机控制兑换流程,voucher_pool实现券码原子发放与核销。

ThinkPHP6.x 实现积分商城的完整闭环,核心在于统一积分账户、隔离实物与虚拟商品的履约逻辑、保障兑换幂等性,并通过事件驱动解耦业务流程。下面从关键模块出发,给出可落地的设计方案。
积分账户与流水管理
使用独立数据表 user_points 记录用户积分余额和冻结积分(用于防并发超兑),避免直接在用户主表中存储积分字段。每次积分变动必须通过流水表 points_log 记录明细,含字段:user_id、change_amount(正为增加,负为扣除)、type(如“login”、“order_pay”、“exchange”)、related_id(关联订单或兑换记录ID)、status(success/pending/failed)。
关键建议:
- 所有积分增减操作必须走事务,先写日志再更新余额,失败则回滚
- 兑换前校验时,用
SELECT ... FOR UPDATE锁定用户积分行,防止超兑 - 冻结积分用于“下单未支付”场景:用户提交兑换申请后冻结对应积分,支付成功再扣减,取消则解冻
商品与兑换规则分离设计
实物商品和虚拟券应共用同一张商品表 goods,但通过 goods_type 字段区分(如 physical / virtual),并在 goods_ext 扩展表中按类型存差异化字段:
立即学习“PHP免费学习笔记(深入)”;
-
实物类:需存
stock(库存)、weight、express_template_id、require_address(是否需要收货地址) -
虚拟类:存
code_pool_id(券码池ID)、valid_days(有效期天数)、auto_send(是否自动发码)
兑换规则不硬编码,而是配置在 exchange_rule 表中,支持按商品、用户等级、时间段设置不同所需积分,便于运营灵活调整。
兑换流程与状态机控制
兑换不是简单扣分发货,而是一套带状态迁移的流程:待提交 → 待支付 → 已支付 → 配货中 → 已发货(实物)/已发码(虚拟)→ 完成。状态变更全部由服务层控制,禁止前端直传状态。
典型处理逻辑:
- 用户提交兑换 → 创建
exchange_order记录,状态为pending,冻结积分 - 调用支付网关(如微信JSAPI)→ 支付成功回调中将状态改为
paid,触发履约任务 - 履约任务(可用 think-swoole 或定时器触发)根据
goods_type分发:实物走发货逻辑(生成物流单、减库存),虚拟走发码逻辑(从券码池取号、写入user_voucher表) - 任一环节失败,记录错误并触发补偿机制(如自动解冻积分、通知运营人工介入)
虚拟券的码池与核销闭环
虚拟商品的核心是券码生命周期管理。建立 voucher_pool 表预生成批次码(如10万条),每条含唯一 code、status(unused/used/expired)、used_at、used_by。兑换时从池中取一条 status = unused 的码,用 UPDATE ... WHERE status = 'unused' LIMIT 1 保证原子性。
核销环节需独立接口(如 /api/voucher/use),验证码有效性、检查过期、限制重复核销,并同步更新 voucher_pool 和 user_voucher 状态。核销成功后可触发积分返还(如退货)、消息通知等后续动作,推荐用 event 类触发,保持扩展性。
整个闭环不需要强依赖第三方队列,用 TP6 的命令行任务 + 数据库状态轮询即可支撑中小规模业务;高并发场景再引入 Redis + 消息队列做削峰。重点是把积分、商品、订单、履约、核销五层边界划清,每层只专注一件事。



















