修改消息后台的启动和停止文件并整理文档
This commit is contained in:
257
server/消息推送文档/DELIVERY_E2E_TROUBLESHOOTING_20260310.md
Normal file
257
server/消息推送文档/DELIVERY_E2E_TROUBLESHOOTING_20260310.md
Normal file
@@ -0,0 +1,257 @@
|
||||
# 物流 Webhook → 通知入库 → 推送到手机:联调问题记录(2026-03-10)
|
||||
|
||||
本文记录本次将链路跑通时遇到的阻塞点、根因与修复方式,便于后续环境复现与排障。
|
||||
|
||||
目标链路:
|
||||
1) webhook-receiver 接收物流回调并落库
|
||||
2) DB/服务侧写入 `notify_queue`
|
||||
3) `server/notify-worker.js` 消费 `notify_queue`,生成 `express_notifications`
|
||||
4) `server/push-server.js` 的 consumer 轮询 `express_notifications`,调用 `CLOUD_FUNC_URL` 推送到设备 `cid`
|
||||
|
||||
---
|
||||
|
||||
## 1. 症状:notify-worker 报 `order not found for waybill`
|
||||
|
||||
**现象**
|
||||
- `notify_queue.process_status=skipped` 或 `failed`
|
||||
- `last_error=order not found for waybill`
|
||||
|
||||
**根因**
|
||||
- `public.ml_orders` 启用了 RLS。
|
||||
- 后端通过 PostgREST 仅使用 `apikey`(或无法解码的 Bearer)访问时,`auth.uid()` 为空,RLS policy 不成立,导致对 `ml_orders` 的查询返回 `200 []`(看起来像“无数据”,实为被行级安全过滤)。
|
||||
|
||||
**验证方法**
|
||||
- 对比:SQL Editor 里能查到订单,但经 `/rest/v1/ml_orders` 查不到。
|
||||
|
||||
**临时修复(用于快速跑通链路)**
|
||||
```sql
|
||||
ALTER TABLE public.ml_orders DISABLE ROW LEVEL SECURITY;
|
||||
```
|
||||
|
||||
> 备注:你当前观察到“把安全策略关上就可以进行推送”,就是因为 notify-worker 能重新读到 `ml_orders.user_id/merchant_id` 以解析收件人。
|
||||
|
||||
**恢复建议(生产化方向)**
|
||||
- 不建议长期关闭 RLS。
|
||||
- 推荐做法(本仓库已支持):
|
||||
- 保持全局 `SUPA_USE_BEARER=false`(避免影响其它服务/自托管网关 JWT 配置不一致带来的 `PGRST301`)
|
||||
- 仅对 notify-worker 开启 Bearer:在 `server/config.json` 设置 `NOTIFY_WORKER_SUPA_USE_BEARER=true`
|
||||
- 确保 `SUPA_KEY`/`SERVICE_ROLE_KEY` 是可用的 service_role key(JWT)
|
||||
- 备选:通过 RPC/安全视图完成必要查询;避免依赖 `DISABLE RLS`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 症状:写入 `express_notifications` 时报 `42P10`
|
||||
|
||||
**现象**
|
||||
- `notify_queue.process_status=failed`
|
||||
- `last_error` 包含:
|
||||
- `there is no unique or exclusion constraint matching the ON CONFLICT specification`
|
||||
|
||||
**根因**
|
||||
- notify-worker / push-server 使用 PostgREST upsert:
|
||||
- `POST /rest/v1/express_notifications?on_conflict=message_id`
|
||||
- 但数据库侧 `express_notifications(message_id)` 只有“部分唯一索引”(例如 `WHERE message_id IS NOT NULL`),无法匹配 `ON CONFLICT(message_id)`,触发 `42P10`。
|
||||
|
||||
**修复(改为普通唯一索引)**
|
||||
- 执行脚本:
|
||||
- `pages/mall/delivery/doc/需求文档/20260310_fix_express_notifications_on_conflict_message_id.sql`
|
||||
|
||||
核心 SQL(摘要):
|
||||
```sql
|
||||
DROP INDEX IF EXISTS public.ux_express_notifications_message_id;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_express_notifications_message_id
|
||||
ON public.express_notifications(message_id);
|
||||
```
|
||||
|
||||
**修复后验证**
|
||||
- `notify_queue` 最新记录不再失败;`express_notifications` 能正常生成/更新。
|
||||
|
||||
---
|
||||
|
||||
## 3. 症状:push-server 启动失败 `EADDRINUSE 0.0.0.0:7301`
|
||||
|
||||
**现象**
|
||||
- `node server/push-server.js` 直接退出
|
||||
- 提示端口占用
|
||||
|
||||
**根因**
|
||||
- 7301 端口已有 node 进程占用(重复启动或遗留进程)
|
||||
|
||||
**排查/处理(Windows)**
|
||||
```powershell
|
||||
netstat -ano | Select-String ":7301 "
|
||||
Get-Process -Id <PID>
|
||||
Stop-Process -Id <PID> -Force
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 症状:PGRST301(JWT 无法解码)导致访问异常
|
||||
|
||||
**现象**
|
||||
- PostgREST 返回 `PGRST301` 或“JWT cannot be decoded/verified”
|
||||
|
||||
**根因**
|
||||
- 在自托管/网关配置不一致时,手动发送 `Authorization: Bearer <SUPA_KEY>` 可能触发 JWT 解码失败。
|
||||
- 本仓库对 Supabase REST 调用默认只发 `apikey`,仅在显式 `SUPA_USE_BEARER=true` 时才附加 Bearer。
|
||||
|
||||
**处理建议**
|
||||
- 保持 `SUPA_USE_BEARER=false`(默认)
|
||||
- 使用可用的 `apikey/service_role key` 走 Kong key-auth
|
||||
|
||||
相关实现:
|
||||
- `server/push-server.js` 的 `supaFetch` 逻辑
|
||||
|
||||
---
|
||||
|
||||
## 4.1 症状:`503 PGRST002`(PostgREST schema cache / 数据库不可用)
|
||||
|
||||
**现象**
|
||||
- webhook-receiver 返回 `502 { ok:false, message:'... HTTP 503 {"code":"PGRST002" ... }' }`
|
||||
- 或 worker 日志持续刷:
|
||||
- `HTTP 503 {"code":"PGRST002","message":"Could not query the database for the schema cache. Retrying."}`
|
||||
|
||||
**根因**
|
||||
- 这不是 RLS 问题。
|
||||
- 这是 **PostgREST 无法连接到 Postgres**(或数据库正在重启/不可用/连接池耗尽/磁盘满等),因此无法构建/刷新 schema cache。
|
||||
|
||||
**快速验证**
|
||||
```powershell
|
||||
# 直连 REST 读一条数据(只要能返回 200/空数组就说明 REST 已恢复)
|
||||
$cfg=Get-Content .\server\config.json -Raw | ConvertFrom-Json
|
||||
$u=($cfg.SUPA_URL.TrimEnd('/')) + '/rest/v1/platform_express_waybills?select=id&limit=1'
|
||||
Invoke-WebRequest -UseBasicParsing -Uri $u -Headers @{ apikey=$cfg.SUPA_KEY; Accept='application/json' } -Method GET
|
||||
```
|
||||
|
||||
**处理建议(按优先级)**
|
||||
1) 在 Supabase 部署机器上确认 Postgres 是否正常(能否连接、磁盘是否满、CPU/内存是否打满)。
|
||||
2) 重启 PostgREST/rest 服务(多数情况下会自动恢复 schema cache)。
|
||||
3) 如使用 docker-compose:优先重启 db 与 rest(容器名因环境不同可能不同)。
|
||||
4) 待 REST 恢复后再重跑本仓库的 3 个 Node 后台(或等待它们自动恢复)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 症状:数据库显示 `send_status=success`,但手机没弹通知
|
||||
|
||||
**现象**
|
||||
- `express_notifications.send_status=success`
|
||||
- 但手机端没有系统通知/横幅
|
||||
|
||||
**常见原因**
|
||||
1) 推送内容为空(部分通道/客户端不会展示空内容通知)
|
||||
2) 客户端处于前台/透传模式:消息到达但不自动弹系统通知(需要客户端本地展示)
|
||||
3) 手机系统通知权限/渠道被关闭、省电策略限制
|
||||
|
||||
**本次定位与修复**
|
||||
- notify-worker 生成的 `express_notifications` 通常只有 `event_text_safe`(可作为标题),body/content 可能为空。
|
||||
- push-server consumer 调云函数时原先传入的 `content` 可能为空,导致“云函数返回成功,但不展示”。
|
||||
- 已在 `server/push-server.js` 增加兜底:当 body/content 为空时,使用 title/event_text_safe 作为 content。
|
||||
|
||||
验证方法(直接调用云函数对 CID 推送)
|
||||
- 观察云函数返回中是否包含类似 `successed_online`;并对比手机是否实际展示。
|
||||
|
||||
---
|
||||
|
||||
## 6. 症状:merchant 侧 `send_status=no-targets`
|
||||
|
||||
**现象**
|
||||
- `express_notifications.aud=merchant` 的记录 `send_status=no-targets`
|
||||
- `last_error=no active devices`
|
||||
|
||||
**根因**
|
||||
- 设备绑定表 `push_devices` 仅存在 `user_id` 绑定,没有 `merchant_id` 绑定。
|
||||
|
||||
**处理建议**
|
||||
- 让商家端也调用注册接口写入 `merchant_id` 维度的设备(或插入对应记录)。
|
||||
- push-server consumer 对 merchant 的设备查询路径是:
|
||||
- `push_devices?merchant_id=eq.<recipient_id>&is_active=eq.true`
|
||||
|
||||
---
|
||||
|
||||
## 7. 端到端自检清单(最短路径)
|
||||
|
||||
1) 触发 webhook 测试:
|
||||
- `node pages/mall/delivery/webhook-server/test-send.js`
|
||||
|
||||
2) 查队列:
|
||||
- `notify_queue` 最新应为 `process_status=queued` 且 `last_error=null`
|
||||
|
||||
3) 查通知:
|
||||
- `express_notifications` 最新应生成 `aud=user` 与 `aud=merchant` 两条
|
||||
- `aud=user` 通常应推进到 `send_status=success`
|
||||
|
||||
4) 查设备:
|
||||
- `push_devices` 中对应 `user_id`/`merchant_id` 必须存在 `is_active=true` 的 `cid`
|
||||
|
||||
---
|
||||
|
||||
## 7.1 Windows 一键启动三服务(建议)
|
||||
|
||||
为了保证与联调时一致的效果,推荐用脚本一次性启动 3 个后台进程(并落日志):
|
||||
|
||||
1) 启动(会默认释放 7201/7301 端口占用):
|
||||
- `powershell -ExecutionPolicy Bypass -File .\server\scripts\start-delivery-backend.ps1`
|
||||
|
||||
2) 触发与联调一致的 webhook 测试(打到本机 7201):
|
||||
- `node .\pages\mall\delivery\webhook-server\test-send.js`
|
||||
|
||||
3) 停止:
|
||||
- `powershell -ExecutionPolicy Bypass -File .\server\scripts\stop-delivery-backend.ps1`
|
||||
|
||||
日志文件默认在仓库根目录:
|
||||
- `webhook-receiver.log/.err.log`
|
||||
- `notify-worker.log/.err.log`
|
||||
- `push-server.log/.err.log`
|
||||
|
||||
PID 文件(用于停止/排查进程)在:
|
||||
- `server/.runtime/delivery-backend.pids.json`
|
||||
|
||||
查看当前是否仍在运行(示例):
|
||||
- `Get-Content .\server\.runtime\delivery-backend.pids.json`
|
||||
- `Get-Process -Id <PID>`
|
||||
|
||||
注意:`test-send.js` 默认请求 `http://localhost:7201/webhook/express/status`,所以必须先启动 webhook-receiver。
|
||||
|
||||
### 7.1.1 脚本行为与常见坑(PowerShell 5.1)
|
||||
|
||||
- PID 文件由 `start-delivery-backend.ps1` 写入,采用 **UTF-8 无 BOM**,避免 PS 5.1 下 `ConvertFrom-Json` 因 BOM/编码细节解析失败。
|
||||
- `stop-delivery-backend.ps1` 会优先按 PID 文件停止进程,并在解析/停止异常时兜底尝试释放端口(7201/7301)。
|
||||
|
||||
如果你不先停止就重复启动,通常会出现:
|
||||
- `EADDRINUSE`(端口占用导致启动失败)
|
||||
- 多实例 worker/consumer 并发运行,导致重复消费、重复写入、重复推送(排障难度陡增)
|
||||
|
||||
如果 `test-send.js` 返回 `{ ok: false, message: 'waybill not found' }`:
|
||||
- 说明数据库里没有 `platform_express_waybills.tracking_no = 'TEST_YT_20260206_0007'` 的运单记录。
|
||||
- 处理方式:先在 `platform_express_waybills` 补一条对应 tracking_no 的运单(并关联到你的 `ml_orders`),再重试发送。
|
||||
|
||||
---
|
||||
|
||||
## 8. 常用 SQL / 操作片段
|
||||
|
||||
### 8.1 重跑某条队列记录(把 failed/skipped 重置为可再次消费)
|
||||
```sql
|
||||
UPDATE public.notify_queue
|
||||
SET processed_at = NULL,
|
||||
process_status = NULL,
|
||||
last_error = NULL
|
||||
WHERE id = '<notify_queue.id>';
|
||||
```
|
||||
|
||||
### 8.2 检查 message_id 是否存在重复(创建唯一索引前)
|
||||
```sql
|
||||
SELECT message_id, COUNT(*)
|
||||
FROM public.express_notifications
|
||||
WHERE message_id IS NOT NULL
|
||||
GROUP BY message_id
|
||||
HAVING COUNT(*) > 1;
|
||||
```
|
||||
|
||||
### 8.3 查看 `push_devices` 是否有绑定
|
||||
```sql
|
||||
SELECT *
|
||||
FROM public.push_devices
|
||||
WHERE user_id = '<user_id>' OR merchant_id = '<merchant_id>'
|
||||
ORDER BY updated_at DESC
|
||||
LIMIT 20;
|
||||
```
|
||||
Reference in New Issue
Block a user