简介
本文件面向电商开发者,系统化梳理 DouPHP 物流配送体系,覆盖物流公司集成、快递单号管理、物流轨迹跟踪、运费计算、配送规则(地址验证、配送范围控制、自提点)、异常处理、数据缓存、批量查询优化与监控告警等。文档以代码为依据,提供可落地的架构图、流程图与开发指引,帮助快速扩展新物流公司、实现自定义运费规则并保障系统稳定运行。
项目结构
围绕“订单-物流-运费”的主线,DouPHP 将物流相关能力分布在以下层次:
- 插件层:物流公司抽象与运费方法暴露(如 express 插件)。
- API/控制器层:订单结算时运费重算、后台发货录入。
- 服务层:订单状态推进、库存解锁、物流信息持久化。
- 模型与数据层:订单主表、收货地址表、插件配置表等。
- 前端交互:购物车/结算页运费动态更新。
graph TB
subgraph "前端"
FE["订单页面<br/>global.js"]
end
subgraph "API/控制器"
API_ORD["order.php 路由"]
API_CHK["CheckoutController::recalculateShipping"]
end
subgraph "服务层"
ORD_SVC["OrderService::tracking"]
EXP_SVC["ExpressService::methods"]
end
subgraph "数据层"
DB_ORD["订单表 order"]
DB_ADDR["收货地址表 order_address"]
DB_PLUG["插件表 plugin"]
end
FE --> API_CHK
API_CHK --> ORD_SVC
ORD_SVC --> DB_ORD
ORD_SVC --> DB_ADDR
ORD_SVC --> DB_PLUG
EXP_SVC --> DB_PLUG
核心组件
- 物流公司插件接口:通过 ExpressService 暴露配送方式、名称与费用,供结算与展示使用。
- 订单物流跟踪:后台录入物流公司与运单号,首次填写自动推进订单状态为已发货并解锁库存。
- 运费重算:结算页根据选择的物流公司与订单金额重新计算运费,返回前端渲染。
- 订货模块(B2B/批发):提供订货列表、加购、提交订货单等能力,可与物流流程衔接。
- 收货地址与展示:订单详情从 order_address 统一读取收件人信息,保证前后端一致。
架构总览
下图展示了“选择物流公司→计算运费→下单→发货→状态同步”的端到端流程,以及关键数据流向。
sequenceDiagram
participant U as "用户"
participant FE as "前端页面"
participant API as "API 结算接口"
participant OS as "OrderService"
participant PL as "插件(Express)"
participant DB as "数据库"
U->>FE : 选择物流公司/修改收货地
FE->>API : 请求重算运费
API->>PL : 获取配送方法与费用
PL-->>API : 返回可用方式与价格
API->>DB : 写入/更新运费与订单金额
API-->>FE : 返回最新运费与总价
U->>FE : 提交订单
FE->>API : 创建订单含 shipping_id
API->>DB : 保存订单与收货地址
Note over U,DB : 后台发货
U->>OS : 录入物流公司+运单号
OS->>DB : 更新 shipping_id/tracking_no/shipped_at
OS->>DB : 若首次发货则改状态为已完成并解锁库存
OS-->>U : 返回详情页URL
详细组件分析
物流公司插件与运费方法
- 插件职责:声明配送方式 id/name/desc/price,供结算与展示调用。
- 费用来源:从插件配置中读取固定费用或按策略计算后的结果。
- 扩展建议:新增物流公司时,实现对应插件并提供 methods() 输出;在结算流程中通过 plugin() 门面解析。
classDiagram
class ExpressService {
+string PLUGIN_ID
+methods() array
}
class PluginFacade {
+getWithConfig(id) array
}
ExpressService --> PluginFacade : "读取插件配置"
订单物流跟踪(物流公司集成与快递单号管理)
- 输入校验:后台表单仅允许 order_id、shipping_id、tracking_no 三个字段,防止越权注入。
- 业务逻辑:
- 若订单已有运单号:仅更新物流公司、运单号与发货时间。
- 若首次填写:将订单状态推进为“已完成”,设置 allow_aftersale=1,并解锁商品锁定库存。
- 跳转:完成后返回订单详情页。
flowchart TD
Start(["开始"]) --> V["校验 order_id"]
V --> |非法| Err["抛出异常并返回"]
V --> |合法| Load["加载订单"]
Load --> Has{"是否已有 tracking_no?"}
Has --> |是| Update["更新 shipping_id/tracking_no/shipped_at"]
Has --> |否| Promote["推进状态为已完成<br/>设置 allow_aftersale=1<br/>解锁库存"]
Update --> Done["返回详情页URL"]
Promote --> Done
实时运费计算与结算
- 触发点:结算页选择物流公司或优惠券变化时,调用 API 重算运费。
- 计算过程:根据 shipping_id、订单金额与优惠券金额,调用结算服务重新计算运费与总价。
- 返回内容:包含运费、优惠金额、订单总额格式化值,前端用于刷新显示。
sequenceDiagram
participant FE as "前端"
participant API as "CheckoutController"
participant CS as "结算服务"
FE->>API : POST 重算运费
API->>CS : recalculateShipping(shippingId, itemAmount, couponAmount)
CS-->>API : 返回运费与订单总额
API-->>FE : JSON{shipping_fee, order_amount_format,...}
订货模块与物流衔接(B2B场景)
- 订货列表:支持按分类筛选、分页、默认排序。
- 加购/改量/删除:维护订货购物车,支持数量变更与行删除。
- 提交订货单:生成订货单号,写入联系人、电话、地址与订单金额,并将购物车明细关联到订货单。
- 与物流的关系:订货单可作为后续发货/物流跟踪的业务载体。
flowchart TD
A["浏览订货列表"] --> B["加入购物车/修改数量"]
B --> C["提交订货单"]
C --> D["生成 dh_sn 并落库"]
D --> E["关联购物车明细到订货单"]
E --> F["可选:更新默认收货信息"]
收货地址与展示
- 数据来源:订单详情中的收件人、电话、省市区、详细地址、邮编统一从 order_address 表读取,避免历史字段不一致问题。
- 展示:后台订单详情页直接渲染上述字段,便于客服与运营核对。
依赖关系分析
- 控制器/路由:API 路由将订单相关接口指向 Work/Cashier 控制器;后台路由将订单操作指向 OrderService。
- 服务依赖:OrderService 依赖订单核心服务、支付服务与支付方式名称解析器;ExpressService 依赖插件门面。
- 数据依赖:订单主表、收货地址表、插件配置表构成物流与运费的核心数据支撑。
graph LR
R["order.php 路由"] --> C1["WorkController"]
R --> C2["CashierController"]
C2 --> S1["CheckoutController"]
S1 --> S2["OrderService"]
S2 --> M1["订单模型"]
S2 --> M2["收货地址模型"]
S2 --> P1["插件(Express)"]
性能与优化
- 批量查询优化
- 后台订单列表采用分批查询与条件过滤,减少无效数据加载。
- 收货地址批量拉取后映射到订单列表,避免 N+1 查询。
- 运费计算缓存
- 对常用物流公司配置与基础费率进行缓存(例如 Redis/Memcached),降低重复计算与外部依赖开销。
- 异步与重试
- 物流轨迹拉取、状态同步建议走队列异步执行,失败指数退避重试,避免阻塞主流程。
- 前端体验
- 运费变更采用局部刷新(AJAX),减少整页重载。
故障排查指南
- 无法发货/状态未推进
- 检查后台表单校验是否通过(order_id 必填且为正整数)。
- 确认订单是否已有 tracking_no;若已有,仅更新不推进状态。
- 运费不更新
- 检查结算接口是否成功返回新的运费与订单总额。
- 确认前端是否正确接收并渲染 shipping_fee 与 order_amount_format。
- 地址显示为空
- 核对 order_address 是否存在对应记录;后台详情组装会优先从此表取值。
- 插件不可用
- 检查插件 slug 与配置是否存在;ExpressService 会从插件配置读取 name/fee 等信息。
结论
DouPHP 的物流配送体系以“插件化物流公司 + 订单物流跟踪 + 结算运费重算”为核心,具备清晰的边界与可扩展性。通过后台表单校验与服务层事务化处理,确保物流状态与库存一致性;结合前端 AJAX 局部刷新提升用户体验。在此基础上,可通过插件扩展新物流公司、通过结算服务扩展复杂运费规则,并通过缓存与队列实现高并发与稳定性保障。
附录:开发示例与最佳实践
集成新物流公司(插件)
- 步骤
- 新建插件目录与 manifest,注册 slug 与配置项(name、fee 等)。
- 实现 ExpressService 风格的 methods(),返回 id/name/desc/price。
- 在结算流程中通过 plugin() 门面获取该物流公司并参与运费计算。
- 参考路径
- ExpressService.php:11-35
实现自定义运费计算规则
- 思路
- 在结算服务中依据 shipping_id、订单金额、优惠券金额计算运费。
- 支持按区域、重量、件数、会员等级等维度差异化计费。
- 将计算结果写回 Session/缓存,并在前端刷新显示。
- 参考路径
- CheckoutController.php(API):168-197
- global.js(前端运费更新):97-113
处理物流异常情况
- 常见异常
- 物流公司接口超时/失败:进入队列重试,记录日志并告警。
- 运单号格式错误:在表单层校验并提示修正。
- 状态不同步:提供人工干预入口,支持补发状态与日志审计。
- 参考路径
- OrderService.php(后台订单服务):500-543
物流数据缓存与批量查询优化
- 缓存策略
- 物流公司配置与基础费率缓存(短 TTL)。
- 热门区域运费模板缓存。
- 批量优化
- 订单列表批量拉取收货地址并映射。
- 物流轨迹批量查询与去重。
- 参考路径
- OrderService.php(后台订单服务):96-200
物流监控告警
- 指标
- 发货成功率、轨迹查询成功率、平均耗时、失败率。
- 告警
- 连续失败阈值触发告警(邮件/IM)。
- 慢查询与外部接口超时告警。
- 落地
- 在服务层埋点日志,结合监控系统(Prometheus/Grafana 或自建)采集与可视化。