ThinkPHP实现多仓库发货需构建库存锁定、仓库选择、物流分配三环节闭环:库存扣减须原子化SQL操作并校验affectedRows;仓库按SKU绑定、店铺归属、地址匹配三级决策并落库记录;物流需结合承运商能力与订单条件自动选优降级;数据结构须扩展地理围栏、策略规则及库存流水日志。

ThinkPHP 实现多仓库发货,核心不在“能不能写”,而在于怎么把库存锁定、仓库选择、物流分配这三个环节串成一条不掉链子的业务流水线。关键不是堆功能,而是让每一步都可验证、可回溯、可兜底。
库存锁定必须原子化,不能靠“先查再减”
很多项目用 find() 读出库存,判断够不够,再 save() 更新——这在并发下单时必然超卖。ThinkPHP 6/8 不提供开箱即用的乐观锁,必须手动构造带条件的 UPDATE 语句:
- 用 Db::raw('stock - 1') 直接在 SQL 层完成扣减,避免 PHP 层中间状态
- WHERE 条件必须包含库存约束,例如 WHERE id = ? AND stock >= ?
- 执行后检查 affectedRows():等于 1 表示成功;等于 0 表示库存不足或已被抢走,需立刻返回失败
- 若涉及批次/效期,还需在 WHERE 中加入 batch_no 或 expire_date > NOW() 等条件
发货仓选择要分层决策,不能只靠默认设置
真实业务中,一个订单的发货仓往往由多个维度共同决定,建议按优先级顺序逐层匹配:
-
SKU 绑定优先:某些商品(如冷链、定制品)只能从指定仓库出,数据库中需有
product_warehouse_map表,强制路由 -
店铺归属次之:淘宝旗舰店发上海仓,抖音小店发深圳仓,在订单创建时就根据
shop_id查出对应默认仓 - 收货地址兜底:当 SKU 和店铺都没强约束时,调用地理围栏服务(如高德逆地理编码),匹配省/市,再查预设的「区域-仓库」映射表
- 所有策略结果必须落库到订单主表的
warehouse_id字段,并记录匹配依据(如 “match_by: address”),便于对账追溯
物流分配要联动库存与承运能力
选完仓库只是开始,还得决定用哪家快递、走什么线路。这一环常被忽略,但直接影响履约时效和成本:
立即学习“PHP免费学习笔记(深入)”;
- 每个仓库需维护 可用承运商列表 及其服务范围(如:SH_WMS 支持顺丰/京东/中通,但中通不送新疆)
- 订单生成时,结合 重量+体积+目的地+时效要求 查询承运商匹配规则表,返回最优选项
- 若首选快递当日已截单或库存所在仓无该快递揽收排班,则自动降级到备选方案,而非直接报错
- 物流单号生成后,必须同步更新库存单据状态为「已打单」,并写入操作日志,禁止跳过出库直接发货
数据结构要支撑多维交叉,不能只做简单关联
光有商品表、仓库表、库存表三张基础表远远不够。真实场景需要这些扩展:
- warehouse_area_rule:定义每个仓库覆盖的省市区,支持多边形地理围栏字段(JSON 或 GIS 类型)
- product_warehouse_policy:记录某 SKU 在某仓是否允许销售、是否启用调拨、最低起订量等策略
- logistics_route_config:存储各仓到不同区域的首重/续重资费、预计时效、禁发品类等
- 所有库存变动(入库、出库、调拨、盘点差异)必须记入 stock_journal 流水表,含操作人、单据号、前后数量、时间戳



















