chore(msg): merge msg full implementation into main
Merge feat/msg-ai10 with complete messaging service
This commit is contained in:
@@ -1,26 +1,188 @@
|
||||
-- Msg 服务数据库初始化脚本
|
||||
-- 表:msg_notifications / msg_notification_preferences
|
||||
-- ============================================================
|
||||
-- Msg 服务数据库初始化脚本(v1.0 完整实现)
|
||||
-- 对齐 services/msg/src/notifications/notifications.schema.ts
|
||||
-- services/msg/src/shared/outbox/outbox.schema.ts
|
||||
-- 仲裁依据:02-architecture-design.md §3.1 + G11(cuid2 varchar(32))
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS msg_notifications (
|
||||
id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
user_id CHAR(36) NOT NULL,
|
||||
type VARCHAR(50) NOT NULL,
|
||||
title VARCHAR(200) NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
channel VARCHAR(20) NOT NULL DEFAULT 'in_app',
|
||||
is_read BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
metadata JSON NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_notifications_user_id (user_id),
|
||||
INDEX idx_notifications_is_read (is_read),
|
||||
INDEX idx_notifications_created_at (created_at),
|
||||
INDEX idx_notifications_user_read (user_id, is_read)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
-- 数据库已由 docker-compose 创建(msg_db),此处仅建表
|
||||
-- 若需切换数据库:USE msg_db;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS msg_notification_preferences (
|
||||
user_id CHAR(36) NOT NULL PRIMARY KEY,
|
||||
email_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
sms_enabled BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
push_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
in_app_enabled BOOLEAN NOT NULL DEFAULT TRUE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
SET NAMES utf8mb4;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
-- ============================================================
|
||||
-- 1. msg_notifications(通知主表 · 扩展)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `msg_notifications` (
|
||||
`id` VARCHAR(32) NOT NULL COMMENT 'cuid2 主键',
|
||||
`user_id` VARCHAR(32) NOT NULL COMMENT '接收用户 ID',
|
||||
`type` VARCHAR(32) NOT NULL COMMENT '通知类型: system/exam/homework/grade/attendance/mastery',
|
||||
`title` VARCHAR(255) NOT NULL COMMENT '通知标题',
|
||||
`content` TEXT NOT NULL COMMENT '通知正文',
|
||||
`channel` VARCHAR(32) NOT NULL COMMENT '渠道: in_app/email/sms/push/wechat',
|
||||
`is_read` BOOLEAN NOT NULL DEFAULT FALSE COMMENT '是否已读',
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`status` VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/sent/delivered/read/recalled/failed',
|
||||
`metadata` JSON NULL COMMENT '扩展元数据',
|
||||
`related_entity_type` VARCHAR(64) NULL COMMENT '关联实体类型(exam/homework/grade/attendance/mastery)',
|
||||
`related_entity_id` VARCHAR(32) NULL COMMENT '关联实体 ID',
|
||||
`group_id` VARCHAR(32) NULL COMMENT '群组 ID(批量发送/撤回用)',
|
||||
`sender_id` VARCHAR(32) NULL COMMENT '发送者 ID',
|
||||
`template_id` VARCHAR(32) NULL COMMENT '使用的模板 ID',
|
||||
`event_id` VARCHAR(64) NULL COMMENT '幂等键(Kafka event_id 或自生成)',
|
||||
`read_at` TIMESTAMP NULL COMMENT '已读时间',
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_notifications_event_id` (`event_id`),
|
||||
KEY `idx_notifications_user_id` (`user_id`),
|
||||
KEY `idx_notifications_is_read` (`is_read`),
|
||||
KEY `idx_notifications_created_at` (`created_at`),
|
||||
KEY `idx_notifications_user_read` (`user_id`, `is_read`),
|
||||
KEY `idx_notifications_group_id` (`group_id`),
|
||||
KEY `idx_notifications_related` (`related_entity_type`, `related_entity_id`),
|
||||
KEY `idx_notifications_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='msg 通知主表';
|
||||
|
||||
-- ============================================================
|
||||
-- 2. msg_notification_preferences(用户偏好 · 扩展)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `msg_notification_preferences` (
|
||||
`id` VARCHAR(32) NOT NULL COMMENT 'cuid2 主键',
|
||||
`user_id` VARCHAR(32) NOT NULL COMMENT '用户 ID',
|
||||
`type` VARCHAR(32) NOT NULL COMMENT '通知类型',
|
||||
`channels` JSON NOT NULL COMMENT '启用的渠道数组,如 ["in_app","email"]',
|
||||
`frequency_limit` INT NULL COMMENT '频率限制(每小时最大通知数)',
|
||||
`quiet_hours_start` VARCHAR(8) NULL COMMENT '免打扰开始(HH:MM)',
|
||||
`quiet_hours_end` VARCHAR(8) NULL COMMENT '免打扰结束(HH:MM)',
|
||||
`quiet_hours_timezone` VARCHAR(64) NULL DEFAULT 'Asia/Shanghai',
|
||||
`enabled` BOOLEAN NOT NULL DEFAULT TRUE COMMENT '是否启用该类型偏好',
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_preferences_user_type` (`user_id`, `type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='msg 用户通知偏好';
|
||||
|
||||
-- ============================================================
|
||||
-- 3. msg_notification_templates(通知模板)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `msg_notification_templates` (
|
||||
`id` VARCHAR(32) NOT NULL COMMENT 'cuid2 主键',
|
||||
`code` VARCHAR(64) NOT NULL COMMENT '模板编码(业务可读)',
|
||||
`type` VARCHAR(32) NOT NULL COMMENT '通知类型',
|
||||
`title_template` VARCHAR(255) NOT NULL COMMENT '标题模板(含 {{var}})',
|
||||
`content_template` TEXT NOT NULL COMMENT '正文模板(含 {{var}})',
|
||||
`default_channels` JSON NOT NULL COMMENT '默认渠道数组',
|
||||
`variables` JSON NOT NULL COMMENT '变量声明数组,如 ["userName","examTitle"]',
|
||||
`locale` VARCHAR(16) NOT NULL DEFAULT 'zh-CN',
|
||||
`status` VARCHAR(32) NOT NULL DEFAULT 'draft' COMMENT '状态: draft/active/archived',
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_templates_code` (`code`),
|
||||
KEY `idx_templates_type` (`type`),
|
||||
KEY `idx_templates_status` (`status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='msg 通知模板';
|
||||
|
||||
-- ============================================================
|
||||
-- 4. msg_notification_deliveries(投递记录)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `msg_notification_deliveries` (
|
||||
`id` VARCHAR(32) NOT NULL COMMENT 'cuid2 主键',
|
||||
`notification_id` VARCHAR(32) NOT NULL COMMENT '关联通知 ID',
|
||||
`channel` VARCHAR(32) NOT NULL COMMENT '投递渠道',
|
||||
`status` VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/sent/delivered/failed/retrying',
|
||||
`external_id` VARCHAR(128) NULL COMMENT '外部服务返回的消息 ID',
|
||||
`attempt_count` INT NOT NULL DEFAULT 0,
|
||||
`max_retry` INT NOT NULL DEFAULT 3,
|
||||
`last_error` TEXT NULL,
|
||||
`next_retry_at` TIMESTAMP NULL,
|
||||
`delivered_at` TIMESTAMP NULL,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_deliveries_notification_id` (`notification_id`),
|
||||
KEY `idx_deliveries_status` (`status`),
|
||||
KEY `idx_deliveries_next_retry` (`next_retry_at`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='msg 通知投递记录';
|
||||
|
||||
-- ============================================================
|
||||
-- 5. msg_outbox_events(事务性 Outbox)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `msg_outbox_events` (
|
||||
`event_id` VARCHAR(64) NOT NULL COMMENT '事件 ID(Kafka 幂等键)',
|
||||
`aggregate_type` VARCHAR(64) NOT NULL COMMENT '聚合类型: notification',
|
||||
`aggregate_id` VARCHAR(32) NOT NULL COMMENT '聚合 ID(通知 ID)',
|
||||
`event_type` VARCHAR(64) NOT NULL COMMENT '事件类型: notification.sent/read/recalled/failed',
|
||||
`topic` VARCHAR(128) NOT NULL COMMENT '目标 Kafka topic',
|
||||
`payload` JSON NOT NULL COMMENT '事件 payload',
|
||||
`status` VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/published/failed',
|
||||
`retry_count` INT NOT NULL DEFAULT 0,
|
||||
`max_retry_count` INT NOT NULL DEFAULT 5,
|
||||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
`published_at` TIMESTAMP NULL,
|
||||
`next_retry_at` TIMESTAMP NULL,
|
||||
`last_error` TEXT NULL,
|
||||
`metadata` JSON NULL,
|
||||
PRIMARY KEY (`event_id`),
|
||||
KEY `idx_outbox_status_next_retry` (`status`, `next_retry_at`),
|
||||
KEY `idx_outbox_topic` (`topic`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='msg Outbox 事务性事件';
|
||||
|
||||
-- ============================================================
|
||||
-- 6. processed_events(消费幂等 · Redis 降级表)
|
||||
-- ============================================================
|
||||
CREATE TABLE IF NOT EXISTS `processed_events` (
|
||||
`event_id` VARCHAR(64) NOT NULL COMMENT '事件 ID(幂等键)',
|
||||
`topic` VARCHAR(128) NOT NULL COMMENT '来源 topic',
|
||||
`processed_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (`event_id`),
|
||||
KEY `idx_processed_topic` (`topic`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='msg 消费幂等记录';
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = 1;
|
||||
|
||||
-- ============================================================
|
||||
-- 初始化数据:默认通知模板(可选)
|
||||
-- ============================================================
|
||||
INSERT INTO `msg_notification_templates` (`id`, `code`, `type`, `title_template`, `content_template`, `default_channels`, `variables`, `locale`, `status`)
|
||||
SELECT * FROM (SELECT
|
||||
'tmpl_welcome_001' AS id,
|
||||
'welcome' AS code,
|
||||
'system' AS type,
|
||||
'欢迎加入 Edu 云课堂' AS title_template,
|
||||
'你好 {{userName}},欢迎加入 Edu 云课堂!开始你的学习之旅吧。' AS content_template,
|
||||
JSON_ARRAY('in_app') AS default_channels,
|
||||
JSON_ARRAY('userName') AS variables,
|
||||
'zh-CN' AS locale,
|
||||
'active' AS status
|
||||
) AS tmp
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `msg_notification_templates` WHERE `code` = 'welcome');
|
||||
|
||||
INSERT INTO `msg_notification_templates` (`id`, `code`, `type`, `title_template`, `content_template`, `default_channels`, `variables`, `locale`, `status`)
|
||||
SELECT * FROM (SELECT
|
||||
'tmpl_exam_published_001' AS id,
|
||||
'exam_published' AS code,
|
||||
'exam' AS type,
|
||||
'新考试通知' AS title_template,
|
||||
'「{{className}}」班级发布了新考试:{{examTitle}}' AS content_template,
|
||||
JSON_ARRAY('in_app') AS default_channels,
|
||||
JSON_ARRAY('className', 'examTitle') AS variables,
|
||||
'zh-CN' AS locale,
|
||||
'active' AS status
|
||||
) AS tmp
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `msg_notification_templates` WHERE `code` = 'exam_published');
|
||||
|
||||
INSERT INTO `msg_notification_templates` (`id`, `code`, `type`, `title_template`, `content_template`, `default_channels`, `variables`, `locale`, `status`)
|
||||
SELECT * FROM (SELECT
|
||||
'tmpl_grade_recorded_001' AS id,
|
||||
'grade_recorded' AS code,
|
||||
'grade' AS type,
|
||||
'成绩录入通知' AS title_template,
|
||||
'你的{{subject}}成绩已录入{{score}}' AS content_template,
|
||||
JSON_ARRAY('in_app') AS default_channels,
|
||||
JSON_ARRAY('subject', 'score') AS variables,
|
||||
'zh-CN' AS locale,
|
||||
'active' AS status
|
||||
) AS tmp
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `msg_notification_templates` WHERE `code` = 'grade_recorded');
|
||||
|
||||
Reference in New Issue
Block a user