Files
medical-mall/docs/DELIVERY_E2E_TROUBLESHOOTING_20260310.md
not-like-juvenile e67016a6f4 消息推送
2026-03-10 16:39:50 +08:00

216 lines
7.4 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 → 通知入库 → 推送到手机联调问题记录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;
```
**恢复建议(生产化方向)**
- 不建议长期关闭 RLS。
- 建议让后端使用可被 PostgREST 正确解码/信任的 service_role 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. 症状PGRST301JWT 无法解码)导致访问异常
**现象**
- 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` 逻辑
---
## 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。
如果 `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;
```