整理文档及修改配置
This commit is contained in:
184
server/消息推送文档/DELIVERY_NOTIFICATION_DATA_FLOW.md
Normal file
184
server/消息推送文档/DELIVERY_NOTIFICATION_DATA_FLOW.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# 订单发货与物流通知:全链路数据流转分析文档
|
||||
|
||||
本文档详细描述了本商城系统中,用户订单在发货阶段,从底层物流提供商(快递100)触发Webhook回掉,一直到最终向用户设备下发App内推送消息的全量数据流转过程。
|
||||
|
||||
---
|
||||
|
||||
## 整体数据流图谱
|
||||
|
||||
整个数据运转被精心设计为异步且解耦的数个阶段,以确保在高并发回调或网络异常时,数据不丢失、不阻塞。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Kuaidi100 as 快递100 (Webhook)
|
||||
participant Receiver as Webhook Receiver
|
||||
participant Supabase as Supabase (PostgreSQL)
|
||||
participant DBTrigger as 数据库触发器
|
||||
participant NotifyWorker as Notify Worker
|
||||
participant PushServer as Push Server
|
||||
participant UniPush as 云函数/UniPush
|
||||
participant UserApp as 用户App
|
||||
|
||||
%% 第一阶段:外部数据接入与存储
|
||||
Kuaidi100->>Receiver: HTTP POST 物流状态更新
|
||||
Note over Kuaidi100,Receiver: 验证签名(sign),解析参数
|
||||
Receiver->>Supabase: 插入/更新 platform_express_tracking_events
|
||||
Supabase-->>Receiver: 存入成功返回
|
||||
Receiver-->>Kuaidi100: 200 OK 返回给快递平台
|
||||
|
||||
%% 第二阶段:事件触发与入队
|
||||
Supabase->>DBTrigger: 触发器: event_to_queue_trigger
|
||||
Note over DBTrigger: 监听到 tracking_events 写入/更新<br>(条件检查:特定物流状态等)
|
||||
DBTrigger->>Supabase: 插入新任务至 notify_queue 表
|
||||
|
||||
%% 第三阶段:队列消费与通知生成
|
||||
loop 轮询读取队列 (10秒/次)
|
||||
NotifyWorker->>Supabase: 捞取 status='pending' 的任务
|
||||
NotifyWorker->>Supabase: 分析业务数据 (查ml_orders等)
|
||||
Note over NotifyWorker: 数据转换、格式化模板内容
|
||||
NotifyWorker->>Supabase: 将生成的推送记录写入 express_notifications 表
|
||||
NotifyWorker->>Supabase: 更新 notify_queue.status 为 'processed'
|
||||
end
|
||||
|
||||
%% 第四阶段:消息分发与远端推送
|
||||
loop 轮询待推送通知 (10秒/次)
|
||||
PushServer->>Supabase: 捞取 push_status='pending' 的记录
|
||||
PushServer->>Supabase: 结合 push_devices 表查找用户cid
|
||||
PushServer->>UniPush: HTTP POST (附带云函数URL & 推送体)
|
||||
Note over PushServer,UniPush: 触发 unicloud 上的云函数
|
||||
UniPush->>UserApp: 发放手机系统推送通知 (通道下发)
|
||||
UniPush-->>PushServer: 返回推送结果 (成功/失败)
|
||||
PushServer->>Supabase: 更新 express_notifications 推送状态 (delivered/failed)
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 阶段一:外部回调接入与原始数据落库
|
||||
|
||||
### 1. Webhook 请求接收
|
||||
- **服务组件**:`server/webhook-receiver.js` ( 基于 Node.js + Express )
|
||||
- **触发源头**:快递100平台(订阅回调业务)。
|
||||
- **行为过程**:
|
||||
- 接收 HTTP POST 请求(默认端口 3001,路径 `POST /webhook/kuaidi100`)。
|
||||
- **参数验证**:解析 `param` 数据和 `sign` 签名,比对系统中配置的 secret 确保数据安全,非假冒请求。
|
||||
- **源头日志留痕**:原始的 JSON 报文会被原封不动地记录在 **`platform_express_event_raw`** 表中。这是追踪客诉(如“没收到通知”时排查是否丢件)的**第一查证现场**。
|
||||
- **数据提取**:解包得到快递单号、最新物流轨迹节点、物流状态编码(如:在途、派件、签收等)。
|
||||
|
||||
### 2. 落入业务核心库
|
||||
- 表目标:主要涉及 `platform_express_tracking_events`(物流事件记录)或 `platform_express_waybills`(运单主表)。
|
||||
- **处理要求**:将接收到的非结构化或外部结构化数据,转译为本系统认可的数据类型,并 Upsert(更新或插入)入库。至此,外部调用阶段结束,返回 `200 { "result": true, "returnCode": "200", "message": "成功" }` 予以确认,这确保了外部平台不再重复推送同一条消息。
|
||||
|
||||
---
|
||||
|
||||
## 阶段二:数据库内部事件流转(触发器驱动)
|
||||
|
||||
为了让核心写入逻辑与后续繁琐的通知逻辑解耦,系统利用了 PostgreSQL 强大的触发器(Trigger)能力。
|
||||
|
||||
### 1. 触发器监听
|
||||
- **关联脚本**:`20260309_add_notify_queue_and_trigger.sql` (位于 `pages/mall/delivery/doc/需求文档/`)
|
||||
- 当 `platform_express_tracking_events` 发生 `INSERT` (或特定 `UPDATE`) 时,触发底层 SQL 逻辑。
|
||||
|
||||
### 2. 队列填充 (`notify_queue`)
|
||||
- 触发器脚本 `event_to_queue_trigger` 负责初步筛选。它可能会排除一些毫无业务通知价值的冗余轨迹节点,只抓取关键节点(例如:“正在派发”、“已签收”)。
|
||||
- 把关联的业务主键(如:运单号、关联订单 ID)、事件类型,作为 JSON payload,以极快的速度 `INSERT` 进入 `notify_queue` 表,设定初始状态为 `pending`。
|
||||
- **优点**:Webhook API 的响应时间不再受后续通知逻辑(查询用户组、查找文案模板)的拖累。
|
||||
|
||||
---
|
||||
|
||||
## 阶段三:通知生成流水线(Notify Worker 消费任务)
|
||||
|
||||
这是业务逻辑最密集的部分,将纯粹的“发生了一件事”转化为具体的“应该发给谁、发什么内容”的推送任务数据。
|
||||
|
||||
### 1. 任务抓取与消费
|
||||
- **服务组件**:`server/notify-worker.js` ( 独立的 Node.js 守护进程 )
|
||||
- Worker 在后台持续运行,定期(例如每 10 秒)扫描 `notify_queue` 中处于 `pending` 状态的任务项。
|
||||
|
||||
### 2. 业务溯源与数据拼接
|
||||
- Worker 锁定任务后,从 Payload 提取运单 ID / 订单 ID。
|
||||
- **关联查询**:向数据库发起查询,结合 `ml_orders` (订单表)、可能的用户属性表、商品主数据表,查询出:买家的 `user_id`,商品名称摘要等内容。
|
||||
- **模版渲染**:例如,将“订单 + 运单号处于签收状态”渲染为中文文本:“您购买的商品【xxx等】已由用户签收”。
|
||||
|
||||
### 3. 持久化至推送队代表(加工日志留存)
|
||||
- 组装完毕后,Worker 将成型的通知指令,插入到真实的通知表 `express_notifications` 中,其包括目标 `user_id`、具体的 `title`、`body`。
|
||||
- 把该条新建的记录标记为 `push_status = 'pending'`。
|
||||
- 最后,将 `notify_queue` 中的原始任务记录状态更新为 `processed`。如果中间报错(例如解析运单或匹配订单失败),则会标记为 **`failed`** 并带有 **`error_msg`**,此表充当了**加工阶段的完整状态日志**。
|
||||
|
||||
---
|
||||
|
||||
## 阶段四:物理推送与结果反馈(Push Server 分发)
|
||||
|
||||
该阶段将上阶段准备好的具体通知内容,通过第三方通道真正发送到用户设备。
|
||||
|
||||
### 1. 推送目标检索
|
||||
- **服务组件**:`server/push-server.js` ( 基于 Node.js + Express )
|
||||
- Push Server 定期轮询 `express_notifications` 中 `push_status = 'pending'` 的新通知项。
|
||||
|
||||
### 2. 补齐通道标识(CID)
|
||||
- 系统根据需通知的目标 `user_id`,去 `push_devices`(Client ID 表) 中查找此用户最后活跃设备的绑定的 `cid`。如果没有找到合适的 `cid`,可能直接将该通知记为 `failed` 或转为站内信处理。
|
||||
|
||||
### 3. 请求云函数下发 (DCloud / uniCloud 侧)
|
||||
- **关联配置**:`server/config.json` 的 `CLOUD_FUNC_URL`。
|
||||
- Push Server 封装目标 `cid` 列表、文章标题 (title)、摘要主体 (body)、可能的附加跳转数据(payload 数据),构建一个 HTTP POST 载荷。
|
||||
- 通过网络请求抛给特定的 **DCloud 阿里云/腾讯云空间** 上部署的 **uniCloud 云函数服务**。
|
||||
- **核心下发通道技术栈**:因为本商城前端使用的是 uni-app x 架构,该云函数服务内包装了 DCloud 官方提供的 **UniPush 2.0 管理端 SDK**。由其直接对接个推(Getui)和包括华米OV(华为、小米、OPPO、vivo)以及苹果(APNs)在内的各大手机硬件厂商原生的**离线下发通道**,以保证 App 无论是否在后台驻留都能收到消息。
|
||||
|
||||
### 4. 结果更新与下发日志跟踪
|
||||
- 网络请求完成后,Push Server 会接受到云函数关于各客户端接收/下发的汇报。
|
||||
- 该结果会作为**下发日志**回写至对应的 `express_notifications` 记录中:
|
||||
- 如果成功,`push_status` 变为 **`delivered`**。
|
||||
- 如果失败,`push_status` 变为 **`failed`**,且将具体的错误文本(例如 `"invalid cid"`,无效的通道ID)存入 **`provider_response`** 字段中。这直接揭示了**为什么下发失败**。
|
||||
- **至此,一条由快递公司产生的外部轨迹更新,完美结束了内部的长尾运转,闭环成了用户手机屏幕上的一次震动提醒。**
|
||||
|
||||
---
|
||||
|
||||
## 业务溯源与客诉排查指南
|
||||
|
||||
本架构天然具备完整的数据追溯能力,所有生命周期均在数据库中永久留痕。在处理诸如“用户投诉没有收到签收推送”的问题时,运维人员可按照以下步骤依序溯源:
|
||||
|
||||
1. **查源头是否丢件 (`platform_express_event_raw`)**:根据快递单号检索此表,确认底层物流商是否真的推送了回调数据。如果没有记录,说明是快递公司没发/漏发。
|
||||
2. **查系统是否消费失败 (`notify_queue`)**:根据单号检索,看是否存在对应记录。如果在,看 `status` 字段。如果是 `pending`,说明 `notify-worker` 服务挂了没消费;如果是 `failed`,请查看 `error_msg`。
|
||||
3. **查终端是否下发失败 (`express_notifications`)**:如果前面都正常,看此表的 `push_status`。如果是 `pending` 说明云函数未执行/Push Server离线;如果是 `failed`,直接查看 `provider_response` 确认厂商阻断或CID失效的具体原因。
|
||||
|
||||
---
|
||||
|
||||
## 架构演进与为何不使用 Redis 的说明
|
||||
|
||||
针对很多大厂常见的推送后台中大量使用 Redis 的现状,本系统出于以下考量采取了**“去中间件化”的轻量级 PostgreSQL 强依赖架构**:
|
||||
|
||||
1. **队列平替**:大厂常将 `notify_queue` 的待推消息放在 Redis(如 List/Stream)。我们直接利用 Supabase PostgreSQL 的实体表 `notify_queue` 结合 10 秒级定时轮询 (Poll) 实现了队列。
|
||||
- **优势**:数据强一致性,发生断电/宕机等灾难时消息绝对不丢;极大地节约了项目运维成本,不需要单独起 Redis 实例或关心 RDB/AOF 策略。
|
||||
- **容限**:在单日处理数万至十万级流转的状态下,这类轮询对 PostgreSQL 的 IO 压力微乎其微。只有当出现“秒杀级别数十万并发群发”时,才需演进到 Redis 消息队列。
|
||||
2. **设备缓存查找平替**:查找 `userId -> cid` 虽然是典型的 KV 场景,但通过 Supabase 的索引表直接查询依然能保证极低的延迟响应。
|
||||
|
||||
这套设计对于我们十万级规模商城是性价比极高、免维护且极其稳壮的 Enterprise-Ready (企业级可用)的轻量化方案。
|
||||
|
||||
---
|
||||
|
||||
## 系统容量预期与未来性能优化指南
|
||||
|
||||
随着业务长期运行和单量增长,本套无中间件架构在某些极限边界上可能会暴露出其物理瓶颈。为了防患于未然,现将已知的极限边缘与预案说明如下,供未来的后端维护者参考:
|
||||
|
||||
### 1. 历史数据爆炸与日志表清理(DB 膨胀约束)
|
||||
由于本系统利用 `platform_express_event_raw`、`notify_queue` 和 `express_notifications` 等实体表承担了主要的业务流转与日志溯源功能,它们是“只增不减”的。
|
||||
- **隐患**:随着每天单量的累积,单表数据可轻易超过百万条,导致查询缓慢、索引膨胀或耗尽 Supabase 数据库的物理容量。
|
||||
- **未来演进四大方案(由简到强建议)**:
|
||||
1. **方案一:轻量级定时硬清理(起步期适用)**:在 Supabase 面板开启 `pg_cron`,每晚跑定时 SQL 脚本,直接 `DELETE` 掉 60 天前的历史记录。优点是零门槛实施,缺点是追溯期短且引发表碎片。
|
||||
2. **方案二:冷热数据分离归档(小型商城标配)**:创建同构归档表(如 `notify_queue_archive`),每晚将旧数据从热表迁移至归档表再删热表。确保生产环境极快的同时,运维能查陈年老账。
|
||||
3. **方案三:声明式表分区 Table Partitioning(高配优雅解)**:将主表按月改造成分区表层级(如 `queue_2026_01`)。业务代码零修改,删数据只需直接 `DROP TABLE` 具体月表,毫秒级释放巨大闲置空间,完全无磁盘碎片。
|
||||
4. **方案四:日志流外部卸载(大厂海量终极解)**:挂载监听 Supabase Realtime Webhook 或者 PostgreSQL 的逻辑复制流(Logical Replication),将所有的日志插入和变化发往第三方 Elasticsearch 或云厂商极其廉价的 OSS 对象存储桶里长存。主事务数据库里发完件立马删空,只留运行中的活跃数据。
|
||||
|
||||
### 2. 避免队列发生“毒药死循环” (Poison Message)
|
||||
- **隐患**:本纯 DB 驱动的队列体系未完全建立如同专业死信队列(Dead Letter Queue)那般的重试计数和隔离。如果在运行 `notify-worker` 的拼接数据逻辑中,有一条“破损订单数据”触发了未能 Catch 住的致命错(Fatal Error) 使得 Node.js 进程崩塌,进程通过守护脚本重启后,这个任务依然是 `pending`。Worker 重新抓取再次崩溃,就会形成堵死同批其他订单消息的“毒药效应”。
|
||||
- **未来预案/开发原则**:在 `notify-worker.js` 中新增、修改关于订单及商品表的复杂关联解析代码时,**必须将对单条业务的独立 Try-Catch 包裹进最内层的批次 `map` 中**,失败了直接给它标记为 `failed` 并写入 `error_msg` 抛弃,**绝不能**向外层抛出导致整个批次断裂。
|
||||
|
||||
### 3. 排队性能上限设置 (Scale-up)
|
||||
- **隐患**:当前我们提供的起服动作 (`start-delivery-backend.ps1`) 均为单线程执行(一个 Node 进程)。一分钟只能通过轮询处理规定上限条数的推送,若双十一爆发发货,可能会导致发信严重滞后(排队几小时后才收到)。
|
||||
- **未来预案**:
|
||||
1. 第一个手段是修改配置:适当调短轮询间隙时间 (`POLL_MS`),或加大每次查询限制 (`BATCH_SIZE`)。
|
||||
2. 第二个手段是多实例并发:如果要开多个 Worker 同时跑以消化消息波峰。在做这个扩展前,必须保证通过修改 SQL 将获取队列的查询改造为 `SELECT ... FOR UPDATE SKIP LOCKED`。以此实现单条消息在数据库级别的悲观跨行抢占,防止多个 Worker 获取并重复发送同一个通知。
|
||||
|
||||
本设计在每个阶段都支持“断层续接”:
|
||||
1. 若 Webhook receiver 崩溃,外部快递100会按照自己的退避算法发起重试,保证事件能抵达网络库。
|
||||
2. 若 Notify worker 崩溃,数据库里的 `notify_queue` 会静静堆积 pending 数据,一旦恢复,Worker 会把积压数据逐步消化。
|
||||
3. 若 Push server 网络故障或 云函数URL失效,`express_notifications` 依然保留 `pending`,待网络或配置恢复(如更新 `CLOUD_FUNC_URL`)后自动补偿发送。
|
||||
@@ -441,6 +441,7 @@ node .\pages\mall\delivery\webhook-server\test-send.js
|
||||
|
||||
## 6. 进一步阅读(从“总览”到“可落地”)
|
||||
|
||||
- 数据流详细流转图谱与分析:`server/消息推送文档/DELIVERY_NOTIFICATION_DATA_FLOW.md`
|
||||
- webhook-receiver:`pages/mall/delivery/webhook-server/README.md`
|
||||
- push-server(运行与变更记录):`server/PUSH_SERVER_README.md`
|
||||
- notify-worker(消息生成入队):`server/NOTIFY_WORKER_README.md`
|
||||
|
||||
@@ -71,8 +71,12 @@ curl -X POST http://localhost:7301/api/v1/notifications -H "Content-Type: applic
|
||||
|
||||
重要链接
|
||||
- push-server 源码: [server/push-server.js](../push-server.js)
|
||||
- notify-worker: [server/notify-worker.js](../notify-worker.js)
|
||||
- webhook-receiver: [pages/mall/delivery/webhook-server/README.md](../../pages/mall/delivery/webhook-server/README.md)
|
||||
- 完整全链路数据流转分析:`DELIVERY_NOTIFICATION_DATA_FLOW.md`
|
||||
- 运行与变更记录:`PUSH_SERVER_README.md`
|
||||
- 消息生成Worker服务:`NOTIFY_WORKER_README.md`
|
||||
- 云函数集成指引:`UNI_PUSH2_CLOUD_FUNCTION.md`
|
||||
- 排错指南:`DELIVERY_E2E_TROUBLESHOOTING_20260310.md`
|
||||
- webhook-receiver:`../../pages/mall/delivery/webhook-server/README.md`
|
||||
- 部署脚本: [server/scripts/start-delivery-backend.ps1](../scripts/start-delivery-backend.ps1)
|
||||
- 迁移脚本与 SQL: pages/mall/delivery/doc/需求文档/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user