This commit is contained in:
sjk
2025-11-17 13:39:05 +08:00
commit d4cfe2b9de
479 changed files with 109324 additions and 0 deletions

View File

@@ -0,0 +1,237 @@
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
}