Files
medical-mall/pages/mall/delivery/doc/需求文档/推送与设备需求文档_含建表附录.md
2026-02-24 11:28:13 +08:00

13 KiB
Raw Blame History

推送与设备需求文档(含建表附录)

本文档为 推送与设备需求文档.md 的拷贝,并在末尾追加 express_tracking_platform_upgrade.sql 的建表示例,便于 DBA / 后端直接参考与执行。


(以下为需求文档原文)

推送与设备需求文档

本文档定义平台端基于 push_devicesexpress_notifications 的推送能力需求,覆盖设备 CID 的采集/管理、物流事件驱动的消息生成与送达、隐私与验收标准。

关联文档:

  • 配送模块需求文档.md
  • 接口规范.md
  • 前端字段清单.md
  • 状态映射表.md
  • mall_sql/migrations/20260224_add_push_devices_and_notifications.sql

一、目标

  • 保存每个用户/商家设备的推送 CID支持多设备、多平台、多 appid
  • 在轨迹事件触发时,生成消息中心记录并向对应活跃设备下发推送(通知/透传)。
  • 提供消息幂等、未读/已读管理与审计能力,且严格控制敏感字段透传。

二、范围

  • 包含:设备注册/解绑、设备活跃检测、消息表写入、消息去重、对外推送触发接口与消息中心 API。
  • 不包含:具体第三方推送厂商通道的离线能力(小米/华为等厂商通道配置为后续任务)。

三、术语

  • CIDPush 平台分配的 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
    • 约束:同一 appidcid 唯一;按 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=... — 列出某用户活跃设备

  1. 消息与消息中心
  • POST /api/v1/notifications/express/create — 由后端服务写入消息并触发推送(通常由 EventProcessor 调用) 请求体示例:

    {
      "aud":"user",
      "recipient_id":"<user_uuid>",
      "order_id":"<order_uuid>",
      "waybill_id":"<waybill_uuid>",
      "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 批量)

  1. 运维/调试
  • 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_keymessage_id 保证)。
  4. 推送 payload 不包含敏感信息(检查 payload 字段)。

十、迁移与回滚建议

  • 在执行迁移脚本前,先在测试库运行并检查索引与触发器。
  • 若生产已有其它命名冲突,请先手工评估 push_devicesexpress_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_safestatus_codeevent_timepayload.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简化仅供参考

-- 中间队列表(示例)
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
CREATE OR REPLACE VIEW public.vw_express_notifications_for_user AS
SELECT n.*
FROM public.express_notifications n
WHERE n.aud = 'user';
  1. 未读计数物化视图(按用户/商家)
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 的活跃设备:
SELECT p.* FROM public.push_devices p
WHERE p.user_id = (SELECT o.user_id FROM public.ml_orders o WHERE o.id = '<order_uuid>')
  AND p.appid = 'default' AND p.is_active = true;
  • 创建消息并返回 message_id 的示例(业务层用 INSERT ... RETURNING
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', '<user_uuid>', '<order_uuid>', '<waybill_uuid>', '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 消费并调用推送通道。

请告诉我要先做哪一项。

(以下为附录:平台侧建表示例摘录)

-- 第三方快递轨迹(平台侧)表结构升级 (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