# 生产表说明：platform_express_*（第三方快递轨迹平台侧）

本文档对应生产迁移脚本：`pages/mall/delivery/doc/需求文档/express_tracking_platform_upgrade.sql`。

## 1. 设计目标与边界

- 目标：为第三方承运（韵达/圆通/中通等）提供统一的“运单 + 轨迹事件 + 原始回调留痕”入库模型，支撑用户端/商家端/平台后台同源展示。
- 边界：
  - 仅覆盖第三方快递轨迹，不包含自营骑手/配送任务（`ml_delivery_*`）。
  - 生产仅使用 `platform_express_*` 三表；不包含任何 `mock_*` 测试表。

## 2. 表关系概览

- `platform_express_waybills`（运单主表）
  - 1 条运单对应 N 条轨迹事件（`platform_express_tracking_events`）。
- `platform_express_tracking_events`（轨迹事件事实表）
  - 时间线展示、状态判断、告警统计的主来源。
  - 对同一运单使用 `(waybill_id, dedupe_key)` 做幂等去重。
- `platform_express_event_raw`（原始接收留痕表）
  - 记录每一次 webhook/轮询/人工补录的原始请求/响应内容与验签结果、解析错误，用于审计与排障。

## 3. 字段说明

### 3.1 `public.platform_express_waybills`（运单主表）

用途：
- 以 `(carrier, tracking_no)` 为唯一键管理“运单实体”，可选关联订单；承载“当前状态/预计到达/最近同步时间”等运单级摘要信息。

约束/触发器/索引：
- 唯一约束：`uk_platform_express_waybill (carrier, tracking_no)`
- 外键：`order_id -> public.ml_orders(id)`，`ON DELETE SET NULL`
- `updated_at` 触发器：`trigger_platform_express_waybills_updated_at`（UPDATE 时自动刷新）
- 常用索引：
  - `idx_platform_express_waybills_order_id (order_id)`
  - `idx_platform_express_waybills_order_no (order_no)`
  - `idx_platform_express_waybills_tracking_no (tracking_no)`
  - `idx_platform_express_waybills_status (current_status_code)`

字段表：

| 字段 | 类型 | 可空 | 默认/约束 | 含义 |
|---|---|---:|---|---|
| id | UUID | 否 | PK，`uuid_generate_v4()` | 运单主键 |
| order_id | UUID | 是 | FK -> `ml_orders(id)`，删除置空 | 关联订单主键（推荐） |
| order_no | VARCHAR(64) | 是 |  | 订单号（展示/兜底，用于无法拿到 `order_id` 的场景） |
| carrier | VARCHAR(32) | 否 | 参与唯一约束 | 承运方编码（如 `YUNDA`/`YTO`/`ZTO` 等；也可接入聚合方编码） |
| tracking_no | VARCHAR(64) | 否 | 参与唯一约束 | 运单号/快递单号 |
| source | VARCHAR(16) | 否 | 默认 `'mock'` | 运单数据来源：`mock`/`carrier`/`aggregator` 等 |
| current_status_code | VARCHAR(32) | 否 | 默认 `'SHIPPED'` | 运单当前平台状态（用于列表页/摘要），通常由最新事件映射得到 |
| current_status_text | TEXT | 是 |  | 运单当前状态文本（可为最新事件文本的“清洗版”或平台自定义） |
| eta | TIMESTAMPTZ | 是 |  | 预计送达时间（可选；来自承运方/聚合方或平台预测） |
| last_synced_at | TIMESTAMPTZ | 是 |  | 最近一次与承运方/聚合方同步时间（用于健康度与补偿轮询判断） |
| created_at | TIMESTAMPTZ | 否 | 默认 `NOW()` | 创建时间 |
| updated_at | TIMESTAMPTZ | 否 | 默认 `NOW()`；触发器维护 | 更新时间（UPDATE 自动刷新） |


### 3.2 `public.platform_express_tracking_events`（轨迹事件表）

用途：
- 存储每一个轨迹节点（揽收/到站/运输中/派送/签收/异常等），作为时间线展示与状态机判断的事实来源。
- 保留第三方原文（`event_code`/`event_text`/`raw_payload`），同时写入平台统一状态（`status_code`）。
- 通过 `dedupe_key` 实现幂等：重复回调/轮询不会重复插入。

约束/索引：
- 外键：`waybill_id -> platform_express_waybills(id)`，`ON DELETE CASCADE`
- 唯一约束：`uk_platform_express_event_dedupe (waybill_id, dedupe_key)`
- 常用索引：
  - `idx_platform_express_events_waybill_time (waybill_id, event_time)`（按运单查时间线）
  - `idx_platform_express_events_tracking_time (tracking_no, event_time)`（按运单号查）
  - `idx_platform_express_events_status (status_code)`（按状态统计/筛选）
  - `idx_platform_express_events_received_at (received_at)`（按接收时间排障）
  - `gin_platform_express_events_raw_payload`（`raw_payload` JSONB GIN，便于排障检索）

字段表：

| 字段 | 类型 | 可空 | 默认/约束 | 含义 |
|---|---|---:|---|---|
| id | UUID | 否 | PK，`uuid_generate_v4()` | 事件主键 |
| waybill_id | UUID | 否 | FK，级联删除 | 所属运单 |
| carrier | VARCHAR(32) | 否 |  | 承运方编码（冗余字段，便于查询/对账） |
| tracking_no | VARCHAR(64) | 否 |  | 运单号（冗余字段，便于查询/对账） |
| event_id | VARCHAR(128) | 是 |  | 第三方事件唯一 ID（有些承运方会提供；优先用于幂等） |
| event_time | TIMESTAMPTZ | 否 |  | 事件发生时间（时间线排序的主依据） |
| event_code | VARCHAR(64) | 否 |  | 第三方事件码/节点码（保留原值） |
| event_text | TEXT | 否 |  | 第三方事件文本（保留原值；展示前可做脱敏/清洗） |
| status_code | VARCHAR(32) | 否 |  | 平台统一状态码（由 `event_code/event_text` 规则映射得出） |
| node_name | VARCHAR(128) | 是 |  | 节点/网点名称（如“xx转运中心”） |
| location | TEXT | 是 |  | 地理位置文本（如省市区/网点地址；不建议存精确经纬度，除非明确需求） |
| description | TEXT | 是 |  | 平台补充说明（如“疑似退回件，已通知商家”） |
| evidence_urls | JSONB | 否 | 默认 `[]` | 证据/附件 URL 列表（签收图、面单、异常凭证等） |
| raw_payload | JSONB | 是 |  | 第三方原始事件载荷（用于审计/排障；注意权限控制） |
| received_at | TIMESTAMPTZ | 否 | 默认 `NOW()` | 平台接收时间（用于排查延迟/乱序） |
| source | VARCHAR(16) | 否 | 默认 `'webhook'` | 数据进入平台的方式：`webhook`/`poll`/`manual` |
| dedupe_key | VARCHAR(256) | 否 | 参与唯一约束 | 幂等去重键（建议：有 `event_id` 用 `event_id`；否则构造 `tracking_no|event_code|event_time` 并按需要拼接 `node/location`） |
| created_at | TIMESTAMPTZ | 否 | 默认 `NOW()` | 入库时间 |

幂等与乱序建议：
- 幂等：插入事件时以 `(waybill_id, dedupe_key)` 唯一约束兜底；遇到冲突可 `ON CONFLICT DO NOTHING`。
- 乱序：允许旧事件晚到入库；前端展示按 `event_time` 排序。
- 运单摘要（`platform_express_waybills.current_status_*`）建议由“最新事件（按 event_time）映射结果”更新，避免用 `received_at` 作为最新依据。


### 3.3 `public.platform_express_event_raw`（原始接收留痕表）

用途：
- 记录每一次 webhook 回调或轮询结果的“原始内容 + 安全审计 + 解析结果”。
- 用于：验签问题定位、第三方字段变更回溯、解析失败重放、争议审计。

索引：
- `idx_platform_express_raw_received_at (received_at)`
- `idx_platform_express_raw_tracking_no (tracking_no)`
- `idx_platform_express_raw_signature_valid (signature_valid)`
- `gin_platform_express_raw_body`（`body` JSONB GIN，便于按字段排障检索）

字段表：

| 字段 | 类型 | 可空 | 默认/约束 | 含义 |
|---|---|---:|---|---|
| id | UUID | 否 | PK，`uuid_generate_v4()` | 原始接收记录主键 |
| received_at | TIMESTAMPTZ | 否 | 默认 `NOW()` | 平台收到请求/响应的时间 |
| source | VARCHAR(16) | 否 | 默认 `'webhook'` | 来源：`webhook`/`poll`/`manual` |
| client_id | VARCHAR(64) | 是 |  | 调用方/渠道标识（如回调应用 ID、聚合方商户号等） |
| carrier | VARCHAR(32) | 是 |  | 承运方编码（可从路由/请求体解析；可能为空） |
| tracking_no | VARCHAR(64) | 是 |  | 运单号（可能为空：如批量回调或解析失败） |
| signature_valid | BOOLEAN | 是 |  | 验签是否通过（解析失败或未验签可为空） |
| signature | TEXT | 是 |  | 签名原文（如 header 签名） |
| ts_header | TEXT | 是 |  | 时间戳 header（用于防重放；字段名取决于对接规范） |
| request_id | VARCHAR(64) | 是 |  | 请求追踪 ID（平台生成或对方传入） |
| remote_ip | INET | 是 |  | 来源 IP（用于安全审计/黑白名单） |
| headers | JSONB | 是 |  | 请求头（建议按需过滤敏感头再落库） |
| body | JSONB | 是 |  | 原始请求体/响应体（用于回溯与重放） |
| parse_error | TEXT | 是 |  | 解析错误信息（成功解析则为空） |
| dedupe_key | VARCHAR(256) | 是 |  | 原始请求去重辅助键（可选：如 `hash(headers+body)`） |

权限与合规建议（实现侧）：
- `raw_payload`、`platform_express_event_raw.body/headers` 可能包含敏感信息（手机号、地址、签名等），建议仅后台/运维具备读取权限，并记录审计日志。

## 4. 常见查询与写入路径（建议）

- 发货/绑定运单：
  - Upsert `platform_express_waybills`（按 `(carrier, tracking_no)`）；有订单关联时写入 `order_id/order_no`。
- 接收回调/轮询：
  - 先插入 `platform_express_event_raw` 做留痕（无论解析成功与否）。
  - 解析出事件后写入 `platform_express_tracking_events`；用 `dedupe_key` 做幂等。
  - 视需要更新 `platform_express_waybills.current_status_*`、`last_synced_at`。
- 订单详情页展示：
  - 通过 `order_id` 查 `platform_express_waybills`。
  - 再按 `waybill_id` 查 `platform_express_tracking_events`，按 `event_time` 升序组装时间线。
