商城系统
概述
商城是玩家把货币/充值转化为道具的入口,也是运营做活动(折扣、限购、限时)的主阵地。商城的核心难点:订单防重复(同一笔购买只扣一次款、只发一次货)、发货补偿(扣款成功但发货失败如何兜底)、以及限购/限时/折扣的并发控制。本文设计完整的商城系统。
一、商品定义
1.1 商品配置
ShopItem(配置表):
shopId、itemId(售出道具)、count、price(货币+数量)
stockType(不限量/限总量/限单人)
limitCount(限购数量)、limitPeriod(日/周/总)
discount(折扣价,原价展示)
timeRange(上架起止时间)
channels(可用渠道:金币/钻石/充值)商品分类:
常规商品:长期上架
限时商品:活动期间上架(见活动章节)
折扣商品:原价划线 + 现价
直购商品:绑定真实充值(走支付渠道)1.2 商品池
商品池按渠道 + 玩家可见性过滤:
可见性过滤:
等级限制(未到等级隐藏)
时间限制(未到上架时间隐藏)
VIP/会员专属
已售罄/已达限购 → 置灰二、限购/折扣/限时
2.1 限购控制
限购维度:
单人限购(每日/每周/总次数)
总量限购(全服限量,售罄即止)
实现:
单人限购:Redis INCR(key: limit:{playerId}:{shopId}:{period})
总量限购:Redis DECR 库存(key: stock:{shopId})
扣减与购买同原子操作(Lua 脚本)// Lua:库存充足 + 未达限购 → 扣减
local stock = redis.call('DECR', KEYS[1])
if stock < 0 then
redis.call('INCR', KEYS[1])
return -1
end
local bought = redis.call('INCR', KEYS[2])
if bought > tonumber(ARGV[1]) then
redis.call('DECR', KEYS[1])
redis.call('DECR', KEYS[2])
return -2
end
return 02.2 折扣与限时
折扣:
价格字段带原价与现价
折扣区间与活动绑定,到期恢复原价
客户端展示划线价(服务端最终按现价收)
限时:
上下架时间配置化
定时任务/运行时判断控制可购性
下架后不可购买,已购不受影响三、购买支付流程
3.1 支付流程
购买流程(游戏内货币):
1. 客户端请求购买(shopId, count)
2. 服务端校验:商品存在、在售、未超限购、未超总量
3. 校验货币余额
4. 创建订单(status = CREATED)
5. 扣减货币(条件更新,余额不足回滚)
6. 发货(发道具/货币,走统一发放接口)
7. 更新订单状态(PAID)
8. 推送购买结果订单模型:
orderId、playerId、shopId、count
currencyId、payAmount、itemList
status(CREATED/PAID/FAILED/REFUNDED)
createTime、payTime3.2 真实充值
充值流程:
1. 客户端请求充值订单 → 生成 orderId
2. 调支付渠道下单(微信/支付宝/苹果)
3. 渠道异步回调 → 校验签名/金额
4. 按 channelOrderId 幂等(唯一索引)
5. 发货(钻石到账)→ 订单置 PAID幂等关键:
orderId 服务端唯一生成
channelOrderId 数据库唯一索引
重复回调不重复发货(状态判断)
发货失败 → 落补偿任务重试四、订单防重复
4.1 防重复购买
防重复手段:
服务端生成 orderId(客户端不可自定义)
同商品同玩家限购计数(Redis INCR 原子)
订单状态机(仅 CREATED → PAID 单向)
幂等键:同 playerId + shopId + period 去重// 幂等校验:同一玩家同一商品在周期内只买一次
boolean bought = redis.setIfAbsent(
"buy:" + playerId + ":" + shopId + ":" + period, "1");
if (!bought) {
return fail(ALREADY_BOUGHT);
}4.2 发货原子性
扣款与发货的一致性:
同事务内完成(强一致,推荐)
或扣款 → 发 MQ → 异步发货(最终一致)
扣款成功但发货失败 → 补偿任务重试发货
重试幂等:以订单号判断,不重复发货补偿机制:
定时扫描"已扣款未发货"订单(PAID 但 item 未到账)
重试发货,多次失败告警人工介入
退款场景:发货失败超时 → 原路退回货币五、实现要点
核心接口:
getShopList(playerId, channel)
buy(playerId, shopId, count)
recharge(playerId, productId, channel) // 生成充值单
rechargeCallback(channelOrderId, result) // 渠道回调工程注意:
价格以服务端配置为准(客户端展示仅供参考)
限购/库存用 Redis 原子操作
充值回调验签 + 幂等
订单表定期归档常见坑:
并发购买超库存 → Lua 原子扣减
重复回调重复发货 → 唯一索引 + 状态判断
扣款未发货 → 补偿任务 + 告警
折扣期边界 → 以下单时刻价格为准与其他系统衔接:
发货 → 道具/货币统一接口
限时 → 活动系统配置
流水 → 货币流水/道具流水审计
直购 → 支付渠道(第三方接入见账号章节)