Files
medical-mall/docs/DELIVERY_WEBHOOK_PUSH_SERVER_OVERVIEW.md
2026-03-09 17:27:56 +08:00

279 lines
14 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.
# 物流 Webhook 接收器 & Push Server推送后台总览
面向读者:需要理解“物流事件如何入库、如何触发消息、如何推送到 App”的后端/运维/联调同学。
本文只做**总结性说明**;细节请跳转到对应子文档与源码。
---
## 1. 这两套后台分别解决什么问题?
### Webhook 接收器webhook-receiver
**一句话**:把第三方/承运方推过来的“物流轨迹事件”接进来,完成验签(可选)、留痕,并把事件写入 Supabase 的 `platform_express_*` 表。
它关注的是:
- Webhook 请求能否稳定接收(可公网暴露、可控验签)
- 原始 payload 是否可追溯(用于审计/排障)
- 轨迹事件是否能归一化写入(供业务查询、消息生成)
- 运单摘要状态是否能更新(用于页面“当前状态”展示)
### Push Serverpush-server
**一句话**:维护“账号 ↔ 设备 CID”的映射并提供“消息入队 + 消费下发”能力,把通知可靠地投递到云函数(由云函数完成 uni-push2 真实下发)。
它关注的是:
- 设备 CID 的注册/解绑与存储(本地 JSON 回退 + Supabase 表)
- 生成推送任务(写入 `express_notifications`
- 消费推送任务并调用 `CLOUD_FUNC_URL`(重试/回写状态)
---
## 1.1 用什么做的(技术栈/依赖)
这两套服务都是“轻量 Node.js HTTP 服务”,共同特点:
- **运行时**Node.js建议 18+;你当前环境是 Node.js 22 也可以)
- **HTTP 框架**Express
- **JSON 解析**body-parser
- **与 Supabase 通信**:直接调用 PostgREST REST API`$SUPA_URL/rest/v1/...`),通过 `apikey`(可选 Bearer鉴权
- **配置加载**:复用 `server/load-config.js`,支持 `.env` / `.json` / `CONFIG_FILE` 指定
差异点:
- webhook-receiver额外用 `crypto` 做可选 HMAC 验签;主要写 `platform_express_*` 三表
- push-server额外包含“消费者轮询 + 重试退避 + 调用云函数”的逻辑;本地 JSON 作为设备表回退(`server/data/push_devices.json`
---
## 2. 整体链路(从物流事件到手机通知)
建议把链路理解为 3 段:
1) **事件入库段**Webhook 接收器负责)
- 第三方回调 → webhook-receiver → 写入/更新:
- `platform_express_event_raw`(原始留痕)
- `platform_express_tracking_events`(归一化事件事实表)
- `platform_express_waybills`(运单摘要 current_status_*
2) **消息生成段**(通常是“事件处理器/任务/触发器”,不在这两个服务里)
- 监听/轮询 `platform_express_tracking_events` 的新增事件
- 按推送策略(关键状态、去噪、幂等)生成“消息中心记录/推送任务”
- 写入 `express_notifications`(或调用 push-server 的 HTTP 接口让其写入)
3) **推送下发段**Push Server + 云函数负责)
- push-server consumer 读取 `express_notifications` 的待处理记录
- 找到目标设备(`push_devices` 中的活跃 CID
- 对每个 CID POST 到 `CLOUD_FUNC_URL`
- 云函数内部调用 uni-push2 下发 → App 收到通知
一个简化示意图:
```mermaid
flowchart LR
Third[第三方/承运方] -->|POST webhook| WH[webhook-receiver :7201]
WH --> RAW[(platform_express_event_raw)]
WH --> EV[(platform_express_tracking_events)]
WH --> WB[(platform_express_waybills)]
EV --> EP[事件处理器/任务/触发器]
EP --> N[(express_notifications)]
App[App 客户端] -->|注册CID| PS[push-server :7301]
PS --> D[(push_devices)]
PS -->|轮询待发通知| N
PS -->|POST| CF[CLOUD_FUNC_URL 云函数]
CF --> UP[uni-push2]
UP --> App
```
参考(更完整的业务口径与隐私约束):
- `pages/mall/delivery/doc/需求文档/物流消息推送方案_用户端与商家端.md`
- `pages/mall/delivery/doc/需求文档/推送与设备需求文档.md`
---
## 3. Webhook 接收器webhook-receiver
### 3.1 位置与入口
- 代码:`pages/mall/delivery/webhook-server/webhook-receiver.js`
- 说明文档:`pages/mall/delivery/webhook-server/README.md`
### 3.2 对外接口
- `POST /webhook/express/status`
- 作用:接收轨迹变更回调
- 返回:
- 成功:`{ ok: true }`
- 运单不存在(不算系统错误):`{ ok: false, message: 'waybill not found' }`HTTP 200
- Supabase 鉴权失败等HTTP 502 + `{ ok:false, message:'...' }`
- `GET /health`:健康检查
### 3.3 写库行为(核心表)
接收器在一次 webhook 调用中做三件事(顺序上:先留痕,再归一化,再更新摘要):
1) **原始留痕**:插入 `platform_express_event_raw`
- 保存carrier、tracking_no、body原始 payload、received_at、signature_valid 等
2) **定位运单**:在 `platform_express_waybills` 中查找对应记录
- 优先按 `tracking_no`,其次按 `order_no`
- 找不到时返回 `{ ok:false, message:'waybill not found' }`
3) **写入事件 & 更新运单摘要**
- 将第三方的状态字段映射到平台统一的 `status_code`(如 `OUT_FOR_DELIVERY/DELIVERED/EXCEPTION/...`
- `PATCH platform_express_waybills`:更新 `current_status_code/current_status_text/last_synced_at`
- `INSERT platform_express_tracking_events`:写入归一化事件事实数据(包含 raw_payload、dedupe_key 等)
> 注意:当前实现的 `dedupe_key` 使用 `WEBHOOK_ + Date.now()`,更偏向“留痕与追溯”;如需严格幂等(重复回调不重复入库),建议按文档口径构造稳定 dedupe_key例如 `tracking_no|event_code|event_time`)。
### 3.4 验签(可选)
- 配置 `WEBHOOK_SECRET` 后会校验:
- `X-Timestamp``X-Signature`
- 签名算法:`HMAC-SHA256(secret, bodyText + timestamp)`,输出 hex
- 目前验签失败不会直接拒绝入库(会在 raw 表记录 `signature_valid=false`),可按需要升级为“验签失败直接 4xx”。
### 3.5 配置与启动方式
接收器会先通过 `server/load-config.js` 把配置注入 `process.env`,优先级是:
1) 系统环境变量
2) `CONFIG_FILE/CONFIG_PATH` 指定的文件
3) 接收器同目录的 `webhook.config.json`
4) `server/.env``server/config.json``server/config.json.example`
常用环境变量:
- `SUPA_URL`(必需)
- `SUPA_KEY`(必需,建议使用 service_role仅后端使用
- `PORT`(默认 7201
- `SUPA_USE_BEARER`(默认 false
- `WEBHOOK_SECRET`(可选)
---
## 4. Push Serverpush-server
### 4.1 位置与入口
- 代码:`server/push-server.js`
- 说明文档(偏“可运行/可运维”):
- `server/PUSH_SERVER_README.md`
- `server/README.md`
### 4.2 核心能力
1) **设备 CID 管理**
- `POST /api/v1/push/register`:注册/更新设备(写本地 JSON并尝试 upsert 到 `push_devices`
- `POST /api/v1/push/unregister`:解绑/置 inactive
- `GET /api/v1/push/devices`:列出设备(优先 Supabase
2) **推送下发(云函数模式)**
- `POST /api/v1/push/send`
- 直接按 `cids``user_id` 发送(对每个 CID 调用 `CLOUD_FUNC_URL`
3) **通知入队 + 消费者轮询下发**
- `POST /api/v1/notifications`:写入 `express_notifications`(排队)
- consumer可选启用
- 定时轮询 `express_notifications` 的待处理记录
- 取到记录后查询目标 CID 列表
- 逐个调用 `CLOUD_FUNC_URL`,并回写状态/错误/重试次数
> 备注本仓库当前为“仅云函数模式”push-server 自身不直接对接 uni-push真实推送逻辑在云函数里。
### 4.3 数据依赖Supabase 表)
- `public.push_devices`
- 存储 `cid ↔ user_id/merchant_id``is_active`
- `public.express_notifications`
- 存储待发通知、状态、重试信息等(用于消息中心/推送队列)
迁移脚本参考:`pages/mall/delivery/doc/需求文档/20260224_add_push_devices_and_notifications.sql`
### 4.4 关键配置env
- `PORT`:默认 7301
- `SUPA_URL``SUPA_KEY`/`SERVICE_ROLE_KEY`
- `SUPA_USE_BEARER`:同 webhook-receiver默认只发 `apikey`
消费者相关:
- `ENABLE_CONSUMER=true`(或 `CONSUMER_ENABLED=true`
- `CONSUMER_POLL_MS`:轮询间隔(默认 2000
- `CLOUD_FUNC_URL`:云函数 HTTP invoke 地址(必需)
- `PUSH_TOKEN`:可选鉴权透传给云函数
- 重试配置:`MAX_RETRIES / RETRY_INITIAL_MS / RETRY_FACTOR / RETRY_MAX_MS`
### 4.5 常见问题(定位方向)
- Supabase 报 `PGRST301` / 401通常是 Bearer/JWT_SECRET 不匹配导致,优先只用 `apikey``SUPA_USE_BEARER=false`)。
- `/api/v1/push/send` 返回 `push_clientid required`:云函数侧没有正确解析请求体(常见于 HTTP 触发器把 body 放在 `event.body`)。
- 中文变 `????`Windows PowerShell 5.1 发送 JSON 编码问题,按 `server/PUSH_SERVER_README.md` 的 UTF-8 字节方式发送。
- 消费者不工作:检查 `ENABLE_CONSUMER``CLOUD_FUNC_URL` 是否配置;再查 `express_notifications` 是否有待处理记录。
---
## 5. 给联调/运维的“最小心智模型”
- Webhook-receiver 只负责:**接收** & **入库**`platform_express_*`)。
- Push-server 只负责:**设备表** & **通知队列** & **调用云函数**`push_devices/express_notifications`)。
- 中间那段“什么时候该推、推给谁、推什么文案/脱敏后字段”通常在**事件处理器/业务服务**里实现(或数据库触发器 + 消费者)。
把这三段拆开,排障会非常快:
1) webhook 是否收到?(看 webhook-receiver 日志 / raw 表)
2) tracking_events 是否写入?(看事件表)
3) notifications 是否生成?(看 express_notifications
4) push-server 是否消费?(看 push-server 日志 / 重试字段)
5) 云函数是否下发成功?(看云函数日志 / uni-push 返回)
---
## 5.1 一页摘要
### 业务价值(为什么要做)
- **提升履约感知**:物流关键节点及时触达(用户端/商家端),减少客服咨询与退款/纠纷风险。
- **可追溯**Webhook 原始留痕 + 事件事实表 + 消息队列表,出了问题能定位“卡在哪一段”。
- **解耦与可演进**Webhook 入库、消息生成、推送下发三段解耦,后续可替换推送通道/触发方式而不重写全链路。
### 范围边界(做了什么 / 没做什么)
- **已覆盖**:第三方物流事件接收入库;设备 CID 注册与管理;通知入队与消费;调用云函数(由云函数实际对接 uni-push2
- **不在本两服务内**:消息生成策略(哪些事件要推、文案/脱敏、收件人映射、幂等 dedupe_key 规则)通常由“事件处理器/业务服务/任务”负责。
### 外部依赖(上线前必须明确)
- **Supabase/Postgres**:两服务都依赖 REST 读写(需要稳定网络、正确的 key 权限、相关表已迁移)。
- **云函数**push-server 依赖 `CLOUD_FUNC_URL` 可用;云函数内部需能成功调用 uni-push2。
- **第三方/承运方**Webhook 接入参数、验签口径、回调重试策略需要对齐。
### 风险与控制点
- **密钥风险**`service_role key` 属高权限,必须只在服务器环境变量中使用;严禁进入前端/日志/截图。
- **隐私合规**raw 表与 payload 可能含敏感字段,必须限制读取权限;推送内容只允许“脱敏/清洗后的摘要”。
- **可靠性**Webhook 可重复/乱序;需要稳定的幂等策略(事件 dedupe_key、消息 dedupe_key避免重复消息与状态回退。
- **可用性**push-server consumer 失败要可重试、可观测、可告警;云函数故障不应影响主业务写库。
### 运维成本(需要投入多少人/怎么管)
- **部署形态**:两个常驻 Node 进程(端口默认 webhook 7201、push 7301建议用进程守护Windows 服务 / pm2 / docker / supervisor 任选其一)。
- **日志与排障**:至少保留 7~30 天日志;能按 request_id/message_id/tracking_no 追踪。
### 建议监控指标(可用来验收与周报)
- Webhook每分钟请求量、2xx 比例、验签失败率、写库失败率、waybill not found 比例。
- 推送待处理队列长度pending/retrying 数)、每分钟消费量、成功率、平均重试次数、失败 Top 原因HTTP 非 2xx / 超时 / 云函数业务 errCode
- 端到端:从事件入库到推送送达的 P50/P95 延迟(分钟级即可)。
### 责任分工(出现问题找谁)
- webhook-receiver对“第三方回调接入、验签、入库”负责。
- 事件处理器/业务服务:对“消息生成规则、脱敏文案、收件人映射、幂等策略”负责。
- push-server + 云函数:对“队列消费、重试、推送通道调用成功率”负责。
### 上线检查清单(最小版)
1) 表结构已迁移(`platform_express_*``push_devices``express_notifications`)。
2) 密钥仅在服务器环境变量,日志不打印敏感值。
3) webhook `/health` 正常;能写 raw 与 events运单状态能更新。
4) push-server `/health` 正常能注册设备能入队consumer 能调用云函数并回写状态。
5) 监控与告警已配置(至少:连续失败、队列堆积、云函数不可达)。
---
## 6. 进一步阅读(从“总览”到“可落地”)
- webhook-receiver`pages/mall/delivery/webhook-server/README.md`
- push-server运行与变更记录`server/PUSH_SERVER_README.md`
- push-server消费者与云函数约定`server/README.md`
- 配置加载器:`server/load-config.js`
- 业务方案与隐私口径:
- `pages/mall/delivery/doc/需求文档/物流消息推送方案_用户端与商家端.md`
- `pages/mall/delivery/doc/需求文档/推送与设备需求文档.md`
---
## 7. 安全提醒(强烈建议)
- `SUPA_KEY/service_role key` 只允许在后端/服务器环境使用,**严禁下发到前端**。
- 如需把 push-server 的发送/入队接口对外开放建议至少加一层鉴权Bearer token、IP 白名单或内网访问)。
- `platform_express_event_raw.body/raw_payload` 可能包含敏感信息,生产上建议严格控制读取权限并记录审计。