-- ============================================= -- Education 模块 — 收藏夹 DDL -- Ticket #10: 学生收藏题目 -- Migration: 007 -- Prerequisites: 000-education-schema.sql (base tables) -- ============================================= -- ============================================= -- Preconditions -- ============================================= -- Operator is expected to verify: -- SELECT COUNT(*) FROM information_schema.tables -- WHERE table_schema = DATABASE() -- AND table_name = 'education_favorite'; -- Result MUST be 0 before executing this migration. -- ============================================= -- 收藏表 -- ============================================= -- Purpose: Student favorites for questions with safe snapshots. -- Each (tenant, user, target_type, target_id) is a unique entry. -- Logical deletion: setting deleted=1 marks as unfavorited. -- Re-adding after deletion reactivates the row via ON DUPLICATE KEY UPDATE. -- -- target_type values: 'QUESTION' (extensible enum) -- -- Snapshot fields (stem, type, difficulty, options, content_version): -- populated at creation time from the visible question's safe fields. -- These snapshots preserve the question state as it appeared when favorited, -- and remain stable even if the source question later changes or becomes unavailable. -- -- available flag: -- FALSE when the source question becomes hidden/unpublished after being -- favorited. Existing favorites with available=FALSE remain listable but -- display an "unavailable" indicator. New favorites cannot be created for -- unavailable resources. -- -- Indexes: -- uk_tenant_user_target — per (tenant, user, target_type, target_id) uniqueness. -- INSERT ... ON DUPLICATE KEY UPDATE is the primary reactivation path. -- idx_tenant_user — covers listing queries filtered by current tenant+user. -- idx_tenant_user_target_type — covers target-type-filtered listing. CREATE TABLE `education_favorite` ( `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键', `tenant_id` BIGINT NOT NULL COMMENT '租户编号', `user_id` BIGINT NOT NULL COMMENT '学生用户编号', `target_type` VARCHAR(32) NOT NULL COMMENT '目标类型:QUESTION', `target_id` VARCHAR(64) NOT NULL COMMENT '目标 ID(题目 ID)', -- safe snapshot fields `stem` TEXT DEFAULT NULL COMMENT '题干快照', `type` VARCHAR(32) DEFAULT NULL COMMENT '题型快照', `difficulty` VARCHAR(32) DEFAULT NULL COMMENT '难度快照', `options` JSON DEFAULT NULL COMMENT '选项快照 JSON(不含 isCorrect)', `content_version` VARCHAR(64) NOT NULL DEFAULT '' COMMENT '题目内容版本', -- availability `available` BIT(1) NOT NULL DEFAULT b'1' COMMENT '源资源是否可用', -- audit `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`), UNIQUE KEY `uk_tenant_user_target` (`tenant_id`, `user_id`, `target_type`, `target_id`), KEY `idx_tenant_user` (`tenant_id`, `user_id`), KEY `idx_tenant_user_target_type` (`tenant_id`, `user_id`, `target_type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='教育-收藏夹'; -- ============================================= -- Post-migration verification queries -- ============================================= -- Verify new table exists: -- SHOW CREATE TABLE education_favorite; -- Verify unique key is enforced: -- SHOW INDEX FROM education_favorite WHERE Key_name = 'uk_tenant_user_target'; -- Verify no orphan data (should be 0 after fresh migration): -- SELECT COUNT(*) FROM education_favorite;