-- ============================================= -- Education 模块 — 题库目录原生表 DDL -- Ticket #20: 原生实现题库目录浏览,替换 Scalar adapter -- Migration: 008 -- Prerequisites: 000-education-schema.sql (database creation) -- 001-education-tenant-seed.sql (tenant seed data) -- ============================================= -- ============================================= -- Preconditions -- ============================================= -- Verify none of these tables already exist: -- SELECT TABLE_NAME FROM information_schema.tables -- WHERE table_schema = DATABASE() -- AND TABLE_NAME IN ( -- 'education_region', 'education_school', 'education_major', -- 'education_subject', 'education_category', -- 'education_content_entry', 'education_content_node', -- 'education_question_collection', 'education_question', -- 'education_practice_blueprint', 'education_question_collection_question' -- ); -- Result MUST be 0 before executing this migration. -- ============================================= -- 1. 地区表 -- ============================================= -- Indexes: -- idx_tenant — tenant-scoped queries (tenant-aware interceptor default) -- idx_tenant_active_order — active region listing ordered by display order CREATE TABLE `education_region` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `legacy_id` VARCHAR(64) DEFAULT NULL COMMENT '旧系统地区 ID', `name` VARCHAR(100) NOT NULL COMMENT '地区名称', `code` VARCHAR(32) DEFAULT NULL COMMENT '地区编码', `short_name` VARCHAR(50) DEFAULT NULL COMMENT '地区简称', `full_name` VARCHAR(200) DEFAULT NULL COMMENT '地区全称', `icon` VARCHAR(500) DEFAULT NULL COMMENT '地区图标 URL', `pinyin` VARCHAR(200) DEFAULT NULL COMMENT '地区拼音', `is_hot` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否热门地区', `is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值', `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`), KEY `idx_tenant_active_order` (`tenant_id`, `is_active`, `sort_order`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-地区'; -- ============================================= -- 2. 院校表 -- ============================================= CREATE TABLE `education_school` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `legacy_id` VARCHAR(64) DEFAULT NULL COMMENT '旧系统院校 ID', `region_id` BIGINT DEFAULT NULL COMMENT '地区 ID', `module_id` BIGINT DEFAULT NULL COMMENT '地区模块 ID', `name` VARCHAR(200) NOT NULL COMMENT '院校名称', `professional_exam_date` VARCHAR(200) DEFAULT NULL COMMENT '专业考试日期说明', `metadata` JSON DEFAULT NULL COMMENT '院校扩展数据', `is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值', `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`), KEY `idx_tenant_region` (`tenant_id`, `region_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-院校'; -- ============================================= -- 3. 专业表 -- ============================================= CREATE TABLE `education_major` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `legacy_id` VARCHAR(64) DEFAULT NULL COMMENT '旧系统专业 ID', `region_id` BIGINT DEFAULT NULL COMMENT '地区 ID', `school_id` BIGINT DEFAULT NULL COMMENT '院校 ID', `name` VARCHAR(200) NOT NULL COMMENT '专业名称', `description` VARCHAR(500) DEFAULT NULL COMMENT '专业说明', `study_tips` VARCHAR(500) DEFAULT NULL COMMENT '学习建议', `is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值', `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`), KEY `idx_tenant_school` (`tenant_id`, `school_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-专业'; -- ============================================= -- 4. 科目表 -- ============================================= CREATE TABLE `education_subject` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `region_id` BIGINT DEFAULT NULL COMMENT '地区 ID', `school_id` BIGINT DEFAULT NULL COMMENT '院校 ID', `major_id` BIGINT DEFAULT NULL COMMENT '专业 ID', `module_id` BIGINT DEFAULT NULL COMMENT '地区模块 ID', `name` VARCHAR(200) NOT NULL COMMENT '科目名称', `type` VARCHAR(50) DEFAULT NULL COMMENT '科目类型', `is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值', `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`), KEY `idx_tenant_major` (`tenant_id`, `major_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-科目'; -- ============================================= -- 5. 题目分类表 -- ============================================= CREATE TABLE `education_category` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `subject_id` BIGINT DEFAULT NULL COMMENT '科目 ID', `legacy_node_id` VARCHAR(64) DEFAULT NULL COMMENT '旧导航节点 ID', `name` VARCHAR(200) NOT NULL COMMENT '分类名称', `is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值', `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`), KEY `idx_tenant_subject` (`tenant_id`, `subject_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-题目分类'; -- ============================================= -- 6. 内容入口表 -- ============================================= -- Indexes: -- uk_tenant_entry_key — per-tenant uniqueness for entry key CREATE TABLE `education_content_entry` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `legacy_id` VARCHAR(64) DEFAULT NULL COMMENT '旧系统内容入口 ID', `region_id` BIGINT DEFAULT NULL COMMENT '地区 ID', `entry_key` VARCHAR(100) NOT NULL COMMENT '内容入口唯一键', `name` VARCHAR(200) NOT NULL COMMENT '内容入口名称', `entry_type` VARCHAR(50) NOT NULL COMMENT '内容入口类型', `icon` VARCHAR(500) DEFAULT NULL COMMENT '图标', `route` VARCHAR(200) DEFAULT NULL COMMENT '前端路由', `description` VARCHAR(500) DEFAULT NULL COMMENT '入口说明', `visibility` VARCHAR(50) NOT NULL DEFAULT 'PUBLIC' COMMENT '可见性', `access_rules` JSON DEFAULT NULL COMMENT '访问规则 JSON', `layout_config` JSON DEFAULT NULL COMMENT '布局配置 JSON', `is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值', `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`), UNIQUE KEY `uk_tenant_entry_key` (`tenant_id`, `entry_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-内容入口'; -- ============================================= -- 7. 内容节点表(树形结构) -- ============================================= -- Indexes: -- idx_entry_parent — supports tree navigation: find children of a parent within an entry -- idx_tenant_entry — tenant-scoped queries by entry CREATE TABLE `education_content_node` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `entry_id` BIGINT NOT NULL COMMENT '内容入口 ID', `parent_id` BIGINT DEFAULT NULL COMMENT '父节点 ID(NULL=根节点)', `name` VARCHAR(200) NOT NULL COMMENT '节点名称', `title` VARCHAR(200) DEFAULT NULL COMMENT '节点标题', `node_type` VARCHAR(50) NOT NULL COMMENT '节点类型', `marker_type` VARCHAR(50) DEFAULT NULL COMMENT '节点标记类型', `depth` INT NOT NULL DEFAULT 0 COMMENT '树深度', `is_leaf` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否叶子节点', `is_selectable` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否可被选择', `is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值', `metadata` JSON DEFAULT NULL COMMENT '扩展元数据', `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`), KEY `idx_entry_parent` (`entry_id`, `parent_id`), KEY `idx_tenant_entry` (`tenant_id`, `entry_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-内容节点'; -- ============================================= -- 8. 题集表 -- ============================================= CREATE TABLE `education_question_collection` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `entry_id` BIGINT DEFAULT NULL COMMENT '内容入口 ID', `node_id` BIGINT DEFAULT NULL COMMENT '内容节点 ID', `name` VARCHAR(200) NOT NULL COMMENT '题集名称', `title` VARCHAR(200) DEFAULT NULL COMMENT '题集标题', `collection_type` VARCHAR(50) NOT NULL COMMENT '题集类型', `question_count` INT NOT NULL DEFAULT 0 COMMENT '题目数量', `duration_minutes` INT DEFAULT NULL COMMENT '建议作答时长(分钟)', `access_rules` JSON DEFAULT NULL COMMENT '访问规则 JSON', `is_active` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否启用', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值', `metadata` JSON DEFAULT NULL COMMENT '扩展元数据', `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`), KEY `idx_tenant_entry` (`tenant_id`, `entry_id`), KEY `idx_tenant_node` (`tenant_id`, `node_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-题集'; -- ============================================= -- 9. 题目表(核心表) -- ============================================= -- Indexes: -- idx_tenant_published — 查询已发布题目(主查询路径) -- idx_tenant_collection — 按题集筛选 -- idx_tenant_subject — 按科目筛选 -- idx_tenant_node — 按内容节点筛选 -- idx_tenant_type_diff — 按题型和难度筛选 -- Security note: correct_answer 和 explanation 列存在但需在 service 层过滤; -- 学生端 DTO 绝不得包含这两列。 -- Options JSON: 存储 label, content, isCorrect, order — 完整选项数据。 -- Service 层在学生端返回前剥离 isCorrect。 CREATE TABLE `education_question` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `content_version` INT NOT NULL DEFAULT 1 COMMENT '内容版本号(递增,保证历史报告稳定性)', `stem` TEXT NOT NULL COMMENT '题干', `type` VARCHAR(32) NOT NULL COMMENT '题型', `type_label` VARCHAR(50) DEFAULT NULL COMMENT '题型显示名称', `difficulty` VARCHAR(32) DEFAULT NULL COMMENT '难度', `question_content` JSON DEFAULT NULL COMMENT '题干结构化内容(兼容复杂题型)', `options` JSON NOT NULL COMMENT '选项 JSON [{label, content, isCorrect, order}]', `correct_answer` VARCHAR(500) DEFAULT NULL COMMENT '正确答案(敏感字段,学生端不可返回)', `explanation` TEXT DEFAULT NULL COMMENT '答案解析(敏感字段,学生端不可返回)', `analysis` TEXT DEFAULT NULL COMMENT '深度解析(敏感字段)', `status` VARCHAR(20) NOT NULL DEFAULT 'PUBLISHED' COMMENT '题目状态:PUBLISHED-已发布, HIDDEN-已隐藏, DRAFT-草稿, INACTIVE-停用', `is_published` BIT(1) NOT NULL DEFAULT b'1' COMMENT '是否已发布', `collection_id` BIGINT DEFAULT NULL COMMENT '所属题集 ID', `subject_id` BIGINT DEFAULT NULL COMMENT '所属科目 ID', `node_id` BIGINT DEFAULT NULL COMMENT '所属内容节点 ID', `tags` JSON DEFAULT NULL COMMENT '标签 JSON 数组', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '显示排序值', `metadata` JSON DEFAULT NULL COMMENT '扩展元数据', `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`), KEY `idx_tenant_published` (`tenant_id`, `is_published`, `status`), KEY `idx_tenant_collection` (`tenant_id`, `collection_id`), KEY `idx_tenant_subject` (`tenant_id`, `subject_id`), KEY `idx_tenant_node` (`tenant_id`, `node_id`), KEY `idx_tenant_type_diff` (`tenant_id`, `type`, `difficulty`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-题目'; -- ============================================= -- 10. 练习蓝图表 -- ============================================= CREATE TABLE `education_practice_blueprint` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `mode` VARCHAR(50) NOT NULL COMMENT '练习模式', `entry_id` BIGINT DEFAULT NULL COMMENT '内容入口 ID', `node_id` BIGINT DEFAULT NULL COMMENT '内容节点 ID', `collection_id` BIGINT DEFAULT NULL COMMENT '题集 ID', `question_limit` INT DEFAULT NULL COMMENT '题目数量限制', `duration_minutes` INT DEFAULT NULL COMMENT '建议作答时长(分钟)', `eligible_count` INT NOT NULL DEFAULT 0 COMMENT '符合条件的题目数', `total_count` INT NOT NULL DEFAULT 0 COMMENT '题库总数', `available_types` JSON DEFAULT NULL COMMENT '可用题型列表 JSON', `available_difficulties` JSON DEFAULT NULL COMMENT '可用难度列表 JSON', `min_questions` INT NOT NULL DEFAULT 1 COMMENT '最少题目数', `max_questions` INT NOT NULL DEFAULT 200 COMMENT '最多题目数', `suggested_count` INT NOT NULL DEFAULT 20 COMMENT '建议题目数', `creator` VARCHAR(64) DEFAULT '' COMMENT '创建者', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `updater` VARCHAR(64) DEFAULT '' COMMENT '更新者', `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `deleted` BIT(1) NOT NULL DEFAULT b'0' COMMENT '是否删除', PRIMARY KEY (`id`), KEY `idx_tenant` (`tenant_id`), KEY `idx_tenant_collection` (`tenant_id`, `collection_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-练习蓝图'; -- ============================================= -- 11. 题集-题目关联表(多对多) -- ============================================= CREATE TABLE `education_question_collection_question` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `collection_id` BIGINT NOT NULL COMMENT '题集 ID', `question_id` BIGINT NOT NULL COMMENT '题目 ID', `sort_order` INT NOT NULL DEFAULT 0 COMMENT '排序值', PRIMARY KEY (`id`), UNIQUE KEY `uk_collection_question` (`collection_id`, `question_id`), KEY `idx_question` (`question_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-题集题目关联'; -- ============================================= -- Post-migration verification queries -- ============================================= -- Verify all tables exist: -- SELECT TABLE_NAME FROM information_schema.tables -- WHERE table_schema = DATABASE() -- AND TABLE_NAME LIKE 'education_%' -- ORDER BY TABLE_NAME; -- Verify indexes (sample): -- SHOW INDEX FROM education_question WHERE Key_name IN ('idx_tenant_published', 'idx_tenant_collection'); -- SHOW INDEX FROM education_content_node WHERE Key_name = 'idx_entry_parent'; -- Verify no orphan data (should be 0 after fresh migration for catalog tables): -- SELECT 'region', COUNT(*) FROM education_region -- UNION ALL SELECT 'school', COUNT(*) FROM education_school -- UNION ALL SELECT 'major', COUNT(*) FROM education_major -- UNION ALL SELECT 'subject', COUNT(*) FROM education_subject -- UNION ALL SELECT 'category', COUNT(*) FROM education_category -- UNION ALL SELECT 'content_entry', COUNT(*) FROM education_content_entry -- UNION ALL SELECT 'content_node', COUNT(*) FROM education_content_node -- UNION ALL SELECT 'question_collection', COUNT(*) FROM education_question_collection -- UNION ALL SELECT 'question', COUNT(*) FROM education_question -- UNION ALL SELECT 'practice_blueprint', COUNT(*) FROM education_practice_blueprint -- UNION ALL SELECT 'collection_question', COUNT(*) FROM education_question_collection_question;