修复涵盖 6 大类问题: 1. api-gateway - 路径前缀剥离 /api 而非 /api/v1,保留下游 /v1/ controller 前缀 - JWKS URL 默认值修复 - publicPaths 白名单对齐 /v1/iam/* 2. iam - iam.module.ts exports 补充 PermissionCacheService 和 IamRepository - main.ts resolveProtoPath() 多路径探测 proto 文件 3. core-edu - app.module.ts AuthMiddleware 全局注册 4. BFF 层 GraphQL 端点(teacher-bff / parent-bff / student-bff) - teacher-bff: mock dataScope OWN→SELF 对齐 GraphQL enum;WHATWG Request header .get() 提取 - parent-bff: handleNodeRequestAndResponse 不存在 → 直接 yoga(req,res);WHATWG Request header .get() 提取 - student-bff: auth.resolver 移除 ActionState 信封返回扁平对象;WHATWG Request header .get() 提取 5. ai - Kafka 事务降级 + 10s 超时 - gRPC 拦截器降级 - dev mode 禁用事务模式 6. 前端 + 共享包 - teacher-portal: MF 插件条件实例化 + transpilePackages + extensionAlias - ui-components: error-boundary.tsx 添加 use client - ui-tokens: tailwind-theme.css 移除 @layer base - shared-ts: 导出从源码改为 dist 编译产物;OutboxModule global:true 7. infra - .gitignore 补充 keys/ *.pem *.key secrets/ 排除规则 - infra/init-sql/02-all-services-schema.sql 36 张表 DDL 验证结果: - TS typecheck: 19 个 workspace 项目全部通过 - Go vet + Ruff: 通过 - 15 服务全部启动成功 - 3 个 BFF GraphQL 端点 + 4 个前端页面全部 200 - Gateway → iam → core-edu 端到端链路验证通过 AI identity: trae-main(集成测试修复会话)
610 lines
24 KiB
SQL
610 lines
24 KiB
SQL
-- ============================================================
|
||
-- Edu 微服务 All-Services Schema (P2 阶段,单库 next_edu_cloud)
|
||
-- 由 Drizzle ORM schema 文件转换而来
|
||
-- 使用 CREATE TABLE IF NOT EXISTS,可重复执行
|
||
-- 字符集:utf8mb4 / utf8mb4_unicode_ci;引擎:InnoDB
|
||
-- ============================================================
|
||
|
||
-- ============================================================
|
||
-- 1. IAM 服务(services/iam/src/iam/iam.schema.ts)
|
||
-- ============================================================
|
||
|
||
-- 1.1 用户表
|
||
CREATE TABLE IF NOT EXISTS `iam_users` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`email` VARCHAR(255) NOT NULL,
|
||
`password_hash` VARCHAR(255) NOT NULL,
|
||
`name` VARCHAR(100) NOT NULL,
|
||
`status` VARCHAR(20) NOT NULL DEFAULT 'active',
|
||
`data_scope` ENUM('self','subject','class','grade','school','all') NOT NULL DEFAULT 'self',
|
||
`password_changed_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`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 `uniq_iam_users_email` (`email`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 1.2 角色表
|
||
CREATE TABLE IF NOT EXISTS `iam_roles` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`name` VARCHAR(50) NOT NULL,
|
||
`description` VARCHAR(255) NULL,
|
||
`role_type` ENUM('system','organization','temporary') NOT NULL DEFAULT 'system',
|
||
`level` INT NOT NULL DEFAULT 0,
|
||
`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 `uniq_iam_roles_name` (`name`),
|
||
INDEX `idx_iam_roles_type` (`role_type`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 1.3 用户-角色关联表
|
||
CREATE TABLE IF NOT EXISTS `iam_user_roles` (
|
||
`user_id` CHAR(36) NOT NULL,
|
||
`role_id` CHAR(36) NOT NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
INDEX `idx_iam_user_roles_pk` (`user_id`, `role_id`),
|
||
INDEX `idx_iam_user_roles_role` (`role_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 1.4 权限表
|
||
CREATE TABLE IF NOT EXISTS `iam_permissions` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`name` VARCHAR(100) NOT NULL,
|
||
`resource` VARCHAR(50) NOT NULL,
|
||
`action` VARCHAR(50) NOT NULL,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uniq_iam_permissions_name` (`name`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 1.5 角色-权限关联表
|
||
CREATE TABLE IF NOT EXISTS `iam_role_permissions` (
|
||
`role_id` CHAR(36) NOT NULL,
|
||
`permission_id` CHAR(36) NOT NULL,
|
||
INDEX `idx_iam_role_permissions_pk` (`role_id`, `permission_id`),
|
||
INDEX `idx_iam_role_permissions_perm` (`permission_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 1.6 刷新令牌表
|
||
CREATE TABLE IF NOT EXISTS `iam_refresh_tokens` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`user_id` CHAR(36) NOT NULL,
|
||
`token_hash` VARCHAR(255) NOT NULL,
|
||
`jti` VARCHAR(36) NULL,
|
||
`expires_at` TIMESTAMP NOT NULL,
|
||
`revoked_at` TIMESTAMP NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_iam_refresh_tokens_user` (`user_id`),
|
||
INDEX `idx_iam_refresh_tokens_jti` (`jti`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 1.7 视口配置表(4 层模型)
|
||
CREATE TABLE IF NOT EXISTS `iam_role_viewports` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`role_id` CHAR(36) NOT NULL,
|
||
`viewport_key` VARCHAR(50) NOT NULL,
|
||
`label` VARCHAR(100) NOT NULL,
|
||
`route` VARCHAR(200) NOT NULL,
|
||
`icon` VARCHAR(50) NULL,
|
||
`sort_order` VARCHAR(10) NOT NULL DEFAULT '0',
|
||
`required_permission` VARCHAR(100) NULL,
|
||
`level` ENUM('admin','teacher','student','parent') NOT NULL DEFAULT 'teacher',
|
||
`component_config` TEXT NULL,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_iam_role_viewports_role` (`role_id`),
|
||
INDEX `idx_iam_role_viewports_level` (`level`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 1.8 学生-家长关系表
|
||
CREATE TABLE IF NOT EXISTS `iam_student_guardians` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`student_id` CHAR(36) NOT NULL,
|
||
`guardian_id` CHAR(36) NOT NULL,
|
||
`relation` VARCHAR(20) NOT NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uniq_student_guardian` (`student_id`, `guardian_id`),
|
||
INDEX `idx_iam_student_guardians_guardian` (`guardian_id`),
|
||
INDEX `idx_iam_student_guardians_student` (`student_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 1.9 审计日志表
|
||
CREATE TABLE IF NOT EXISTS `iam_user_audit_log` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`actor_user_id` CHAR(36) NOT NULL,
|
||
`action` VARCHAR(50) NOT NULL,
|
||
`resource_type` VARCHAR(50) NOT NULL,
|
||
`resource_id` VARCHAR(36) NOT NULL,
|
||
`before_state` TEXT NULL,
|
||
`after_state` TEXT NULL,
|
||
`ip` VARCHAR(45) NULL,
|
||
`user_agent` VARCHAR(255) NULL,
|
||
`trace_id` VARCHAR(64) NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_iam_audit_actor` (`actor_user_id`),
|
||
INDEX `idx_iam_audit_resource` (`resource_type`, `resource_id`),
|
||
INDEX `idx_iam_audit_created` (`created_at`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 1.10 密码历史表
|
||
CREATE TABLE IF NOT EXISTS `iam_password_history` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`user_id` CHAR(36) NOT NULL,
|
||
`password_hash` VARCHAR(255) NOT NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_iam_password_history_user` (`user_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
|
||
-- ============================================================
|
||
-- 2. Classes 服务(services/classes/src/classes/classes.schema.ts)
|
||
-- 班级表(已在 01-init.sql 中创建,此处保留以保完整性,IF NOT EXISTS 安全)
|
||
-- ============================================================
|
||
|
||
CREATE TABLE IF NOT EXISTS `classes` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`name` VARCHAR(100) NOT NULL,
|
||
`grade_id` CHAR(36) NOT NULL,
|
||
`head_teacher_id` CHAR(36) NULL,
|
||
`description` TEXT NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
|
||
-- ============================================================
|
||
-- 3. Core-EDU 服务(services/core-edu/src/**/*.schema.ts)
|
||
-- ============================================================
|
||
|
||
-- 3.1 教师关联表(IAM 事件消费幂等)
|
||
CREATE TABLE IF NOT EXISTS `core_edu_teacher_associations` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`teacher_id` CHAR(36) NOT NULL,
|
||
`class_id` CHAR(36) NOT NULL,
|
||
`subject_id` CHAR(36) NULL,
|
||
`school_id` CHAR(36) NOT NULL,
|
||
`source` VARCHAR(20) NOT NULL DEFAULT 'iam_event',
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uniq_teacher_class_subject` (`teacher_id`, `class_id`, `subject_id`),
|
||
INDEX `idx_teacher_assoc_teacher_id` (`teacher_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.2 考试主表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_exams` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`class_id` CHAR(36) NOT NULL,
|
||
`subject_id` CHAR(36) NOT NULL,
|
||
`title` VARCHAR(200) NOT NULL,
|
||
`description` TEXT NULL,
|
||
`exam_date` DATETIME NOT NULL,
|
||
`duration` INT NOT NULL,
|
||
`total_score` DECIMAL(6,2) NOT NULL,
|
||
`status` VARCHAR(20) NOT NULL DEFAULT 'draft',
|
||
`status_changed_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`status_changed_by` CHAR(36) NULL,
|
||
`school_id` CHAR(36) NOT NULL,
|
||
`created_by` CHAR(36) NOT NULL,
|
||
`archived_at` DATETIME NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_exams_class_status` (`class_id`, `status`),
|
||
INDEX `idx_exams_school_date` (`school_id`, `exam_date`),
|
||
INDEX `idx_exams_created_by` (`created_by`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.3 考试题目关联表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_exam_questions` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`exam_id` CHAR(36) NOT NULL,
|
||
`question_id` CHAR(36) NOT NULL,
|
||
`order` INT NOT NULL,
|
||
`score` DECIMAL(6,2) NOT NULL,
|
||
`question_type` VARCHAR(30) NOT NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_exam_questions_exam_id` (`exam_id`),
|
||
INDEX `idx_exam_questions_question_id` (`question_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.4 考试提交表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_exam_submissions` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`exam_id` CHAR(36) NOT NULL,
|
||
`student_id` CHAR(36) NOT NULL,
|
||
`status` VARCHAR(20) NOT NULL DEFAULT 'not_submitted',
|
||
`submitted_at` DATETIME NULL,
|
||
`graded_at` DATETIME NULL,
|
||
`graded_by` CHAR(36) NULL,
|
||
`total_score` DECIMAL(6,2) NULL,
|
||
`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 `uniq_exam_student` (`exam_id`, `student_id`),
|
||
INDEX `idx_exam_submissions_exam_id` (`exam_id`),
|
||
INDEX `idx_exam_submissions_student_id` (`student_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.5 作业主表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_homework` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`class_id` CHAR(36) NOT NULL,
|
||
`subject_id` CHAR(36) NOT NULL,
|
||
`title` VARCHAR(200) NOT NULL,
|
||
`description` TEXT NULL,
|
||
`due_date` DATETIME NOT NULL,
|
||
`grace_period` INT NOT NULL DEFAULT 300,
|
||
`status` VARCHAR(20) NOT NULL DEFAULT 'assigned',
|
||
`school_id` CHAR(36) NOT NULL,
|
||
`created_by` CHAR(36) NOT NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_homework_class_status` (`class_id`, `status`),
|
||
INDEX `idx_homework_created_by` (`created_by`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.6 作业提交表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_homework_submissions` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`homework_id` CHAR(36) NOT NULL,
|
||
`student_id` CHAR(36) NOT NULL,
|
||
`status` VARCHAR(20) NOT NULL DEFAULT 'not_submitted',
|
||
`submitted_at` DATETIME NULL,
|
||
`graded_at` DATETIME NULL,
|
||
`graded_by` CHAR(36) NULL,
|
||
`total_score` DECIMAL(6,2) NULL,
|
||
`feedback` TEXT NULL,
|
||
`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 `uniq_hw_student` (`homework_id`, `student_id`),
|
||
INDEX `idx_hw_submissions_homework_id` (`homework_id`),
|
||
INDEX `idx_hw_submissions_student_id` (`student_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.7 作业答题表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_homework_answers` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`submission_id` CHAR(36) NOT NULL,
|
||
`question_id` CHAR(36) NOT NULL,
|
||
`answer` TEXT NULL,
|
||
`score` DECIMAL(6,2) NULL,
|
||
`teacher_comment` TEXT NULL,
|
||
`is_correct` TINYINT(1) NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_hw_answers_submission_id` (`submission_id`),
|
||
INDEX `idx_hw_answers_question_id` (`question_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.8 成绩主表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_grades` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`student_id` CHAR(36) NOT NULL,
|
||
`exam_id` CHAR(36) NULL,
|
||
`homework_id` CHAR(36) NULL,
|
||
`score` DECIMAL(6,2) NOT NULL,
|
||
`total_score` DECIMAL(6,2) NOT NULL,
|
||
`feedback` TEXT NULL,
|
||
`graded_by` CHAR(36) NOT NULL,
|
||
`school_id` CHAR(36) NOT NULL,
|
||
`idempotency_key` VARCHAR(128) NULL,
|
||
`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 `uniq_student_exam` (`student_id`, `exam_id`),
|
||
UNIQUE KEY `uniq_student_hw` (`student_id`, `homework_id`),
|
||
UNIQUE KEY `uniq_idempotency` (`idempotency_key`),
|
||
INDEX `idx_grades_student_id` (`student_id`),
|
||
INDEX `idx_grades_exam_id` (`exam_id`),
|
||
INDEX `idx_grades_homework_id` (`homework_id`),
|
||
INDEX `idx_grades_graded_by` (`graded_by`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.9 成绩公式表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_grade_formulas` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`scope` VARCHAR(20) NOT NULL,
|
||
`scope_id` CHAR(36) NOT NULL,
|
||
`formula_type` VARCHAR(20) NOT NULL,
|
||
`weights` JSON NULL,
|
||
`custom_expression` TEXT NULL,
|
||
`effective_from` DATETIME NOT NULL,
|
||
`effective_to` DATETIME NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_grade_formulas_scope` (`scope`, `scope_id`),
|
||
INDEX `idx_grade_formulas_effective` (`effective_from`, `effective_to`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.10 考勤记录表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_attendance` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`schedule_id` CHAR(36) NOT NULL,
|
||
`student_id` CHAR(36) NOT NULL,
|
||
`status` VARCHAR(20) NOT NULL,
|
||
`remark` TEXT NULL,
|
||
`recorded_by` CHAR(36) NOT NULL,
|
||
`school_id` CHAR(36) NOT NULL,
|
||
`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 `uniq_sched_student` (`schedule_id`, `student_id`),
|
||
INDEX `idx_attendance_student_id` (`student_id`),
|
||
INDEX `idx_attendance_schedule_id` (`schedule_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.11 课程表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_courses` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`class_id` CHAR(36) NOT NULL,
|
||
`subject_id` CHAR(36) NOT NULL,
|
||
`teacher_id` CHAR(36) NOT NULL,
|
||
`school_id` CHAR(36) NOT NULL,
|
||
`name` VARCHAR(200) NOT NULL,
|
||
`start_date` DATE NOT NULL,
|
||
`end_date` DATE NOT NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_courses_class_id` (`class_id`),
|
||
INDEX `idx_courses_teacher_id` (`teacher_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.12 课次表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_lessons` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`course_id` CHAR(36) NOT NULL,
|
||
`title` VARCHAR(200) NOT NULL,
|
||
`order` INT NOT NULL,
|
||
`knowledge_point_ids` JSON NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_lessons_course_id` (`course_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.13 排课表
|
||
CREATE TABLE IF NOT EXISTS `core_edu_schedules` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`course_id` CHAR(36) NOT NULL,
|
||
`lesson_id` CHAR(36) NULL,
|
||
`teacher_id` CHAR(36) NOT NULL,
|
||
`class_id` CHAR(36) NOT NULL,
|
||
`room_id` CHAR(36) NULL,
|
||
`start_time` DATETIME NOT NULL,
|
||
`end_time` DATETIME NOT NULL,
|
||
`status` VARCHAR(20) NOT NULL DEFAULT 'scheduled',
|
||
`school_id` CHAR(36) NOT NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_schedules_teacher_time` (`teacher_id`, `start_time`, `end_time`),
|
||
INDEX `idx_schedules_class_time` (`class_id`, `start_time`, `end_time`),
|
||
INDEX `idx_schedules_course_id` (`course_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 3.14 Core-EDU Outbox 表(事务性发件箱)
|
||
CREATE TABLE IF NOT EXISTS `core_edu_outbox` (
|
||
`id` CHAR(36) NOT NULL,
|
||
`event_id` CHAR(36) NOT NULL,
|
||
`aggregate_id` CHAR(36) NOT NULL,
|
||
`aggregate_type` VARCHAR(50) NOT NULL,
|
||
`event_type` VARCHAR(100) NOT NULL,
|
||
`occurred_at` DATETIME NOT NULL,
|
||
`payload` TEXT NOT NULL,
|
||
`status` VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||
`retry_count` BIGINT NOT NULL DEFAULT 0,
|
||
`next_retry_at` DATETIME NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`processed_at` TIMESTAMP NULL,
|
||
PRIMARY KEY (`id`),
|
||
UNIQUE KEY `uniq_event_id` (`event_id`),
|
||
INDEX `idx_outbox_status_retry` (`status`, `next_retry_at`),
|
||
INDEX `idx_outbox_aggregate` (`aggregate_type`, `aggregate_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
|
||
-- ============================================================
|
||
-- 4. Content 服务(services/content/src/**/*.schema.ts)
|
||
-- ============================================================
|
||
|
||
-- 4.1 教材表
|
||
CREATE TABLE IF NOT EXISTS `content_textbooks` (
|
||
`id` VARCHAR(32) NOT NULL,
|
||
`title` VARCHAR(255) NOT NULL,
|
||
`subject_id` VARCHAR(32) NOT NULL,
|
||
`grade_id` VARCHAR(32) NOT NULL,
|
||
`version` VARCHAR(32) NOT NULL DEFAULT '1.0',
|
||
`status` VARCHAR(32) NOT NULL DEFAULT 'draft',
|
||
`tenant_id` VARCHAR(32) NULL,
|
||
`metadata` JSON NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_textbooks_subject_grade` (`subject_id`, `grade_id`),
|
||
INDEX `idx_textbooks_status` (`status`),
|
||
INDEX `idx_textbooks_tenant` (`tenant_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 4.2 章节表
|
||
CREATE TABLE IF NOT EXISTS `content_chapters` (
|
||
`id` VARCHAR(32) NOT NULL,
|
||
`textbook_id` VARCHAR(32) NOT NULL,
|
||
`title` VARCHAR(255) NOT NULL,
|
||
`order_num` INT NOT NULL DEFAULT 0,
|
||
`parent_id` VARCHAR(32) NULL,
|
||
`status` VARCHAR(32) NOT NULL DEFAULT 'draft',
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_chapters_textbook_order` (`textbook_id`, `order_num`),
|
||
INDEX `idx_chapters_parent` (`parent_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 4.3 知识点表
|
||
CREATE TABLE IF NOT EXISTS `content_knowledge_points` (
|
||
`id` VARCHAR(32) NOT NULL,
|
||
`chapter_id` VARCHAR(32) NOT NULL,
|
||
`title` VARCHAR(255) NOT NULL,
|
||
`description` TEXT NULL,
|
||
`difficulty` TINYINT NOT NULL DEFAULT 3,
|
||
`metadata` JSON NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_kp_chapter` (`chapter_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 4.4 题目表
|
||
CREATE TABLE IF NOT EXISTS `content_questions` (
|
||
`id` VARCHAR(32) NOT NULL,
|
||
`knowledge_point_id` VARCHAR(32) NOT NULL,
|
||
`type` VARCHAR(32) NOT NULL,
|
||
`content` TEXT NOT NULL,
|
||
`options` JSON NULL,
|
||
`answer` TEXT NOT NULL,
|
||
`explanation` TEXT NULL,
|
||
`difficulty` TINYINT NOT NULL DEFAULT 3,
|
||
`status` VARCHAR(32) NOT NULL DEFAULT 'draft',
|
||
`source` VARCHAR(32) NOT NULL DEFAULT 'manual',
|
||
`created_by` VARCHAR(32) NOT NULL,
|
||
`metadata` JSON NULL,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_questions_kp` (`knowledge_point_id`),
|
||
INDEX `idx_questions_type_difficulty` (`type`, `difficulty`),
|
||
INDEX `idx_questions_status` (`status`),
|
||
INDEX `idx_questions_source` (`source`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 4.5 Content Outbox 表
|
||
CREATE TABLE IF NOT EXISTS `content_outbox_events` (
|
||
`id` VARCHAR(32) NOT NULL,
|
||
`aggregate_type` VARCHAR(64) NOT NULL,
|
||
`aggregate_id` VARCHAR(32) NOT NULL,
|
||
`event_type` VARCHAR(100) NOT NULL,
|
||
`topic` VARCHAR(128) NOT NULL,
|
||
`payload` TEXT NOT NULL,
|
||
`status` VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||
`retry_count` INT NOT NULL DEFAULT 0,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`published_at` TIMESTAMP NULL,
|
||
`next_retry_at` TIMESTAMP NULL,
|
||
`last_error` TEXT NULL,
|
||
PRIMARY KEY (`id`),
|
||
INDEX `idx_outbox_status_retry` (`status`, `next_retry_at`),
|
||
INDEX `idx_outbox_aggregate` (`aggregate_type`, `aggregate_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
|
||
-- ============================================================
|
||
-- 5. MSG 服务(services/msg/src/**/*.schema.ts)
|
||
-- ============================================================
|
||
|
||
-- 5.1 通知主表
|
||
CREATE TABLE IF NOT EXISTS `msg_notifications` (
|
||
`id` VARCHAR(32) NOT NULL,
|
||
`user_id` VARCHAR(32) NOT NULL,
|
||
`type` VARCHAR(32) NOT NULL,
|
||
`title` VARCHAR(255) NOT NULL,
|
||
`content` TEXT NOT NULL,
|
||
`channel` VARCHAR(32) NOT NULL,
|
||
`is_read` TINYINT(1) NOT NULL DEFAULT 0,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`status` VARCHAR(32) NOT NULL DEFAULT 'pending',
|
||
`metadata` JSON NULL,
|
||
`related_entity_type` VARCHAR(64) NULL,
|
||
`related_entity_id` VARCHAR(32) NULL,
|
||
`group_id` VARCHAR(32) NULL,
|
||
`sender_id` VARCHAR(32) NULL,
|
||
`template_id` VARCHAR(32) NULL,
|
||
`event_id` VARCHAR(64) NULL,
|
||
`read_at` TIMESTAMP NULL,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 5.2 用户偏好表
|
||
CREATE TABLE IF NOT EXISTS `msg_notification_preferences` (
|
||
`id` VARCHAR(32) NOT NULL,
|
||
`user_id` VARCHAR(32) NOT NULL,
|
||
`type` VARCHAR(32) NOT NULL,
|
||
`channels` JSON NOT NULL,
|
||
`frequency_limit` INT NULL,
|
||
`quiet_hours_start` VARCHAR(8) NULL,
|
||
`quiet_hours_end` VARCHAR(8) NULL,
|
||
`quiet_hours_timezone` VARCHAR(64) NULL DEFAULT 'Asia/Shanghai',
|
||
`enabled` TINYINT(1) NOT NULL DEFAULT 1,
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 5.3 通知模板表
|
||
CREATE TABLE IF NOT EXISTS `msg_notification_templates` (
|
||
`id` VARCHAR(32) NOT NULL,
|
||
`code` VARCHAR(64) NOT NULL,
|
||
`type` VARCHAR(32) NOT NULL,
|
||
`title_template` VARCHAR(255) NOT NULL,
|
||
`content_template` TEXT NOT NULL,
|
||
`default_channels` JSON NOT NULL,
|
||
`variables` JSON NOT NULL,
|
||
`locale` VARCHAR(16) NOT NULL DEFAULT 'zh-CN',
|
||
`status` VARCHAR(32) NOT NULL DEFAULT 'draft',
|
||
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 5.4 投递记录表
|
||
CREATE TABLE IF NOT EXISTS `msg_notification_deliveries` (
|
||
`id` VARCHAR(32) NOT NULL,
|
||
`notification_id` VARCHAR(32) NOT NULL,
|
||
`channel` VARCHAR(32) NOT NULL,
|
||
`status` VARCHAR(32) NOT NULL DEFAULT 'pending',
|
||
`external_id` VARCHAR(128) NULL,
|
||
`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`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 5.5 MSG Outbox 表
|
||
CREATE TABLE IF NOT EXISTS `msg_outbox_events` (
|
||
`event_id` VARCHAR(64) NOT NULL,
|
||
`aggregate_type` VARCHAR(64) NOT NULL,
|
||
`aggregate_id` VARCHAR(32) NOT NULL,
|
||
`event_type` VARCHAR(64) NOT NULL,
|
||
`topic` VARCHAR(128) NOT NULL,
|
||
`payload` JSON NOT NULL,
|
||
`status` VARCHAR(16) NOT NULL DEFAULT 'pending',
|
||
`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`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||
|
||
-- 5.6 已处理事件表(消费幂等)
|
||
CREATE TABLE IF NOT EXISTS `processed_events` (
|
||
`event_id` VARCHAR(64) NOT NULL,
|
||
`topic` VARCHAR(128) NOT NULL,
|
||
`processed_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
PRIMARY KEY (`event_id`)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|