238 lines
6.7 KiB
Go
238 lines
6.7 KiB
Go
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
|
||
}
|