# 物流消息推送方案（用户端 + 商家端）

日期：2026-02-11

## 0. 文档定位
本方案用于把“第三方快递轨迹（platform_express_*）”与移动端推送（uni-push2）结合，向：
- **用户端（C 端）**：推送订单物流节点更新，并在消息中心可回溯、可标记已读。
- **商家端（B 端）**：推送与发货/履约强相关的物流节点更新（重点关注异常/签收等），并在商家消息中心可回溯。

约束与口径继承自本目录既有文档：
- 统一事件模型、字段契约、状态映射：见接口与字段相关文档。
- 敏感信息（raw_payload、完整手机号/地址、POD 等）必须按角色授权，**不得通过推送明文下发**。

## 1. 背景与目标
### 1.1 背景
配送模块的事实数据源是第三方轨迹事件流：Webhook/轮询接收后入库到 `platform_express_*` 三表。

### 1.2 目标
1) **及时性**：当运单产生关键节点时，用户/商家能收到提醒。
2) **一致性**：推送只是“变更通知”，最终展示以平台查询接口返回的 `status_history` 为准。
3) **可追溯**：所有推送对应的“物流消息”可在消息中心列表查询，并可已读/未读统计。
4) **合规与隐私**：推送 payload 与通知文案不包含敏感信息，不透传 raw。

### 1.3 不在本方案范围
- 自营骑手/同城配送任务（`ml_delivery_*`）的接单/导航/送达链路。
- 厂商通道（小米/华为等）离线能力细节（可在 uni-push2 联调文档基础上扩展）。

## 2. 数据源与现有模型复用
### 2.1 事实数据表（生产口径）
- `platform_express_waybills`：运单摘要（current_status_*、last_synced_at）。
- `platform_express_tracking_events`：轨迹事件事实表（event_time/event_text/status_code 等）。
- `platform_express_event_raw`：原始留痕与验签审计（仅运维/客服可见）。

结论：
- **消息生成以 `platform_express_tracking_events` 的新增事件为触发**。
- **通知展示以 `platform_express_waybills.current_status_code/text` + 最新事件摘要为基础**。

### 2.2 统一状态码（必须遵循）
使用平台统一 `status_code`：
`ORDER_PLACED / SHIPPED / IN_TRANSIT / OUT_FOR_DELIVERY / READY_FOR_PICKUP / DELIVERED / EXCEPTION / RETURNED`

映射规则来自“状态映射表”，平台内部应保证确定性。

## 3. 总体架构（事件驱动 + 推送唤醒刷新）

### 3.1 核心原则
- **Push ≠ 数据源**：Push 仅承载“发生了什么/在哪里看”，不承载完整时间线。
- **App 打开详情页时以接口/DB查询为准**：避免推送丢失、乱序导致前端状态不一致。

### 3.2 推荐链路
1) 第三方回调/轮询到达平台（Webhook handler / polling job）
2) 平台：验签、防重放、幂等去重、乱序入库
3) 平台：基于“新插入事件”计算是否需要生成物流消息（对 C/B 端分别判断）
4) 平台：写入“物流消息表”（用于消息中心/未读）
5) 平台：按收件人设备 CID 列表调用 uni-push2 发送通知/透传
6) 客户端：收到推送 -> 更新消息中心未读角标/缓存 -> 用户点击进入订单/物流详情 -> 拉取最新时间线

## 4. 推送场景清单（C 端 + B 端）

### 4.1 场景分级（建议最小可用）
为了避免 IN_TRANSIT 等高频节点造成骚扰，MVP 建议：
- **只对“状态级别变化”或“关键状态”推送**，并允许后续配置化扩展。

### 4.2 用户端（C 端）推送场景
对单个订单/运单，建议推送：
- `SHIPPED`：商家已发货/已绑定运单
- `OUT_FOR_DELIVERY`：派送中
- `READY_FOR_PICKUP`：待取件
- `DELIVERED`：已签收
- `EXCEPTION`：异常（破损/拒收/地址不详等）
- `RETURNED`：退回/退件

可选（非必须、后续再做）：
- `IN_TRANSIT`：仅在“跨天/关键节点”或“长时间无更新后恢复更新”时推送。

### 4.3 商家端（B 端）推送场景
商家端更关注履约风险与闭环结果，建议推送：
- `SHIPPED`：发货成功（用于确认运单绑定成功/进入履约）
- `DELIVERED`：已签收（用于完成售后风险关闭）
- `EXCEPTION`：异常（优先级最高）
- `RETURNED`：退回/退件

可选（按业务需要）：
- `OUT_FOR_DELIVERY`：派送中（对高价值订单可能有意义）

### 4.4 同一事件对不同端的文案差异
- 同一事件事实字段同源（event_time/event_text/status_code）。
- **C 端**文案可更贴近用户体验（避免网点内部信息、避免电话明文）。
- **B 端**文案以进度/风险为主（异常/退回明确提示）。

## 5. 消息生成规则（幂等 + 去噪）

### 5.1 生成触发点
在平台完成 `platform_express_tracking_events` 插入成功后触发（建议异步队列/任务）。

### 5.2 幂等与去重
事件表已有 `(waybill_id, dedupe_key)` 唯一约束；消息层建议再做一层幂等：
- 推荐幂等键：`recipient_scope + recipient_id + waybill_id + status_code + event_time`
- 若第三方提供 `event_id`，可用：`recipient_scope + recipient_id + waybill_id + event_id`

保证：同一条事件重试/重复推送不会造成消息中心重复。

### 5.3 去噪（MVP）
- 默认仅对 4.2/4.3 列出的状态集合生成消息。
- 同状态多事件：只推“最新 event_time”对应的一条（或按节点去重）。

## 6. Push payload 规范（建议）

### 6.1 设计目标
- **足够定位**：能让客户端知道要刷新哪个订单/运单、点击去哪里。
- **不泄露敏感**：不含手机号/地址/raw_payload。
- **可审计**：含平台侧 message_id / request_id。

### 6.2 字段建议（透传/通知通用）
```json
{
  "biz": "express",
  "aud": "user", 
  "message_id": "msg_xxx",
  "order_no": "ORD_2026...",
  "carrier": "YTO",
  "tracking_no": "YT123...",
  "status_code": "OUT_FOR_DELIVERY",
  "event_time": "2026-02-05T14:32:00+08:00",
  "event_text": "快件正在派送中",
  "deeplink": {
    "path": "/pages/order/detail",
    "query": {"order_no":"ORD_2026...", "tab":"logistics"}
  }
}
```
说明：
- `event_text` 推荐使用“清洗后的文案”（避免把第三方原文中的手机号明文透传）。
- `aud`：区分用户端/商家端，有利于同一 App 多角色或同一推送通道多消费方。

### 6.3 通知栏标题/摘要建议（示例）
- 用户端：
  - 标题：`物流更新` / `订单物流更新`
  - 内容：`【派送中】快件正在派送中`
- 商家端：
  - 标题：`订单履约提醒`
  - 内容：`【异常】包裹地址不详正在退回`（异常优先级最高）

## 7. 消息中心与未读（服务端为准）

### 7.1 为什么需要消息中心
- Push 可能丢失/被系统拦截；消息中心提供可回溯记录。
- 多设备登录需要统一未读。

### 7.2 建议的数据模型（平台侧新增）
建议新增两类表（命名仅示例）：
1) 设备注册表 `push_devices`
- `id`
- `user_id` / `merchant_id`（二选一或用统一 subject）
- `cid`
- `platform`（android/ios/web）
- `appid`（用于区分环境）
- `last_seen_at`、`is_active`

2) 物流消息表 `express_notifications`
- `id`（message_id）
- `aud`（user/merchant）
- `recipient_id`
- `order_id/order_no`、`waybill_id`、`tracking_no`、`carrier`
- `status_code`、`event_time`、`event_text_safe`
- `read_at`（或拆到 read 表做多端多设备）
- `created_at`
- 幂等键（unique）

### 7.3 API（建议）
- 设备注册：`POST /api/v1/push/register`（登录后上报 CID，绑定到账号）
- 设备解绑：`POST /api/v1/push/unregister`
- 消息列表：`GET /api/v1/notifications?biz=express&aud=user|merchant&page=...`
- 标记已读：`POST /api/v1/notifications/read`（按 message_id 或按订单批量）
- 未读数：`GET /api/v1/notifications/unread-count?biz=express&aud=...`

## 8. 点击跳转（deeplink）

### 8.1 跳转原则
- 点击通知后进入“订单详情-物流区块”或“物流详情页”。
- 页面打开后必须调用平台查询接口刷新 `status_history`。

### 8.2 跳转目标建议
- 用户端：订单详情页（展示物流时间线）
- 商家端：订单详情页（展示物流时间线 + 发货信息）
- 平台后台：不通过 App push 跳转（后台有自己的排障页）

## 9. 安全、权限与隐私

### 9.1 收件人权限校验
- 用户端：仅能收到/查看自己订单的物流消息。
- 商家端：仅能收到/查看自己店铺订单的物流消息。

服务端生成消息时必须校验：`order_no -> 订单归属 -> recipient`。

### 9.2 敏感字段控制（强约束）
- 禁止通过 push payload/通知文案下发：完整手机号、完整地址、raw_payload、签名/密钥。
- `raw_payload` 仅允许客服/运维在后台折叠查看，并记录审计。

### 9.3 CID 绑定安全
- CID 必须在**登录态**下上报并绑定到账号。
- 更换设备/卸载重装：CID 可能变化，需更新绑定。
- 退出登录时建议解绑或标记设备 inactive。

## 10. 失败与补偿
- 推送失败（CID 失效/不属于应用）：标记设备 inactive 并等待下次登录刷新。
- Webhook 入库成功但推送失败：不影响事实数据；消息中心仍可查询到记录（若已写入消息表）。
- 客户端未收到 push：用户进入订单详情时仍能通过查询接口看到最新轨迹。

## 11. 验收清单（与联调口径一致）
1) 插入一条轨迹事件（模拟 webhook）后：
- waybill `current_status_code/text` 更新
- events 表新增且幂等
- 消息中心新增 1 条（不重复）
- 目标端（用户/商家）收到推送

2) 乱序/重复推送：
- 事件表不重复、消息中心不重复
- 当前状态不回退（按平台选定算法）

3) 隐私校验：
- push 文案与 payload 无手机号明文/无 raw

4) 点击跳转：
- 落到订单/物流详情页并刷新出最新时间线

---

## 12. 与本目录文档的对应关系（索引）
- 统一模型与 Webhook 约束：接口规范
- 状态映射：状态映射表
- 展示字段与隐私规则：前端字段清单
- 生产表结构与幂等/乱序：生产表说明_platform_express
- 移动端 push 联调与 CID 归属：uni-push2 安卓联调与取 CID 说明
