消息推送
This commit is contained in:
@@ -144,8 +144,12 @@ CREATE INDEX IF NOT EXISTS idx_express_notifications_created_at ON public.expres
|
||||
CREATE INDEX IF NOT EXISTS idx_express_notifications_read_at ON public.express_notifications(read_at);
|
||||
|
||||
-- 若使用 message_id 做幂等(外部系统/队列),则建立唯一索引
|
||||
-- 注意:这里不能使用“部分唯一索引(WHERE message_id IS NOT NULL)”,
|
||||
-- 否则 PostgREST 的 upsert `?on_conflict=message_id` 会触发 42P10:
|
||||
-- "there is no unique or exclusion constraint matching the ON CONFLICT specification"
|
||||
-- 普通 UNIQUE INDEX 仍允许多个 NULL(符合历史兼容)。
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_express_notifications_message_id
|
||||
ON public.express_notifications(message_id) WHERE message_id IS NOT NULL;
|
||||
ON public.express_notifications(message_id);
|
||||
|
||||
-- =====================================================
|
||||
-- C. 兼容性与外键(若目标表存在则添加外键约束;若不存在则保留字段)
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
-- =====================================================================================
|
||||
-- Add send_status to express_notifications
|
||||
--
|
||||
-- 目的:区分“物流状态(status_code)”与“投递处理状态(send_status)”。
|
||||
-- - status_code:业务/物流状态(SHIPPED/OUT_FOR_DELIVERY/...)
|
||||
-- - send_status:投递状态(null=待发送, processing, retrying, success, failed, no-targets)
|
||||
--
|
||||
-- 创建日期:2026-03-09
|
||||
-- =====================================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE public.express_notifications
|
||||
ADD COLUMN IF NOT EXISTS send_status VARCHAR(32) NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_express_notifications_send_status
|
||||
ON public.express_notifications(send_status);
|
||||
|
||||
-- 兼容旧实现:历史上 push-server consumer 使用 status_code 存投递状态。
|
||||
-- 迁移后 consumer 改读写 send_status,为避免把旧的 success/failed 等记录当成 pending 再次推送,做一次安全回填。
|
||||
UPDATE public.express_notifications
|
||||
SET send_status = status_code
|
||||
WHERE send_status IS NULL
|
||||
AND status_code IN ('processing', 'retrying', 'success', 'failed', 'no-targets');
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,102 @@
|
||||
-- =====================================================================================
|
||||
-- notify_queue + trigger: platform_express_tracking_events -> notify_queue
|
||||
--
|
||||
-- 目的:把“轨迹事件入库”和“消息生成/推送”解耦。
|
||||
-- - Webhook/轮询/手工写入 tracking_events 后,由触发器把关键事件入队到 notify_queue。
|
||||
-- - 常驻 worker 消费 notify_queue,生成 express_notifications(消息中心/推送任务)。
|
||||
-- - push-server consumer 轮询 express_notifications 并调用 CLOUD_FUNC_URL 进行实际下发。
|
||||
--
|
||||
-- 创建日期:2026-03-09
|
||||
-- =====================================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- 队列表:仅做“轻量入队”,避免触发器做外部 IO
|
||||
CREATE TABLE IF NOT EXISTS public.notify_queue (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
|
||||
waybill_id UUID NOT NULL,
|
||||
carrier VARCHAR(32) NULL,
|
||||
tracking_no VARCHAR(64) NULL,
|
||||
|
||||
event_id VARCHAR(128) NULL,
|
||||
status_code VARCHAR(32) NOT NULL,
|
||||
event_time TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
event_text TEXT NULL,
|
||||
source VARCHAR(16) NULL,
|
||||
|
||||
-- 与 platform_express_tracking_events 对齐的幂等键
|
||||
dedupe_key VARCHAR(256) NOT NULL,
|
||||
raw_payload JSONB NULL,
|
||||
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
processed_at TIMESTAMP WITH TIME ZONE NULL,
|
||||
process_status VARCHAR(32) NULL,
|
||||
last_error TEXT NULL,
|
||||
|
||||
CONSTRAINT uk_notify_queue_dedupe UNIQUE (waybill_id, dedupe_key)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_notify_queue_processed_at ON public.notify_queue(processed_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_notify_queue_created_at ON public.notify_queue(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_notify_queue_status_code ON public.notify_queue(status_code);
|
||||
|
||||
-- 触发器函数:入队关键状态事件
|
||||
CREATE OR REPLACE FUNCTION public.notify_new_tracking_event()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $func$
|
||||
DECLARE
|
||||
should_enqueue BOOLEAN := FALSE;
|
||||
BEGIN
|
||||
-- 推送策略(MVP):只对关键状态入队
|
||||
IF NEW.status_code IN ('SHIPPED','OUT_FOR_DELIVERY','READY_FOR_PICKUP','DELIVERED','EXCEPTION','RETURNED') THEN
|
||||
should_enqueue := TRUE;
|
||||
END IF;
|
||||
|
||||
IF should_enqueue THEN
|
||||
INSERT INTO public.notify_queue(
|
||||
waybill_id,
|
||||
carrier,
|
||||
tracking_no,
|
||||
event_id,
|
||||
status_code,
|
||||
event_time,
|
||||
event_text,
|
||||
source,
|
||||
dedupe_key,
|
||||
raw_payload
|
||||
) VALUES (
|
||||
NEW.waybill_id,
|
||||
NEW.carrier,
|
||||
NEW.tracking_no,
|
||||
NEW.event_id,
|
||||
NEW.status_code,
|
||||
NEW.event_time,
|
||||
NEW.event_text,
|
||||
NEW.source,
|
||||
NEW.dedupe_key,
|
||||
NEW.raw_payload
|
||||
)
|
||||
ON CONFLICT (waybill_id, dedupe_key) DO NOTHING;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$func$;
|
||||
|
||||
-- 触发器:tracking_events 写入后入队
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_trigger WHERE tgname = 'trigger_notify_new_tracking_event'
|
||||
) THEN
|
||||
CREATE TRIGGER trigger_notify_new_tracking_event
|
||||
AFTER INSERT ON public.platform_express_tracking_events
|
||||
FOR EACH ROW EXECUTE FUNCTION public.notify_new_tracking_event();
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,30 @@
|
||||
-- =====================================================================================
|
||||
-- Fix express_notifications upsert conflict target (message_id)
|
||||
-- 目的:修复 notify-worker / push-server 在 upsert 时遇到的 42P10:
|
||||
-- "there is no unique or exclusion constraint matching the ON CONFLICT specification"
|
||||
-- 原因:历史迁移可能创建了部分唯一索引(WHERE message_id IS NOT NULL),
|
||||
-- PostgREST 的 upsert `?on_conflict=message_id` 无法匹配该索引。
|
||||
-- 方案:改为普通 UNIQUE INDEX (message_id)。Postgres UNIQUE 允许多条 NULL,兼容旧数据。
|
||||
-- 创建日期:2026-03-10
|
||||
-- =====================================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1) 预检查:是否存在重复的非空 message_id(若存在,创建唯一索引会失败)
|
||||
-- 如有返回结果,请先人工去重后再继续执行后续语句。
|
||||
-- 示例去重策略:保留最新 created_at,其它行将 message_id 置为 NULL 或删除重复行。
|
||||
--
|
||||
-- SELECT message_id, COUNT(*)
|
||||
-- FROM public.express_notifications
|
||||
-- WHERE message_id IS NOT NULL
|
||||
-- GROUP BY message_id
|
||||
-- HAVING COUNT(*) > 1;
|
||||
|
||||
-- 2) 删除历史“部分唯一索引”(如果存在)
|
||||
DROP INDEX IF EXISTS public.ux_express_notifications_message_id;
|
||||
|
||||
-- 3) 创建普通唯一索引,让 `on_conflict=message_id` 正常工作
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS ux_express_notifications_message_id
|
||||
ON public.express_notifications(message_id);
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user