package services import ( "time" "github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models" "gorm.io/gorm" ) // WordBookService 生词本服务 type WordBookService struct { db *gorm.DB } func NewWordBookService(db *gorm.DB) *WordBookService { return &WordBookService{db: db} } // ToggleFavorite 切换单词收藏状态 func (s *WordBookService) ToggleFavorite(userID, wordID int64) (bool, error) { var progress models.UserWordProgress err := s.db.Where("user_id = ? AND vocabulary_id = ?", userID, wordID).First(&progress).Error if err == gorm.ErrRecordNotFound { // 如果没有进度记录,创建一个并设置为收藏 now := time.Now() progress = models.UserWordProgress{ UserID: userID, VocabularyID: wordID, Status: "not_started", StudyCount: 0, CorrectCount: 0, WrongCount: 0, Proficiency: 0, ReviewInterval: 1, FirstStudiedAt: now, LastStudiedAt: now, } // 通过GORM钩子设置IsFavorite(因为是bool类型的零值问题) if err := s.db.Create(&progress).Error; err != nil { return false, err } // 更新IsFavorite为true if err := s.db.Model(&progress).Update("is_favorite", true).Error; err != nil { return false, err } return true, nil } else if err != nil { return false, err } // 切换收藏状态 newStatus := !progress.IsFavorite if err := s.db.Model(&progress).Update("is_favorite", newStatus).Error; err != nil { return false, err } return newStatus, nil } // GetFavoriteWords 获取生词本列表(带分页) func (s *WordBookService) GetFavoriteWords(userID int64, page, pageSize int, sortBy, order string) ([]map[string]interface{}, int64, error) { var total int64 // 统计总数 s.db.Model(&models.UserWordProgress{}). Where("user_id = ? AND is_favorite = ?", userID, true). Count(&total) if total == 0 { return []map[string]interface{}{}, 0, nil } // 构建排序字段 orderClause := "uwp.created_at DESC" switch sortBy { case "proficiency": orderClause = "uwp.proficiency " + order case "word": orderClause = "v.word " + order case "created_at": orderClause = "uwp.created_at " + order } // 查询收藏的单词及其详情 var results []map[string]interface{} offset := (page - 1) * pageSize err := s.db.Raw(` SELECT v.id, v.word, v.phonetic, v.audio_url, v.level, uwp.proficiency, uwp.study_count, uwp.status, uwp.next_review_at, uwp.created_at as favorited_at, GROUP_CONCAT(DISTINCT vd.definition_cn SEPARATOR '; ') as definitions, GROUP_CONCAT(DISTINCT vd.part_of_speech SEPARATOR ', ') as parts_of_speech FROM ai_user_word_progress uwp INNER JOIN ai_vocabulary v ON v.id = uwp.vocabulary_id LEFT JOIN ai_vocabulary_definitions vd ON vd.vocabulary_id = v.id WHERE uwp.user_id = ? AND uwp.is_favorite = true GROUP BY v.id, v.word, v.phonetic, v.audio_url, v.level, uwp.proficiency, uwp.study_count, uwp.status, uwp.next_review_at, uwp.created_at ORDER BY `+orderClause+` LIMIT ? OFFSET ? `, userID, pageSize, offset).Scan(&results).Error if err != nil { return nil, 0, err } return results, total, nil } // GetFavoriteWordsByBook 获取指定词汇书的生词本 func (s *WordBookService) GetFavoriteWordsByBook(userID int64, bookID string) ([]map[string]interface{}, error) { var results []map[string]interface{} err := s.db.Raw(` SELECT v.id, v.word, v.phonetic, v.audio_url, v.level, uwp.proficiency, uwp.study_count, uwp.status, GROUP_CONCAT(DISTINCT vd.definition_cn SEPARATOR '; ') as definitions, GROUP_CONCAT(DISTINCT vd.part_of_speech SEPARATOR ', ') as parts_of_speech FROM ai_user_word_progress uwp INNER JOIN ai_vocabulary v ON v.id = uwp.vocabulary_id INNER JOIN ai_vocabulary_book_words vbw ON CAST(vbw.vocabulary_id AS UNSIGNED) = v.id LEFT JOIN ai_vocabulary_definitions vd ON vd.vocabulary_id = v.id WHERE uwp.user_id = ? AND uwp.is_favorite = true AND vbw.book_id = ? GROUP BY v.id, v.word, v.phonetic, v.audio_url, v.level, uwp.proficiency, uwp.study_count, uwp.status ORDER BY uwp.created_at DESC `, userID, bookID).Scan(&results).Error if err != nil { return nil, err } return results, nil } // GetFavoriteStats 获取生词本统计信息 func (s *WordBookService) GetFavoriteStats(userID int64) (map[string]interface{}, error) { var stats struct { TotalWords int64 MasteredWords int64 ReviewingWords int64 LearningWords int64 AvgProficiency float64 NeedReviewToday int64 } // 统计总数和各状态数量 s.db.Raw(` SELECT COUNT(*) as total_words, SUM(CASE WHEN status = 'mastered' THEN 1 ELSE 0 END) as mastered_words, SUM(CASE WHEN status = 'reviewing' THEN 1 ELSE 0 END) as reviewing_words, SUM(CASE WHEN status = 'learning' THEN 1 ELSE 0 END) as learning_words, AVG(proficiency) as avg_proficiency, SUM(CASE WHEN next_review_at IS NOT NULL AND next_review_at <= NOW() THEN 1 ELSE 0 END) as need_review_today FROM ai_user_word_progress WHERE user_id = ? AND is_favorite = true `, userID).Scan(&stats) return map[string]interface{}{ "total_words": stats.TotalWords, "mastered_words": stats.MasteredWords, "reviewing_words": stats.ReviewingWords, "learning_words": stats.LearningWords, "avg_proficiency": stats.AvgProficiency, "need_review_today": stats.NeedReviewToday, }, nil } // BatchAddToFavorite 批量添加到生词本 func (s *WordBookService) BatchAddToFavorite(userID int64, wordIDs []int64) (int, error) { count := 0 now := time.Now() for _, wordID := range wordIDs { var progress models.UserWordProgress err := s.db.Where("user_id = ? AND vocabulary_id = ?", userID, wordID).First(&progress).Error if err == gorm.ErrRecordNotFound { // 创建新记录 progress = models.UserWordProgress{ UserID: userID, VocabularyID: wordID, Status: "not_started", StudyCount: 0, CorrectCount: 0, WrongCount: 0, Proficiency: 0, ReviewInterval: 1, FirstStudiedAt: now, LastStudiedAt: now, } if err := s.db.Create(&progress).Error; err != nil { continue } if err := s.db.Model(&progress).Update("is_favorite", true).Error; err != nil { continue } count++ } else if err == nil && !progress.IsFavorite { // 更新现有记录 if err := s.db.Model(&progress).Update("is_favorite", true).Error; err != nil { continue } count++ } } return count, nil } // RemoveFromFavorite 从生词本移除 func (s *WordBookService) RemoveFromFavorite(userID, wordID int64) error { return s.db.Model(&models.UserWordProgress{}). Where("user_id = ? AND vocabulary_id = ?", userID, wordID). Update("is_favorite", false).Error }