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
|
|||
|
|
}
|