AnCao/scripts/migrate_wrong_questions.go
yanlongqi 2fbeb23947 优化错题本功能和UI设计
1. 错题本系统重构:
   - 新增错题服务层 (wrong_question_service.go)
   - 实现智能推荐算法(基于掌握度和错误次数)
   - 添加掌握度追踪机制(连续答对6次标记为已掌握)
   - 支持错题筛选和排序功能
   - 新增错题统计趋势分析

2. UI优化:
   - 美化错题本界面,采用毛玻璃卡片设计
   - 添加四宫格统计卡片(错题总数、已掌握、未掌握、掌握率)
   - 优化筛选和操作按钮布局
   - 使用条状进度条显示掌握度
   - 改进响应式设计,优化移动端体验

3. 功能完善:
   - 修复判断题答案显示问题
   - 当掌握率100%时禁用"开始练习"按钮
   - 删除测试文件和 nul 文件
   - 更新文档 (CLAUDE.md)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-08 04:20:42 +08:00

174 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}