代购网站开发中的订单自动化:库存竞态与RPA兜底
有个代购站点,同时接了三单同一款限量卫衣,库存系统显示还剩2件。结果三单都支付成功,仓库发货时才发现——自营仓只有1件,1688代发仓也只剩1件,两边的库存各自扣了一次,但同步延迟导致总量判断失误。最后有一单被迫退款,客户在群里骂了一整天。
这个问题的本质是双库存源的竞态条件。代购网站开发中,自营仓(Redis实时扣减)和1688代发仓(API查询+下单,库存延迟5-30分钟)并存时,简单的“先查后扣”必然出问题。
问题定义:两个库存源,一个超卖漏洞
订单自动化的核心挑战不是“能不能自动下单”,而是“在多个库存源之间如何保证一致性”。典型的崩溃场景:
1. 用户A查库存:自营仓还有1件,1688查询返回1件
2. 用户B同时查:看到同样的2件总量
3. 用户A下单成功,扣减自营仓
4. 用户B下单,调用1688 API时,那1件已被其他渠道买走
5. 结果:B的订单创建成功,但1688实际无货
1688开放平台文档明确写着:商品详情接口的库存同步延迟5-30分钟,大促期间更久。这意味着任何依赖实时查询的扣减逻辑都不可靠。
方案对比:乐观锁 vs 分布式锁 vs 预扣+补偿
| 方案 | 实现 | 适用场景 | 缺陷 |
|---|---|---|---|
| 数据库乐观锁 | UPDATE goods SET stock=stock-1 WHERE id=? AND stock>0 |
单库存源,低并发 | 无法处理跨源总量 |
| Redis分布式锁 | 锁住商品ID,串行化扣减 | 高并发,单源 | 性能瓶颈,锁超时风险 |
| 预扣+异步补偿 | 下单预扣虚拟库存,真正采购时再确认 | 多库存源 | 实现复杂,需要补偿机制 |
代购场景必须选第三种。核心思路:把“下单成功”和“1688采购成功”解耦。用户下单时只校验虚拟库存(一个可配置的安全阈值),不直接调用1688 API。订单进入“待采购”队列,由后台任务串行处理,处理时再去1688真实扣减。如果1688无货,则订单自动回滚并通知用户。
// 下单时预扣虚拟库存(Redis原子操作)
$lua = <<<LUA
local key = KEYS[1]
local threshold = tonumber(ARGV[1])
local current = tonumber(redis.call('get', key) or 0)
if current < threshold then
redis.call('incr', key)
return 1
end
return 0
LUA;
$prelocked = $redis->eval($lua, [$virtualStockKey, $maxConcurrent], 1);
if (!$prelocked) {
throw new Exception('当前下单人数过多,请稍后再试');
}
// 创建订单,状态为“待采购”
异步采购任务从队列中取订单,调用1688 API之前再加一次Redis分布式锁(锁商品ID,超时5秒)。获取锁成功后,重新查询1688实时库存,确认有货才下单。这样即使1688库存滞后,锁也能保证同一时刻只有一个线程在处理该商品的采购。
隐性知识点:RPA兜底1688的“不可API化”操作
1688开放平台不是万能的。有些供应商使用“批量下单”功能、或者需要手动改价、或者下单后要确认“缺货退款”协议——这些操作没有API。如果系统只依赖API,碰到这些订单就卡死了。
解决方案:RPA(机器人流程自动化)。写一个浏览器自动化脚本,模拟人工登录1688卖家后台,自动完成:查找订单→点击改价→输入金额→确认→截图留证。RPA任务由消息队列触发,执行结果回写数据库。
RPA的设计要点:
- 幂等性:每个订单附带一个task_id,RPA执行前检查是否已处理
- 超时熔断:单个任务超过2分钟标记失败,转人工
- 操作留痕:每一步都截图存证,存入订单的audit_log字段
有意思的是,RPA反而比API更稳定——它不受1688接口版本升级的影响。有一次1688静默升级了签名算法,所有API调用全挂,但RPA任务照常运行,那一整天的订单一个都没漏。
落地方案:状态机防重
无论API还是RPA,最终都要更新订单状态。状态机必须用乐观锁防止重复流转:
-- 只有当前状态匹配时才更新
UPDATE orders SET status = '采购成功', supplier_order_id = ?
WHERE order_id = ? AND status = '待采购';
这个SQL影响行数为0时,说明状态已经被其他线程更新了,当前操作直接返回。配合上面的分布式锁,可以做到。
在taocarts 的订单自动化模块中,这套预扣+异步采购+RPA兜底的链路已经跑了一年多,日单量从几十涨到几百,没再出现过1688超卖导致的退款。回头想想,代购网站开发的核心不是写出多优雅的代码,而是把所有“可能出问题”的边界都想清楚——库存同步延迟、API限流、供应商非标操作——然后一个个兜住。下篇我们聊聊仓库管理和合包策略,那是另一个容易翻车的深水区。