核心是用独立状态机统一管控运单状态流转,确保事务安全、权限隔离与消息联动。需建OrderStateMachine类为唯一变更入口,副作用延迟至onCommit执行,区分触发来源与权限边界,并通过事件监听实现轨迹与消息准实时同步。

Yii开发物流后台运单业务,核心不在堆功能,而在控状态、保一致、防错漏。真实场景里,一张运单从录单到签收,要经调度、揽收、在途、派件多个环节,每个节点都可能被人工干预、系统异步触发或外部回调更新——稍不注意,就会出现“状态已改但通知没发”“签收了库存却没扣”“重复推送消息”这类问题。
用独立状态机统一管控流转
别把状态逻辑写进Order模型的beforeSave或一堆if-else里。Yii项目应建一个OrderStateMachine类作为唯一决策入口,所有状态变更(如$stateMachine->apply($order, 'picked_up', $context))必须走它。Behavior只做辅助,比如提供$order->canCancel()校验方法,不执行实际跳转。这样测试可mock状态机,上线后加新状态(如“异常滞留”)只需改状态机配置,不动模型和控制器。
事务安全:副作用必须延迟到onCommit
状态更新成功,不代表发短信、记日志、推公众号就该立刻执行。若事务中途回滚,而日志已写、消息已发,数据就对不上。正确做法是:在状态变更后,用Transaction::onCommit()注册回调,把通知、审计、缓存更新等操作包进去。例如:
- 数据库更新运单status字段
- 调用
$transaction->onCommit(function () use ($order) { \Yii::$app->mailer->compose(...)->send(); }); - Redis缓存用延迟双删:先删旧缓存,事务提交后再删一次
运单操作要区分触发来源与权限边界
后台点击“一键在途”,本质是批量状态跃迁,但不能跳过校验。需检查:当前状态是否为“录单”,目标状态是否允许由管理员触发,该运单是否已被锁定或存在未处理异常。同样,快递员APP扫码揽收,应走另一套API入口(如/api/v1/scan/pickup),带身份签名和设备指纹,与后台操作完全隔离。权限控制不靠前端隐藏按钮,而是在服务层校验Role::can('update_status_by_scan')。
轨迹与消息联动要准实时,不靠轮询
客户查单号看到“已签收”,不是页面定时刷出来的。推荐用MySQL事件监听+轻量长连接(非WebSocket),当express_info表status字段更新时,触发通知服务向指定连接广播。后台“一键发送签收消息”按钮,实际是调用统一消息网关,按模板填充运单号、时间、签收人,并自动选择渠道(公众号模板消息 / 短信 / APP推送),避免每种状态都写一套发消息逻辑。


















