460 lines
24 KiB
Markdown
460 lines
24 KiB
Markdown
# 物流 Webhook 接收器 & Push Server(推送后台)总览
|
||
|
||
面向读者:需要理解“物流事件如何入库、如何触发消息、如何推送到 App”的后端/运维/联调同学。
|
||
|
||
本文只做**总结性说明**;细节请跳转到对应子文档与源码。
|
||
|
||
---
|
||
|
||
## 1. 这两套后台分别解决什么问题?
|
||
|
||
### Webhook 接收器(webhook-receiver)
|
||
**一句话**:把第三方/承运方推过来的“物流轨迹事件”接进来,完成验签(可选)、留痕,并把事件写入 Supabase 的 `platform_express_*` 表。
|
||
|
||
它关注的是:
|
||
- Webhook 请求能否稳定接收(可公网暴露、可控验签)
|
||
- 原始 payload 是否可追溯(用于审计/排障)
|
||
- 轨迹事件是否能归一化写入(供业务查询、消息生成)
|
||
- 运单摘要状态是否能更新(用于页面“当前状态”展示)
|
||
|
||
### Push Server(push-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` 的关键状态事件写入队列表 `notify_queue`
|
||
- 常驻 `notify-worker` 消费 `notify_queue`,按推送策略(关键状态、去噪、幂等)生成“消息中心记录/推送任务”
|
||
- 写入 `express_notifications`(push-server consumer 会复用该表进行下发)
|
||
|
||
相关实现(仓库内):
|
||
- 迁移脚本:`pages/mall/delivery/doc/需求文档/20260309_add_notify_queue_and_trigger.sql`
|
||
- 常驻 worker:`server/notify-worker.js`
|
||
|
||
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 --> TRG[DB Trigger]
|
||
TRG --> Q[(notify_queue)]
|
||
Q --> W[notify-worker]
|
||
W --> 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` 使用稳定哈希(基于 tracking_no / carrier / status_code / event_time / event_code / event_text 的截断组合),用于保证 webhook 重复回调不会重复写入事件表,从而避免重复入队/重复通知。
|
||
|
||
### 3.4 验签(可选)
|
||
- 配置 `WEBHOOK_SECRET` 后会校验:
|
||
- `X-Timestamp`、`X-Signature`
|
||
- 签名算法:`HMAC-SHA256(secret, rawBodyText + timestamp)`,输出 hex(务必使用**原始请求体文本**参与计算)
|
||
- 默认行为:验签失败不会直接拒绝入库(会在 raw 表记录 `signature_valid=false`)。
|
||
- 可选严格模式:设置 `WEBHOOK_REJECT_INVALID_SIGNATURE=true` 后,验签失败将直接返回 HTTP 401。
|
||
|
||
### 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 Server(push-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`
|
||
- 存储待发通知、状态、重试信息等(用于消息中心/推送队列)
|
||
|
||
字段语义(重要,避免联调误判):
|
||
- `status_code`:物流/业务状态(例如 `OUT_FOR_DELIVERY/DELIVERED/EXCEPTION`),由 webhook→tracking_events→notify-worker 生成。
|
||
- `send_status`:投递处理状态(`null`=待发送,`processing/retrying/success/failed/no-targets`),由 push-server consumer 读写。
|
||
|
||
迁移脚本:
|
||
- 新环节(入队+worker):`pages/mall/delivery/doc/需求文档/20260309_add_notify_queue_and_trigger.sql`
|
||
- 字段拆分(consumer 必需):`pages/mall/delivery/doc/需求文档/20260309_add_express_notifications_send_status.sql`
|
||
|
||
迁移脚本参考:`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.4.1 云函数过期/URL 变更时,改哪里?
|
||
|
||
现象:云函数链接过期或更换后,push-server 下发会出现 HTTP 4xx/5xx 或网络错误(日志里通常能看到调用 `CLOUD_FUNC_URL` 失败),并导致 `express_notifications.send_status` 长期处于 `retrying/failed`。
|
||
|
||
需要修改的配置键:
|
||
- `CLOUD_FUNC_URL`:云函数 HTTP invoke 地址(push-server consumer 与 `/api/v1/push/send` 都依赖它)
|
||
- (如云函数鉴权也变化)`PUSH_TOKEN`
|
||
|
||
配置可能存在于以下位置(按优先级理解即可):
|
||
1) **系统环境变量**(临时验证/单次启动最方便)
|
||
2) **显式配置文件**:通过 `CONFIG_FILE/CONFIG_PATH` 指定的 `.json` 或 `.env`
|
||
3) **默认配置文件**:`server/config.json`(`server/load-config.js` 会读取并注入 env)
|
||
|
||
最常用改法(推荐):修改 `server/config.json` 里的 `CLOUD_FUNC_URL`,然后重启 push-server。
|
||
|
||
PowerShell 快速验证(不改文件,先确认新 URL 可用):
|
||
```powershell
|
||
$env:CLOUD_FUNC_URL='https://new-cloudfunc.example/invoke'
|
||
node server/push-server.js
|
||
```
|
||
|
||
改完必须做的动作:
|
||
- **重启 push-server**:`CLOUD_FUNC_URL` 在进程启动时读取,改配置后不重启不会生效。
|
||
|
||
CI/自动化(如果你用了 smoke test):
|
||
- GitHub Actions / CI 里若有云函数 smoke test,通常还需要同步更新 Secrets(例如 `CLOUD_FUNC_URL`)。
|
||
|
||
最小 smoke test(确认云函数通不通):
|
||
```powershell
|
||
curl.exe -sS -X POST "$env:CLOUD_FUNC_URL" -H "Content-Type: application/json" -d "{\"token\":\"$env:PUSH_TOKEN\",\"push_clientid\":\"<DEVICE_CID>\",\"title\":\"ping\",\"content\":\"pong\",\"payload\":{}}"
|
||
```
|
||
|
||
### 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` 是否有待处理记录。
|
||
|
||
---
|
||
|
||
## 4.6 notify-worker(消息生成入队)
|
||
|
||
### 4.6.1 作用(它负责哪一段)
|
||
**一句话**:从 `notify_queue` 消费“需要生成消息的事件”,把它转换成 `express_notifications`(消息中心记录/推送任务),供 push-server consumer 后续下发。
|
||
|
||
它关注的是:
|
||
- **把事件变成消息**:把“物流事件”落成“可按 user/merchant 查询的消息记录”。
|
||
- **收件人映射**:根据运单 → 订单,解析出 `user_id` / `merchant_id`。
|
||
- **幂等入队**:对同一事件避免重复生成(靠稳定的 `message_id` upsert)。
|
||
|
||
> 边界:notify-worker **不负责推送下发**;真正调用 `CLOUD_FUNC_URL` 的是 push-server consumer。
|
||
|
||
### 4.6.2 位置与入口
|
||
- 代码:`server/notify-worker.js`
|
||
- 说明文档(更详细):`server/NOTIFY_WORKER_README.md`
|
||
|
||
### 4.6.3 核心行为(做了什么)
|
||
1) 拉取待处理队列:`notify_queue?processed_at=is.null`(按 `created_at` 升序,批量处理)。
|
||
2) 解析业务上下文:
|
||
- 查 `platform_express_waybills` 取 `order_id/order_no, tracking_no, carrier`
|
||
- 查 `ml_orders` 取 `user_id, merchant_id`
|
||
3) 为每个受众写消息:
|
||
- 若 `user_id` 存在:写 `aud='user'`
|
||
- 若 `merchant_id` 存在:写 `aud='merchant'`
|
||
- 写入方式:upsert 到 `express_notifications`(`on_conflict=message_id`),避免重复。
|
||
4) 回写队列处理结果:把 `notify_queue.processed_at` 置上,并写 `process_status/last_error`(queued/skipped/failed)。
|
||
|
||
### 4.6.4 数据依赖(读/写哪些表)
|
||
- 读取:`notify_queue`、`platform_express_waybills`、`ml_orders`
|
||
- 写入:`express_notifications`
|
||
|
||
关键字段口径:
|
||
- `notify_queue.dedupe_key`:应尽量稳定(同一事件重复到达也一致),否则会造成重复消息。
|
||
- `express_notifications.message_id`:脚本按 `aud|waybill_id|dedupe_key` 计算 hash 生成,用于幂等。
|
||
|
||
### 4.6.5 配置与运行(最小要点)
|
||
配置加载与 webhook/push-server 一致(复用 `server/load-config.js`),并支持同目录 `server/notify-worker.config.json`。
|
||
|
||
常用环境变量:
|
||
- `SUPA_URL`(必需)
|
||
- `SERVICE_ROLE_KEY` 或 `SUPA_KEY`(必需,推荐 service_role)
|
||
- `SUPA_USE_BEARER`(可选,默认 false)
|
||
- `NOTIFY_WORKER_POLL_MS`(默认 2000)
|
||
- `NOTIFY_WORKER_BATCH_SIZE`(默认 20)
|
||
- `RUN_ONCE=true`(只跑一轮便退出,适合验证)
|
||
|
||
启动示例(PowerShell):
|
||
```powershell
|
||
node server/notify-worker.js
|
||
```
|
||
|
||
### 4.6.6 常见问题(定位方向)
|
||
- 队列一直堆积:看 `notify-worker.err.log`/控制台错误;重点检查 `SUPA_URL/SERVICE_ROLE_KEY` 是否正确、表/字段是否存在。
|
||
- 都是 skipped:通常是运单找不到订单(`platform_express_waybills` 缺 `order_id/order_no` 或 `ml_orders` 查不到)。
|
||
- 重复消息:检查上游写入 `notify_queue.dedupe_key` 是否稳定(避免 `Date.now()` 这种随机值)。
|
||
- RLS/403:优先使用 `SERVICE_ROLE_KEY`,并确保 PostgREST 有权限访问相关表。
|
||
|
||
---
|
||
|
||
## 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) 监控与告警已配置(至少:连续失败、队列堆积、云函数不可达)。
|
||
|
||
---
|
||
|
||
## 5.2 联调/运维速记(2026-03-10)
|
||
|
||
下面是本仓库在“webhook → 入库 → 入队 → notify-worker 生成通知 → push-server consumer 调云函数推送到手机”端到端跑通时,最容易踩到的坑与对应结论(只写要点;细节见排障文档)。
|
||
|
||
更详细的排障与 SQL 清单:`docs/DELIVERY_E2E_TROUBLESHOOTING_20260310.md`
|
||
|
||
### 5.2.1 最常见的故障与根因(按出现频率)
|
||
|
||
1) **notify-worker 报 “order not found for waybill” 但数据库明明有订单**
|
||
- 典型根因:`ml_orders` 开了 RLS,PostgREST 用 `apikey` 查询会返回空数组(看起来像“没数据”)。
|
||
- 处理建议:生产上用 `SERVICE_ROLE_KEY` 或服务端 JWT + 正确策略;联调阶段可以临时放开/调整策略以确认链路。
|
||
|
||
2) **写 `express_notifications` upsert 报 `42P10`(ON CONFLICT 找不到唯一约束)**
|
||
- 典型根因:表上是“部分唯一索引(partial unique index)”,而 PostgREST 的 `on_conflict=message_id` 只能匹配“普通 UNIQUE 约束/索引”。
|
||
- 处理建议:确保存在普通唯一索引:`UNIQUE(message_id)`(不要带 WHERE 条件)。
|
||
|
||
3) **push-server 启动失败:`EADDRINUSE` / 7301 端口被占用**
|
||
- 典型根因:旧进程未退出、或端口被其它程序占用。
|
||
- 处理建议:先释放端口再启动;一键启动脚本会自动尝试清理 7201/7301。
|
||
|
||
4) **日志显示云函数调用 success,但手机不弹通知**
|
||
- 典型根因:下发的 `content/body` 为空或过短,在某些系统/机型上会被折叠为“不展示”。
|
||
- 处理建议:确保推送请求里至少有稳定的 `title` + `content`(本仓库已在 consumer 侧做了 content 兜底)。
|
||
|
||
5) **`send_status=no-targets`(找不到目标设备)**
|
||
- 典型根因:`push_devices` 里没有对应 `user_id/merchant_id` 的活跃 `cid`(未注册/已注销/绑定字段为空)。
|
||
- 处理建议:先用 App 触发注册接口写入设备表;若要商家端收推送,必须正确绑定 `merchant_id`。
|
||
|
||
### 5.2.2 Windows 下一键启动/停止(后台常驻)
|
||
|
||
为方便“可运维 + 可自启动”,仓库提供了 PowerShell 脚本把 3 个常驻进程统一拉起/停掉:
|
||
|
||
- 启动(会尽量释放 7201/7301,并把进程 PID 写入 runtime 文件):
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File .\server\scripts\start-delivery-backend.ps1
|
||
```
|
||
|
||
- 停止(按 PID 文件停止进程):
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File .\server\scripts\stop-delivery-backend.ps1
|
||
```
|
||
|
||
- PID 文件位置(便于运维查进程/做自启接入):
|
||
- `server/.runtime/delivery-backend.pids.json`
|
||
|
||
> 说明:脚本按自身路径定位仓库 root,因此可以在任意工作目录执行;并会按 `server/load-config.js` 的规则加载配置(最常见是 `server/config.json`)。`CLOUD_FUNC_URL` 等配置变更后需重启进程才会生效。
|
||
|
||
**脚本兼容性说明(Windows PowerShell 5.1)**
|
||
|
||
- `start-delivery-backend.ps1` 会把 PID 文件写成 **UTF-8 无 BOM**,避免 `ConvertFrom-Json` 在 5.1 下因 BOM/编码细节解析失败。
|
||
- `stop-delivery-backend.ps1` 会:
|
||
- 优先按 PID 文件停止进程,并删除 PID 文件;
|
||
- 解析异常时也会兜底尝试释放端口(7201/7301),避免“端口占用导致下次启动失败”。
|
||
|
||
**如果不停止会怎么样**
|
||
|
||
- 常见现象:下次启动报 `EADDRINUSE`(7201/7301 端口被旧进程占用)。
|
||
- 风险:重复启动多个 `notify-worker`/consumer,可能造成重复消费、重复写消息/推送,排障会非常混乱。
|
||
- 风险:旧进程仍在用旧配置/旧代码,你改了配置但看起来“不生效”。
|
||
|
||
### 5.2.3 快速复现(和联调一致的 E2E)
|
||
|
||
1) 先启动后台(见上节)。
|
||
2) 触发 webhook 测试请求:
|
||
```powershell
|
||
node .\pages\mall\delivery\webhook-server\test-send.js
|
||
```
|
||
3) 观察链路:
|
||
- webhook-receiver 日志是否收到请求;若返回 `{ ok:false, message:'waybill not found' }`,先补齐 `platform_express_waybills` 对应运单。
|
||
- notify-worker 是否把队列变成 `express_notifications`。
|
||
- push-server consumer 是否把 `send_status` 推进到 `success`(或 `no-targets/retrying/failed`)。
|
||
|
||
---
|
||
|
||
## 6. 进一步阅读(从“总览”到“可落地”)
|
||
|
||
- webhook-receiver:`pages/mall/delivery/webhook-server/README.md`
|
||
- push-server(运行与变更记录):`server/PUSH_SERVER_README.md`
|
||
- notify-worker(消息生成入队):`server/NOTIFY_WORKER_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` 可能包含敏感信息,生产上建议严格控制读取权限并记录审计。
|