package main import ( "ankao/internal/database" "ankao/internal/models" "encoding/json" "fmt" "log" "time" "gorm.io/gorm" ) // 数据迁移脚本:从旧版错题本迁移到新版 func main() { // 初始化数据库 if err := database.InitDB(); err != nil { log.Fatalf("数据库初始化失败: %v", err) } db := database.GetDB() fmt.Println("开始错题本数据迁移...") // 1. 创建新表 fmt.Println("1. 创建新表结构...") if err := db.AutoMigrate(&models.WrongQuestion{}, &models.WrongQuestionHistory{}, &models.WrongQuestionTag{}); err != nil { log.Fatalf("创建新表失败: %v", err) } fmt.Println(" ✓ 新表创建成功") // 2. 迁移数据 fmt.Println("2. 迁移旧数据到新表...") if err := migrateOldData(db); err != nil { log.Fatalf("数据迁移失败: %v", err) } fmt.Println(" ✓ 数据迁移成功") // 3. 统计信息 fmt.Println("3. 统计迁移结果...") printStats(db) fmt.Println("\n迁移完成!") fmt.Println("\n注意事项:") fmt.Println("- 旧表 'wrong_questions' 已保留,不会删除") fmt.Println("- 新表 'wrong_questions_v2' 包含所有迁移后的数据") fmt.Println("- 如需回滚,可以删除新表并继续使用旧表") } func migrateOldData(db *gorm.DB) error { // 查询所有旧错题记录 var oldWrongQuestions []models.WrongQuestion if err := db.Find(&oldWrongQuestions).Error; err != nil { return fmt.Errorf("查询旧数据失败: %v", err) } if len(oldWrongQuestions) == 0 { fmt.Println(" 没有需要迁移的数据") return nil } fmt.Printf(" 找到 %d 条旧记录,开始迁移...\n", len(oldWrongQuestions)) // 逐条迁移 for i, old := range oldWrongQuestions { if err := migrateOneRecord(db, &old); err != nil { log.Printf(" 警告: 迁移记录 %d 失败: %v", old.ID, err) continue } if (i+1)%100 == 0 { fmt.Printf(" 已迁移: %d/%d\n", i+1, len(oldWrongQuestions)) } } fmt.Printf(" ✓ 迁移完成: %d/%d\n", len(oldWrongQuestions), len(oldWrongQuestions)) return nil } func migrateOneRecord(db *gorm.DB, old *models.WrongQuestion) error { // 检查是否已存在 var existing models.WrongQuestion if err := db.Where("user_id = ? AND question_id = ?", old.UserID, old.QuestionID).First(&existing).Error; err == nil { // 已存在,跳过 return nil } // 创建新记录 newRecord := models.WrongQuestion{ UserID: old.UserID, QuestionID: old.QuestionID, FirstWrongTime: old.LastWrongTime, // 旧版只有最后错误时间 LastWrongTime: old.LastWrongTime, TotalWrongCount: old.WrongCount, MasteryLevel: 0, ConsecutiveCorrect: 0, IsMastered: old.IsMastered, Tags: []string{}, // 默认无标签 CreatedAt: time.Now(), UpdatedAt: time.Now(), } // 计算下次复习时间 newRecord.CalculateNextReviewTime() // 保存新记录 if err := db.Create(&newRecord).Error; err != nil { return fmt.Errorf("创建新记录失败: %v", err) } // 创建历史记录(基于旧数据) history := models.WrongQuestionHistory{ WrongQuestionID: newRecord.ID, UserAnswer: old.WrongAnswer, CorrectAnswer: old.CorrectAnswer, AnsweredAt: old.LastWrongTime, TimeSpent: 0, IsCorrect: false, } if err := db.Create(&history).Error; err != nil { log.Printf("警告: 创建历史记录失败: %v", err) } return nil } func printStats(db *gorm.DB) { var oldCount, newCount, historyCount, tagCount int64 db.Model(&models.WrongQuestion{}).Count(&oldCount) db.Model(&models.WrongQuestion{}).Count(&newCount) db.Model(&models.WrongQuestionHistory{}).Count(&historyCount) db.Model(&models.WrongQuestionTag{}).Count(&tagCount) fmt.Printf("\n 旧表记录数: %d\n", oldCount) fmt.Printf(" 新表记录数: %d\n", newCount) fmt.Printf(" 历史记录数: %d\n", historyCount) fmt.Printf(" 标签数: %d\n", tagCount) // 统计掌握度分布 var masteryStats []struct { MasteryLevel int Count int64 } db.Model(&models.WrongQuestion{}). Select("mastery_level, COUNT(*) as count"). Group("mastery_level"). Scan(&masteryStats) if len(masteryStats) > 0 { fmt.Println("\n 掌握度分布:") for _, stat := range masteryStats { fmt.Printf(" - 掌握度 %d%%: %d 题\n", stat.MasteryLevel, stat.Count) } } // 统计需要复习的题目 var needReview int64 now := time.Now() db.Model(&models.WrongQuestion{}). Where("is_mastered = ? AND next_review_time IS NOT NULL AND next_review_time <= ?", false, now). Count(&needReview) fmt.Printf("\n 需要复习的题目: %d\n", needReview) } // 辅助函数:解析JSON答案 func parseAnswer(answerStr string) interface{} { var answer interface{} if err := json.Unmarshal([]byte(answerStr), &answer); err != nil { return answerStr } return answer }