Files
medical-mall/pages/mall/delivery/doc/需求文档(现用)/接口规范.md
2026-03-17 11:06:26 +08:00

517 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 配送对接接口规范(模拟三通一达后台 / Mock 承运方 Server
本文档用于定义“模拟承运方后台服务Mock Server”的接口与行为供平台在未接入真实承运方前完成联调与测试。
本文定位:
- 规定 Mock Server 与平台后端之间的交互Webhook 推送 + 控制面 API
- 规定平台侧建议的统一事件模型(用于入库、时间线展示、监控告警)。
关联文档:
- `配送模块需求文档.md`:目标/范围/验收与必须的故障注入
- `状态映射表.md`状态码映射建议Mock event_code -> 平台 status_code
- `前端字段清单.md`:安卓/Web 时间线展示字段契约
包含两类接口:
1) Mock Server -> 平台Webhook 事件推送(模拟承运方回调)
2) 平台/测试工具 -> Mock Server控制面 API创建运单、追加事件、运行场景、触发推送、查询轨迹
## 统一约定
- 时间格式ISO 8601示例"2026-02-05T10:30:00+08:00"
- 所有接口请求/响应均为 JSONContent-Type: application/json
- 写操作建议携带 `request_id` 用于幂等与排查
- 所有第三方回调应保存 `raw_payload` 用于审计
补充说明(安卓端 & Web 端使用场景):
- 本文主要规范 Mock Server 与平台后端的交互;安卓/Web 客户端不应直接调用 Mock Server。
- Web 端联调时如需浏览器直连平台接口,应由平台网关配置 CORS仅测试环境白名单避免将 Mock Server 暴露给公网。
## 一、目标与约束
- 目标:用可控数据模拟关键配送节点与异常场景,覆盖验签、幂等、乱序/重复、延迟等测试。
- 约束:本规范服务于测试/预发布环境,不用于生产承运方直连。
## 二、核心数据模型(建议统一存储字段)
- `carrier`承运方标识例如YUNDA、YTO、KDN
- `tracking_no`:运单号
- `event_id`第三方事件唯一ID用于去重
- `event_time`事件时间ISO8601
- `event_code`:第三方原始事件码(尽量不改写,用于审计与一致性对齐)
- `event_text`:第三方原始事件文案(前端时间线默认展示)
- `status_code`平台统一状态ORDER_PLACED, SHIPPED, IN_TRANSIT, OUT_FOR_DELIVERY, READY_FOR_PICKUP, DELIVERED, EXCEPTION, RETURNED
- `node_name`:节点名称(中转站/网点/城市)
- `location`:节点位置描述(城市/网点地址,注意隐私)
- `description`:事件详细描述
- `evidence_urls`:证据照片/签名链接数组
- `estimated_arrival`ETA若承运方提供可选
- `raw_payload`:原始第三方 JSON审计
- `last_synced_at`:本地同步时间
## 三、接入模式
- 平台接收 Mock Server 的 Webhook 推送(推荐)。
- 平台亦可主动调用 Mock Server 查询轨迹(模拟轮询补偿)。
范围说明(避免误解):
- 若要实现“电子面单/在线下单自动获取运单号”,需要对接第三方(快递公司直连或聚合平台)提供的下单/面单接口。
- 该类“下单/面单”接口不属于本 Mock Server 接口规范范围;本文只覆盖轨迹事件的推送/查询与相关验签、幂等与字段口径。
## 四、接口定义(示例)
1) Mock Server -> 平台事件推送Webhook
- URL: POST /webhook/express/status
- 描述: 承运方/聚合方主动推送运单事件(单条或批量事件)。平台应校验签名并返回 HTTP 200 表示成功接收。
- HTTP 头部(建议):
- `Content-Type: application/json`
- `X-Client-Id`: 承运方或聚合方ID
- `X-Timestamp`: 推送时间戳(防重放)
- `X-Signature`: HMAC-SHA256(body + X-Timestamp) 使用双方共享 secret
- 回调示例(单事件):
{
"tracking_no":"YD123456789",
"carrier":"YUNDA",
"event_id":"e_20260205_0001",
"event_code":"TRANSIT",
"event_text":"运输中",
"event_time":"2026-02-05T14:32:00+08:00",
"node_name":"北京分拨中心",
"location":"北京市朝阳区XXX",
"evidence_urls":[],
"raw_payload":{/* 原始承运方 JSON */}
}
处理要求:
- 平台校验 `X-Signature``X-Timestamp`;若验签失败或时间差过大返回 4xx。若成功返回 HTTP 200。接收后异步完成映射与入库。
- 幂等:基于 `event_id``tracking_no+event_code+event_time` 去重。
2) 平台 -> Mock Server轨迹主动拉取模拟轮询
- URL: GET /mock/v1/track?tracking_no={tracking_no}&carrier={carrier}
- 描述: 平台主动查询单运单轨迹,返回事件数组。
- 响应示例:
{
"tracking_no":"YD123456789",
"carrier":"YUNDA",
"events":[
{"event_id":"e1","event_code":"PICKED","event_text":"已揽收","event_time":"2026-02-04T18:00:00+08:00","node_name":"门店揽收网点","location":"北京市顺义"},
{"event_id":"e2","event_code":"ARRIVAL","event_text":"运输中","event_time":"2026-02-05T14:32:00+08:00","node_name":"北京分拨中心","location":"北京市朝阳区"}
]
}
3) 平台:异常上报 API用户/客服触发,平台侧,不属于 Mock Server
- URL: POST /api/v1/delivery/express/report-exception
- 说明: 该接口为平台自身能力Mock Server 不强制实现。
- 请求示例:
{
"tracking_no":"YD123456789",
"reported_by":"user",
"report_type":"damaged",
"description":"包裹外包装破损,有压痕",
"evidence_urls":["https://.../img1.jpg"]
}
- 平台可选地回传给真实承运方(若未来接入)并记录回传结果。
4) 平台/测试工具 -> Mock Server控制面 API建议实现
4.1 配置回调目标
- URL: POST /mock/v1/config
- 描述: 设置 Mock Server 推送到平台的 Webhook 目标地址与验签参数。
- 请求示例:
{
"target_webhook_url":"https://api.yourplatform.com/webhook/express/status",
"client_id":"carrier_mock_yunda",
"secret":"shared_secret",
"default_carrier":"YUNDA"
}
4.2 创建运单
- URL: POST /mock/v1/waybills
- 描述: 创建一个 mock 运单,可由调用方指定或由服务生成。
- 请求示例:
{
"carrier":"YUNDA",
"tracking_no":"YD123456789",
"order_no":"202602050001"
}
- 响应示例:
{
"carrier":"YUNDA",
"tracking_no":"YD123456789",
"created_at":"2026-02-05T14:00:00+08:00"
}
4.3 追加事件(不推送,仅入库)
- URL: POST /mock/v1/waybills/{tracking_no}/events
- 请求示例:
{
"event_id":"e_20260205_0002",
"event_code":"TRANSIT",
"event_text":"运输中",
"event_time":"2026-02-05T14:32:00+08:00",
"node_name":"北京分拨中心",
"location":"北京市朝阳区",
"evidence_urls":[]
}
4.4 运行预置场景(生成一组事件并可选立即推送)
- URL: POST /mock/v1/waybills/{tracking_no}/run-scenario
- 请求示例:
{
"scenario":"standard_delivered",
"push":true,
"inject":{
"delay_ms":300,
"duplicate":0,
"out_of_order":false,
"bad_signature":false,
"timestamp_skew_seconds":0
}
}
4.5 触发推送(把该运单事件推到平台)
- URL: POST /mock/v1/waybills/{tracking_no}/push
- 请求示例:
{
"mode":"all",
"inject":{
"delay_ms":0,
"duplicate":1,
"out_of_order":true
}
}
4.6 查询运单轨迹Mock Server 自身查询接口)
- URL: GET /mock/v1/waybills/{tracking_no}/track
- 响应示例:
{
"carrier":"YUNDA",
"tracking_no":"YD123456789",
"events":[
{"event_id":"e1","event_code":"PICKED","event_text":"已揽收","event_time":"2026-02-04T18:00:00+08:00"}
]
}
4.7 健康检查
- URL: GET /mock/v1/health
- 响应示例:
{"status":"ok"}
## 五、字段映射与状态对照(示例)
- 平台统一状态 `OUT_FOR_DELIVERY` 映射关系示例:
- 韵达:承运方 `派送中` -> 平台 `OUT_FOR_DELIVERY`
- 聚合:原始 `deliver` -> 平台 `OUT_FOR_DELIVERY`
状态一致性策略(尽量保持平台与第三方一致):
- 一致性定义:平台保存的 `event_code/event_text``raw_payload` 应与第三方可查询到的轨迹语义一致;平台对外展示的 `status_code` 必须由映射表确定性生成(同一承运方同一 `event_code` 在同一版本映射下得到同一 `status_code`)。
- 字段保真:`event_code/event_text` 仅做透传与脱敏展示,不建议“为了统一文案”而改写原文;统一展示口径使用 `status_code` 的标签/颜色/筛选实现。
- 映射表治理:映射表变更需要版本化(例如 `mapping_version`)并走发布流程;避免在生产环境频繁调整导致历史轨迹“同码不同态”。如必须调整,建议补充回放/回填策略以保证历史一致。
- 回查纠偏(可选增强):平台定期或按需对“关键单/争议单”触发第三方轨迹回查;若发现缺失事件则补采入库;若发现语义冲突,优先保留原始事件并追加“平台侧纠偏事件/备注”,避免静默覆盖造成审计对不上。
## 六、验签与安全
- 建议使用 HMAC-SHA256 签名,签名字段为 `body + X-Timestamp`,服务端使用共享 `secret` 校验。
- 防重放:校验 `X-Timestamp` 与当前时间差(例如 < 5 分钟),并保存最近 N 个 `X-Signature``event_id` 用于去重。
## 七、幂等与重试策略
- webhook承运方可能重试多次平台必须基于 `event_id` 或 (`tracking_no`+`event_code`+`event_time`) 去重。
- 主动调用承运方接口失败时采用指数退避重试;关键操作(打单)建议入队异步重试并人工告警。
入库层(幂等去重、乱序处理)建议:
- 幂等目标:同一条事件无论推送/重试多少次,最终数据库只保留一条(或同一条被安全更新),避免重复入库造成时间线膨胀。
- 幂等键优先级:
- 优先使用第三方提供的 `event_id`
- 若缺少 `event_id`,使用兜底组合键:`tracking_no + event_code + event_time`(必要时可补充 `node_name/location` 以降低碰撞)。
- 数据库约束:建议在事件表上建立唯一约束(例如 `carrier + tracking_no + event_id``tracking_no + dedupe_key`),写入采用 Upsert/Insert-Ignore应用层无需“先查再写”来保证并发安全。
- 乱序处理:允许事件乱序写入(以免晚到的历史节点被丢弃);查询展示时按 `event_time` 排序生成时间线。
- 状态不回退(平台侧可选规则):当先收到终态(如 `DELIVERED`)后又收到更早时间的在途事件时,不应将订单状态从终态回退;可采用“以最新 `event_time` 事件计算 current_status”或“按状态流转等级仅允许前进”的策略。
## 八、展示与 UI 要求(与前端对接点)
- 时间线按 `event_time` 展示,标注 `carrier` 来源与 `last_synced_at`
- 节点可展开查看 `description``node_name``location``evidence_urls`(签收照片/回单)。若无证据则隐藏预览。
- 提供“查看第三方原文”链接,展示 `raw_payload`(仅客服/运维可见)。
平台对客户端返回建议(安卓/Web
- 客户端请求应面向平台统一接口(例如:`GET /api/v1/delivery/express/track?order_no=...`),由平台返回统一的 `status_history`
- 响应中建议包含:`carrier``tracking_no``status``status_history``last_synced_at`,以及可选的 `eta`
- `raw_payload` 不建议下发给普通用户端;仅在 Web 客服/运维界面按权限下发并记录审计。
## 九、监控与告警
- 指标webhook 接收成功率、平均同步时延、同步失败率、超过阈值未更新的运单数。
- 告警:承运方 5xx 增加、webhook 验签失败率异常、单运单长时间无更新。
## 十、示例 cURLWebhook 验证场景模拟)
```
curl -X POST https://api.yourplatform.com/webhook/express/status \
-H "Content-Type: application/json" \
-H "X-Client-Id: carrier_yunda" \
-H "X-Timestamp: 2026-02-05T14:32:00+08:00" \
-H "X-Signature: <hmac-signature>" \
-d '{"tracking_no":"YD123456789","event_id":"e_20260205_0001","event_code":"TRANSIT","event_time":"2026-02-05T14:32:00+08:00","event_text":"运输中"}'
```
## 十一、日志与保留策略
- 建议保留 `raw_payload` 与入库审计日志至少 30 天以便纠纷处理审计记录包括接收时间、IP、头部信息与处理结果。
## 十二、兼容与扩展建议
- 建议实现承运商 Adapter 层:承运方差异在 Adapter 层转换为统一模型,便于后续扩展新承运方或替换聚合方。
- 若需签收照片或更高粒度信息,应优先与承运方签订企业级合同或直连,并在 UI 端明确注明凭证来源。
### 十二点一、平台后端适配架构(推荐实现方式)
目标:当不同第三方(直连承运方/聚合平台API 结构、鉴权方式、事件码不同的时候,平台侧**不修改核心业务与数据库结构**即可接入。
核心原则:
- **统一领域模型入库**:第三方差异在入库前完成映射;数据库保存统一事件字段 + `raw_payload`
- **Adapter 可插拔**:每家第三方实现一个 Adapter验签/解析/查询/映射),核心服务只处理统一模型。
- **接入入口两条线**Webhook推送与 Poller轮询补偿最终都走同一条 `EventIngestService`
模块划分(建议):
- `WebhookController`:接收回调、路由到对应 Adapter、快速返回 200业务异步处理
- `CarrierAdapter`(每家一个):验签、解析、字段映射、状态映射、(可选)轨迹查询。
- `AdapterRegistry/Router`:按 `carrier``X-Client-Id` 选择 Adapter支持灰度与多配置
- `EventIngestService`:幂等去重、乱序处理策略、统一入库、写审计日志。
- `TrackQueryService`:给安卓/Web 提供统一查询接口(只读),不透出第三方差异。
- `PollerJob`:定时对“长时间无更新”的运单做轮询补偿(可按承运方频率配置)。
幂等与乱序建议(平台侧):
- 幂等键优先级:`event_id`(优先) > `tracking_no + event_code + event_time`(兜底)。
- 入库:允许乱序写入;查询展示时按 `event_time` 排序。
- 兼容缺字段:缺少 `event_id` 时必须使用兜底组合键;缺少 `location/node_name` 时 UI 降级仅展示 `event_text`
### 架构图Mermaid
```mermaid
flowchart LR
subgraph Clients[客户端]
A[安卓 App] -->|GET 物流时间线| PAPI[平台 API]
W[Web/H5/PC] -->|GET 物流时间线| PAPI
end
subgraph Platform[平台后端]
PAPI --> TQS[TrackQueryService\n统一查询]
TQS --> DB[(Logistics DB\nWaybill + TrackingEvent)]
WH[WebhookController\n接收回调] --> REG[AdapterRegistry/Router\n按carrier或X-Client-Id路由]
REG --> AD1[YUNDA Adapter]
REG --> AD2[YTO Adapter]
REG --> AD3[Aggregator Adapter\n(快递鸟/快递100)]
AD1 --> ING[EventIngestService\n幂等/映射/入库]
AD2 --> ING
AD3 --> ING
ING --> DB
PJ[PollerJob\n轮询补偿] --> REG
PJ -->|queryTrack| AD1
PJ -->|queryTrack| AD2
PJ -->|queryTrack| AD3
end
subgraph ThirdParty[第三方/Mock]
MS[Mock Server 或真实承运方/聚合] -->|Webhook 推送| WH
end
```
### 回调时序图Webhook 推送 -> 入库 -> 客户端展示)
```mermaid
sequenceDiagram
participant TP as Mock/第三方
participant WH as WebhookController
participant REG as AdapterRegistry
participant AD as CarrierAdapter
participant ING as EventIngestService
participant DB as LogisticsDB
participant API as 平台查询API
participant APP as 安卓/Web
TP->>WH: POST /webhook/express/status (headers + body)
WH->>REG: resolveAdapter(carrier/X-Client-Id)
REG->>AD: getAdapter()
WH->>AD: verify + parseWebhook(rawBody)
AD-->>WH: NormalizedEvent[]
WH-->>TP: 200 OK (快速返回)
WH->>ING: ingest(NormalizedEvent[])
ING->>DB: upsert events (幂等去重)
APP->>API: GET /api/v1/delivery/express/track?order_no=...
API->>DB: query events (order_no/tracking_no)
DB-->>API: events ordered by event_time
API-->>APP: timeLine(status_history + last_synced_at)
```
备注Mermaid 图在不支持的 Markdown 渲染器中会降级为代码块,不影响内容阅读。
## 十三、附录:不同第三方 API 形态示例(用于理解差异)
说明:以下为“常见形态示例”,用于帮助团队理解不同第三方接口在鉴权、字段与能力上的差异。
- 不保证与任一承运方/聚合平台的官方文档 100% 一致;对接时必须以官方文档、沙箱与实际回文为准。
- 推荐做法:在平台侧实现 Adapter/Mapper把第三方差异映射为本文定义的统一事件模型再入库。
### 13.1 聚合平台常见形态如快递100/快递鸟类)
典型能力:
- 轨迹查询 API传入 `company_code + tracking_no`(有些场景需要收件人手机号后四位)
- 订阅/推送:先订阅,后续以 Webhook 回调推送事件
- 鉴权:常见 `appKey + sign`MD5/HMAC或 Token
示例:轨迹查询(示例)
```http
POST https://api.aggregator.example.com/v1/track/query
Content-Type: application/json
{
"company_code": "yunda",
"tracking_no": "430123456789",
"phone_last4": "8000",
"nonce": "n_123",
"timestamp": 1738735200,
"sign": "md5_or_hmac_signature"
}
```
示例响应(示例)
```json
{
"success": true,
"tracking_no": "430123456789",
"company_code": "yunda",
"state": "in_transit",
"events": [
{ "time": "2026-02-05T10:12:00+08:00", "context": "已揽收", "location": "深圳市" },
{ "time": "2026-02-05T22:01:00+08:00", "context": "到达广州分拨中心", "location": "广州市" }
]
}
```
差异点:
- `state` / `status` 枚举不统一;事件字段可能是 `context/desc``time/timestamp`
- 不同快递公司可能要求手机号参与查询或订阅验证。
### 13.2 承运方直连常见形态(企业接口)
典型能力:
- 轨迹查询、签收回单POD查询可能拆成多个接口
- 鉴权更严格HMAC、证书、IP 白名单或 OAuth2
示例:轨迹查询(示例)
```http
POST https://open.carrier.example.com/v2/route/query
Content-Type: application/json
X-Client-Id: your_client_id
X-Timestamp: 2026-02-05T10:30:00+08:00
X-Signature: hmac_sha256(body + timestamp)
{
"tracking_no": "SF1234567890",
"include_pod": true
}
```
示例响应(示例)
```json
{
"tracking_no": "SF1234567890",
"routes": [
{ "code": "PICKED", "desc": "已揽收", "time": "2026-02-05T09:10:00+08:00", "station": "深圳XX营业点" },
{ "code": "TRANSIT", "desc": "运输中", "time": "2026-02-05T21:45:00+08:00", "station": "广州分拨中心" }
],
"pod": { "signed": false, "photo_url": null }
}
```
差异点:
- 字段更细网点编码、操作员等但不统一POD 能力通常受权限/合同影响。
### 13.3 Webhook 推送常见形态(订阅后回调)
典型能力:
- 订阅后由第三方主动推送轨迹事件到平台
- 具备重试机制,因此平台必须做幂等去重、乱序处理
示例:回调事件(示例,字段名因第三方而异)
```json
{
"tracking_no": "430123456789",
"carrier": "YUNDA",
"event_id": "evt_0001",
"event_time": "2026-02-05T22:01:00+08:00",
"event_text": "到达广州分拨中心",
"location": "广州市",
"extra": { "pod_photo": null }
}
```
接入建议:
- 无论第三方字段如何变化,平台入库前统一映射到本文“核心数据模型”,并保留 `raw_payload`
- 平台用 `event_id`(优先)或组合键(`tracking_no+event_code+event_time`)实现幂等。
### 13.4 圆通YTO物流轨迹查询接口官方文档摘要
文档入口:
- https://open.yto.net.cn/interfaceDocument/menu251/submenu258
定位:该文档描述“根据圆通运单号查询物流轨迹”的接口形态,典型属于“平台主动查询(轮询)”模式。
关键交互要点(按官方文档整理,具体以控制台配置与最新文档为准):
- 传输HTTPSPOST。
- 报文结构:请求体包含 `timestamp``param``format``sign`
- `param`:以 JSON/XML 字符串承载业务参数;轨迹查询场景下包含圆通运单号字段(示例为 `NUMBER`,一次查询一个单号)。
签名规则(按文档描述抽象):
- 参与签名的明文:`param + method + v`(其中 `method``v` 来自控制台为该接口生成的配置)。
- 将上述明文与客户密钥(`secret`)拼接后做 MD5再对 MD5 的字节结果进行 Base64 编码,得到 `sign`
- 伪公式:`sign = base64(md5((param + method + v) + secret))`
响应字段(按文档列举的 JSON 返回字段抽象):
- 运单号:`waybill_No`
- 走件时间:`upload_Time`yyyy-MM-dd HH:mm:ss
- 物流状态码:`infoContent`(示例枚举包括 GOT/ARRIVAL/DEPARTURE/SENT_SCAN/SIGNED 等)
- 物流信息文案:`processInfo`
- 城市/区县:`city``district`(可选)
- 重量:`weight`(可选)
平台侧映射建议(把第三方差异收敛到统一模型):
- 时间:`upload_Time` -> 平台 `event_time`
- 文案:`processInfo` -> 平台 `event_text`(可另存 `description`
- 状态:`infoContent` -> 平台 `event_code`(保留原码),再映射到平台统一 `status_code`(见 `状态映射表.md`
`infoContent` 到平台统一 `status_code` 的建议映射(示例):
- GOT已揽收-> IN_TRANSIT
- ARRIVAL已收入/到达)-> IN_TRANSIT中转/到达节点统一视为运输中)
- DEPARTURE已发出/离开节点)-> IN_TRANSIT
- SENT_SCAN派件-> OUT_FOR_DELIVERY
- INBOUND自提柜入柜-> IN_TRANSIT或按业务定义为 OUT_FOR_DELIVERY
- SIGNED签收成功-> DELIVERED
- FAILED签收失败-> EXCEPTION
- TMS_RETURN退回-> RETURNED
落库建议:
- 将圆通原始返回(整个数组或单条对象)保存到 `raw_payload`,便于客服/运维对照圆通官网。
- 幂等去重:若第三方无稳定 `event_id`,可用组合键 `tracking_no + infoContent + upload_Time` 生成 `dedupe_key`
### 13.5 韵达YUNDA开放平台官方文档入口与调研清单
文档入口:
- https://open.yundaex.com/api/apiDoc
说明:韵达开放平台文档页面存在较多交互式内容(需登录/控制台配置后才能看到每个接口的 `method/v/测试地址` 等关键信息)。因此本节先固化“对接时必须确认的要点清单”,避免对接过程中遗漏。
从官方文档导航可见的能力分类(用于判断覆盖范围):
- API 鉴权说明
- 电子面单、散件下单
- 物流轨迹
- 售后服务、国际件、基础服务等
韵达轨迹对接需要在控制台确认/落盘的信息(建议形成《承运方接入配置表》):
- 鉴权方式签名算法HMAC/MD5 等)、参与签名字段、编码/排序规则、是否包含时间戳与 nonce。
- 接口要素:轨迹查询接口 URL、`method``v`(如平台/第三方采用“method+version+param”体系
- 订阅/回调能力:是否支持轨迹订阅与回调推送、回调重试策略、回调验签字段。
- 返回字段:事件时间字段、事件码/状态字段、事件文案字段、地点字段(城市/网点)与可选 POD 能力。
平台侧落地方式保持不变:
- 无论韵达返回结构如何,统一通过 Adapter 映射为平台 `TrackingEvent` 领域模型入库,并保留 `raw_payload`
Mock 控制面通用错误码(示例)
- 40001: invalid_parameter
- 40002: missing_required_field
- 40901: duplicate_request
- 50001: internal_error
可选增强(非本期必需):
- 生成 OpenAPI 文档(控制面 API + Webhook 示例)
- 补齐“字段必填矩阵/容错矩阵”(配合 `drop_fields` 故障注入)