# 推送与设备需求文档(含建表附录) 本文档为 `推送与设备需求文档.md` 的拷贝,并在末尾追加 `express_tracking_platform_upgrade.sql` 的建表示例,便于 DBA / 后端直接参考与执行。 --- (以下为需求文档原文) # 推送与设备需求文档 本文档定义平台端基于 `push_devices` 与 `express_notifications` 的推送能力需求,覆盖设备 CID 的采集/管理、物流事件驱动的消息生成与送达、隐私与验收标准。 关联文档: - 配送模块需求文档.md - 接口规范.md - 前端字段清单.md - 状态映射表.md - mall_sql/migrations/20260224_add_push_devices_and_notifications.sql ## 一、目标 - 保存每个用户/商家设备的推送 CID(支持多设备、多平台、多 appid)。 - 在轨迹事件触发时,生成消息中心记录并向对应活跃设备下发推送(通知/透传)。 - 提供消息幂等、未读/已读管理与审计能力,且严格控制敏感字段透传。 ## 二、范围 - 包含:设备注册/解绑、设备活跃检测、消息表写入、消息去重、对外推送触发接口与消息中心 API。 - 不包含:具体第三方推送厂商通道的离线能力(小米/华为等厂商通道配置为后续任务)。 ## 三、术语 - CID:Push 平台分配的 client id。 - 设备:一台移动设备或浏览器实例,用 `cid + appid` 唯一标识。 - 消息(Notification):由轨迹事件驱动的逻辑消息,写入 `express_notifications` 并可同步到消息中心与 Push 通道。 ## 四、数据表与字段(概要) 说明:详细 DDL 请参考迁移脚本:`mall_sql/migrations/20260224_add_push_devices_and_notifications.sql`。 - `push_devices` - `id, user_id, merchant_id, cid, platform, appid, is_active, last_seen_at, registration_source, created_at, updated_at` - 约束:同一 `appid` 下 `cid` 唯一;按 `user_id/merchant_id` 建索引。 - `express_notifications` - `id, aud, recipient_id, order_id, waybill_id, tracking_no, carrier, message_id, event_text_safe, status_code, event_time, payload, read_at, dedupe_key, created_at, updated_at` - 幂等:`message_id` 唯一(若存在)或 `dedupe_key` 在生成逻辑层保证不重复写入。 ## 五、REST API 设计(建议) 注:API 端点为示例,具体实现放在平台后端网关下。 1) 设备管理 - `POST /api/v1/push/register` — 注册/更新设备 请求体:`{user_id?, merchant_id?, cid, platform, appid, registration_source}` 返回:设备记录 - `POST /api/v1/push/unregister` — 解绑设备 请求体:`{cid, appid, user_id?}` - `GET /api/v1/push/devices?user_id=...` — 列出某用户活跃设备 2) 消息与消息中心 - `POST /api/v1/notifications/express/create` — 由后端服务写入消息并触发推送(通常由 EventProcessor 调用) 请求体示例: ```json { "aud":"user", "recipient_id":"", "order_id":"", "waybill_id":"", "tracking_no":"YT123...", "carrier":"YTO", "message_id":"msg_...", "event_text_safe":"包裹正在派送中,派送员预计今日到达", "status_code":"OUT_FOR_DELIVERY", "event_time":"2026-02-24T10:00:00+08:00", "payload": {"deeplink": {"path":"/pages/order/detail","query":{"order_no":"ORD_...","tab":"logistics"}}}, "dedupe_key":"waybill|event_id_or_composite" } ``` - `GET /api/v1/notifications?aud=user&recipient_id=...` — 列表 - `POST /api/v1/notifications/read` — 标记已读(按 message_id 或 recipient_id+order_id 批量) 3) 运维/调试 - `GET /api/v1/push/stats` — 推送成功率/失败率/未读数(用于监控) ## 六、消息生成与推送流程 1) 当 `platform_express_tracking_events` 写入新事件且满足推送策略(见下),平台事件处理器调用 `express_notifications.create` 写入消息表(使用 `dedupe_key` 保证幂等)。 2) 写入成功后,异步任务查询 `push_devices`(按 `aud/recipient_id/appid/is_active`)获取目标 `cid` 列表并调用推送通道(uni-push2 或后端统一推送服务)。 3) 推送返回结果写入日志并根据结果更新统计;若设备失效则将 `push_devices.is_active` 置 false(或记录探测失败)。 推送策略建议(MVP):只在状态级变化或关键状态产生消息:`SHIPPED/OUT_FOR_DELIVERY/READY_FOR_PICKUP/DELIVERED/EXCEPTION/RETURNED`。 ## 七、隐私与安全 - 不通过 push 透传 `raw_payload`、完整手机号、签名、密钥等敏感信息。 - `event_text_safe` 为由平台清洗/脱敏后的文案,任何发送给客户端的文案必须经过该字段。 - 设备绑定时不得将密钥写入前端;CID 由客户端 SDK 提供,后端仅关联 `user_id`/`merchant_id`。 - 审计:对 `push_devices` 的创建/更新/读取 `raw_payload` 访问均需记录审计日志(平台侧实现)。 ## 八、非功能需求 - 可用性:消息写入及触发应具备重试机制,推送通道建议异步队列化处理。 - 可观测性:记录每条消息的 `created_at`, `sent_status`, `sent_at`, `response_code`(推送日志由推送通道记录)。 - 可扩展性:支持按 `appid` 区分不同应用/环境,便于多包管理。 ## 九、验收标准 1) 能成功注册设备并查询到对应 `cid`(按 `user_id`)。 2) 插入一条关键轨迹事件后:在 `express_notifications` 中新增消息且按活跃设备下发推送(模拟推送通道验证)。 3) 相同事件重复到达不会产生重复消息(由 `dedupe_key` 或 `message_id` 保证)。 4) 推送 payload 不包含敏感信息(检查 `payload` 字段)。 ## 十、迁移与回滚建议 - 在执行迁移脚本前,先在测试库运行并检查索引与触发器。 - 若生产已有其它命名冲突,请先手工评估 `push_devices` 与 `express_notifications` 字段与现有表的兼容性。 ## 十一、参考 - `pages/mall/delivery/doc/需求文档/配送模块需求文档.md` - `pages/mall/delivery/doc/需求文档/接口规范.md` - `mall_sql/migrations/20260224_add_push_devices_and_notifications.sql` --- (以下为与目录中其他文档的对齐要点、触发器/队列与视图示例) ## 与目录中其他文档的对齐要点 为保证与 `配送模块需求文档.md`、`接口规范.md`、`前端字段清单.md`、`状态映射表.md` 一致,本文档做如下约定并实现兼容: - 事件模型:消息生成使用 `platform_express_tracking_events` 的归一化字段(`event_id/event_time/event_code/event_text/status_code/waybill_id`)。 - 状态口径:使用 `状态映射表.md` 中定义的 `ORDER_PLACED/SHIPPED/IN_TRANSIT/OUT_FOR_DELIVERY/READY_FOR_PICKUP/DELIVERED/EXCEPTION/RETURNED` 作为 `express_notifications.status_code` 值域。 - 字段契约:客户端仅消费 `express_notifications.event_text_safe`、`status_code`、`event_time`、`payload.deeplink`;原始 `raw_payload` 不对客户端下发(仅后台审计可见)。 - Webhook 与控制面:推送触发入口由事件处理器(或 Mock Server 控制面)调用 `POST /api/v1/notifications/express/create` 写入消息表并触发推送。 ## 自动化:事件入库 -> 消息生成(触发器示例) 建议在应用层以异步任务方式实现;以下为数据库触发器/队列化的参考实现思路(Postgres + pg_notify 或写入中间队列表): 1) 触发器:在 `platform_express_tracking_events` 插入后触发 `notify_new_tracking_event`,把 `waybill_id/event_time/status_code/event_id` 写入 `notify_queue`(或使用 `pg_notify` 发消息)。 2) 后台消费者订阅 `notify_queue`(或 LISTEN/NOTIFY),根据 `推送策略` 和 `recipient` 映射生成 `express_notifications` 并把实际推送任务入列(调用推送服务)。 示例 SQL(简化,仅供参考): ```sql -- 中间队列表(示例) CREATE TABLE IF NOT EXISTS public.notify_queue ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), waybill_id uuid NOT NULL, event_id varchar(128) NULL, status_code varchar(32) NULL, event_time timestamptz NULL, processed boolean NOT NULL DEFAULT false, created_at timestamptz NOT NULL DEFAULT now() ); -- 触发器函数:插入 notify_queue CREATE OR REPLACE FUNCTION public.trg_platform_event_after_insert() RETURNS trigger AS $$ BEGIN INSERT INTO public.notify_queue(waybill_id,event_id,status_code,event_time) VALUES (NEW.waybill_id, NEW.event_id, NEW.status_code, NEW.event_time); RETURN NEW; END; $$ LANGUAGE plpgsql; -- 建触发器(若已存在请在应用层采用幂等部署) DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_platform_events_after_insert') THEN CREATE TRIGGER trigger_platform_events_after_insert AFTER INSERT ON public.platform_express_tracking_events FOR EACH ROW EXECUTE FUNCTION public.trg_platform_event_after_insert(); END IF; END $$; ``` 说明:生产环境推荐把业务逻辑放在后台消费者(可重试、可观测),数据库触发器仅负责把事件推入可靠队列。 ## 视图与未读计数(示例) 为方便前端读取未读数与消息列表,建议创建以下视图或物化视图: 1) 用户消息视图(按 user) ```sql CREATE OR REPLACE VIEW public.vw_express_notifications_for_user AS SELECT n.* FROM public.express_notifications n WHERE n.aud = 'user'; ``` 2) 未读计数物化视图(按用户/商家) ```sql CREATE MATERIALIZED VIEW IF NOT EXISTS public.mv_unread_notifications_count AS SELECT aud, recipient_id, count(*) FILTER (WHERE read_at IS NULL) AS unread_count FROM public.express_notifications GROUP BY aud, recipient_id; -- 刷新策略:可按计划任务或在写入消息时触发快速更新 ``` ## 示例:从事件到推送的完整查询链 - 查找某 `order_id` 的活跃设备: ```sql SELECT p.* FROM public.push_devices p WHERE p.user_id = (SELECT o.user_id FROM public.ml_orders o WHERE o.id = '') AND p.appid = 'default' AND p.is_active = true; ``` - 创建消息并返回 message_id 的示例(业务层用 INSERT ... RETURNING): ```sql INSERT INTO public.express_notifications (aud, recipient_id, order_id, waybill_id, tracking_no, carrier, message_id, event_text_safe, status_code, event_time, payload, dedupe_key) VALUES ('user', '', '', '', 'TEST_123', 'YTO', 'msg_sync_123', '包裹正在派送中', 'OUT_FOR_DELIVERY', now(), jsonb_build_object('deeplink', jsonb_build_object('path','/pages/order/detail','query',jsonb_build_object('order_no','ORD_...'))), 'dedupe_...' ) RETURNING id; ``` ## 与现有文档的补充修改建议(供你选择采纳) - 在 `接口规范.md` 中增加一节:`消息中心/推送契约`,描述 `express_notifications` 的字段与 `POST /api/v1/notifications/express/create` 示例。 - 在 `前端字段清单.md` 中明确 `event_text_safe` 的清洗规则与允许的占位符(例如 `{carrier}`、`{eta}`)。 - 在 `状态映射表.md` 中加入 `推送触发级别` 一栏,列出哪些 `status_code` 会触发推送(默认为关键状态)。 ## 下一步 我可以: - 将上述触发器与视图 SQL 附加到迁移目录作为可选脚本(例如:`mall_sql/migrations/20260224_notifications_trigger_and_views.sql`),并生成 psql/PowerShell 执行命令;或 - 只生成后台消费者示例(Node.js + supabase 或 pg 客户端),负责从 `notify_queue` 消费并调用推送通道。 请告诉我要先做哪一项。 (以下为附录:平台侧建表示例摘录) ```sql -- 第三方快递轨迹(平台侧)表结构升级 (PostgreSQL + Supabase) -- 用途: 引入第三方承运方运单与轨迹事件的统一入库模型 BEGIN; CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE EXTENSION IF NOT EXISTS "btree_gin"; DO $do$ BEGIN IF to_regprocedure('public.update_updated_at_column()') IS NULL THEN CREATE OR REPLACE FUNCTION public.update_updated_at_column() RETURNS TRIGGER LANGUAGE plpgsql AS $func$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $func$; END IF; END $do$; CREATE TABLE IF NOT EXISTS public.platform_express_waybills ( id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), order_id UUID NULL REFERENCES public.ml_orders(id) ON DELETE SET NULL, order_no VARCHAR(64) NULL, carrier VARCHAR(32) NOT NULL, tracking_no VARCHAR(64) NOT NULL, source VARCHAR(16) NOT NULL DEFAULT 'mock', current_status_code VARCHAR(32) NOT NULL DEFAULT 'SHIPPED', current_status_text TEXT NULL, eta TIMESTAMP WITH TIME ZONE NULL, last_synced_at TIMESTAMP WITH TIME ZONE NULL, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), CONSTRAINT uk_platform_express_waybill UNIQUE (carrier, tracking_no) ); -- 跳过部分 COMMIT; ``` --- 作者:自动生成(可手动微调) 日期:2026-02-24