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>
174 lines
4.8 KiB
Go
174 lines
4.8 KiB
Go
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
|
||
}
|