-- M8 基础设施:站内消息(Inbox) -- 通知 worker 将 outbox 事件物化为站内消息;管理员可广播;门户/管理员收件箱按行读回执。 -- inbox_messages:一条消息一个收件人(sender 广播时枚举收件人逐行落库)。 -- recipient_kind + recipient_user_id 区分 admin/portal 两个身份表;不设 FK(跨表)。 CREATE TABLE IF NOT EXISTS gateway.inbox_messages ( id uuid PRIMARY KEY, source_event_id uuid, -- 来源 outbox 事件;广播无来源事件为 NULL recipient_kind text NOT NULL CHECK (recipient_kind IN ('admin', 'portal')), recipient_user_id uuid NOT NULL, -- 具体收件人(admin 或 portal 账号 id) sender_type text NOT NULL CHECK (sender_type IN ('system', 'admin', 'portal')), category text NOT NULL CHECK (category IN ('system', 'approval', 'task_result', 'resource')), title text NOT NULL CHECK (length(title) BETWEEN 1 AND 256), body text NOT NULL DEFAULT '' CHECK (length(body) <= 4000), link text NOT NULL DEFAULT '' CHECK (length(link) <= 512), payload jsonb, read_at timestamptz, -- 已读回执;NULL=未读 created_at timestamptz NOT NULL DEFAULT clock_timestamp() ); -- 幂等:同一来源事件对同一收件人只落一条(重放不重复);NULLS NOT DISTINCT 兜底 NULL 收件人。 CREATE UNIQUE INDEX IF NOT EXISTS inbox_messages_source_event_idx ON gateway.inbox_messages (source_event_id, recipient_kind, recipient_user_id) NULLS NOT DISTINCT WHERE source_event_id IS NOT NULL; -- 收件箱按收件人倒序 + 未读数 CREATE INDEX IF NOT EXISTS inbox_messages_recipient_idx ON gateway.inbox_messages (recipient_kind, recipient_user_id, created_at DESC); CREATE INDEX IF NOT EXISTS inbox_messages_unread_idx ON gateway.inbox_messages (recipient_kind, recipient_user_id) WHERE read_at IS NULL;