511 lines
16 KiB
Go
511 lines
16 KiB
Go
|
|
package services
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"time"
|
|||
|
|
|
|||
|
|
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models"
|
|||
|
|
"gorm.io/gorm"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// LearningSessionService 学习会话服务
|
|||
|
|
type LearningSessionService struct {
|
|||
|
|
db *gorm.DB
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func NewLearningSessionService(db *gorm.DB) *LearningSessionService {
|
|||
|
|
return &LearningSessionService{db: db}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// StartLearningSession 开始学习会话
|
|||
|
|
func (s *LearningSessionService) StartLearningSession(userID int64, bookID string, dailyGoal int) (*models.LearningSession, error) {
|
|||
|
|
now := time.Now()
|
|||
|
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|||
|
|
|
|||
|
|
// 检查今天是否已有学习会话
|
|||
|
|
var existingSession models.LearningSession
|
|||
|
|
err := s.db.Where("user_id = ? AND book_id = ? AND DATE(created_at) = ?",
|
|||
|
|
userID, bookID, today.Format("2006-01-02")).First(&existingSession).Error
|
|||
|
|
|
|||
|
|
if err == nil {
|
|||
|
|
// 已存在会话,返回现有会话
|
|||
|
|
return &existingSession, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err != gorm.ErrRecordNotFound {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建新的学习会话
|
|||
|
|
session := &models.LearningSession{
|
|||
|
|
UserID: userID,
|
|||
|
|
BookID: bookID,
|
|||
|
|
DailyGoal: dailyGoal,
|
|||
|
|
NewWordsCount: 0,
|
|||
|
|
ReviewCount: 0,
|
|||
|
|
MasteredCount: 0,
|
|||
|
|
StartedAt: now,
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if err := s.db.Create(session).Error; err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return session, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetTodayLearningTasks 获取今日学习任务(新词+复习词)
|
|||
|
|
// 学习逻辑:每天学习的单词 = 用户选择的新词数 + 当日所有需要复习的单词
|
|||
|
|
func (s *LearningSessionService) GetTodayLearningTasks(userID int64, bookID string, newWordsLimit int) (map[string]interface{}, error) {
|
|||
|
|
now := time.Now()
|
|||
|
|
|
|||
|
|
// 1. 获取所有需要复习的单词(到期的,不限数量)
|
|||
|
|
var reviewWords []models.UserWordProgress
|
|||
|
|
err := s.db.Raw(`
|
|||
|
|
SELECT uwp.*
|
|||
|
|
FROM ai_user_word_progress uwp
|
|||
|
|
INNER JOIN ai_vocabulary_book_words vbw ON CAST(vbw.vocabulary_id AS UNSIGNED) = uwp.vocabulary_id
|
|||
|
|
WHERE uwp.user_id = ?
|
|||
|
|
AND vbw.book_id = ?
|
|||
|
|
AND uwp.status IN ('learning', 'reviewing')
|
|||
|
|
AND (uwp.next_review_at IS NULL OR uwp.next_review_at <= ?)
|
|||
|
|
ORDER BY uwp.next_review_at ASC
|
|||
|
|
`, userID, bookID, now).Scan(&reviewWords).Error
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 2. 获取新单词(从未学习的)
|
|||
|
|
var newWords []int64
|
|||
|
|
err = s.db.Raw(`
|
|||
|
|
SELECT CAST(vbw.vocabulary_id AS UNSIGNED) as id
|
|||
|
|
FROM ai_vocabulary_book_words vbw
|
|||
|
|
LEFT JOIN ai_user_word_progress uwp ON uwp.vocabulary_id = CAST(vbw.vocabulary_id AS UNSIGNED) AND uwp.user_id = ?
|
|||
|
|
WHERE vbw.book_id = ?
|
|||
|
|
AND uwp.id IS NULL
|
|||
|
|
ORDER BY vbw.sort_order ASC
|
|||
|
|
LIMIT ?
|
|||
|
|
`, userID, bookID, newWordsLimit).Scan(&newWords).Error
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 3. 获取已掌握的单词统计
|
|||
|
|
var masteredCount int64
|
|||
|
|
s.db.Model(&models.UserWordProgress{}).
|
|||
|
|
Joins("INNER JOIN ai_vocabulary_book_words vbw ON CAST(vbw.vocabulary_id AS UNSIGNED) = ai_user_word_progress.vocabulary_id").
|
|||
|
|
Where("ai_user_word_progress.user_id = ? AND vbw.book_id = ? AND ai_user_word_progress.status = 'mastered'",
|
|||
|
|
userID, bookID).
|
|||
|
|
Count(&masteredCount)
|
|||
|
|
|
|||
|
|
// 4. 获取词汇书总词数
|
|||
|
|
var totalWords int64
|
|||
|
|
s.db.Model(&models.VocabularyBookWord{}).Where("book_id = ?", bookID).Count(&totalWords)
|
|||
|
|
|
|||
|
|
return map[string]interface{}{
|
|||
|
|
"newWords": newWords,
|
|||
|
|
"reviewWords": reviewWords,
|
|||
|
|
"masteredCount": masteredCount,
|
|||
|
|
"totalWords": totalWords,
|
|||
|
|
"progress": float64(masteredCount) / float64(totalWords) * 100,
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RecordWordStudy 记录单词学习结果
|
|||
|
|
func (s *LearningSessionService) RecordWordStudy(userID int64, wordID int64, difficulty string) (*models.UserWordProgress, error) {
|
|||
|
|
now := time.Now()
|
|||
|
|
|
|||
|
|
var progress models.UserWordProgress
|
|||
|
|
err := s.db.Where("user_id = ? AND vocabulary_id = ?", userID, wordID).First(&progress).Error
|
|||
|
|
|
|||
|
|
isNew := false
|
|||
|
|
if err == gorm.ErrRecordNotFound {
|
|||
|
|
isNew = true
|
|||
|
|
// 创建新记录
|
|||
|
|
progress = models.UserWordProgress{
|
|||
|
|
UserID: userID,
|
|||
|
|
VocabularyID: wordID,
|
|||
|
|
Status: "learning",
|
|||
|
|
StudyCount: 0,
|
|||
|
|
CorrectCount: 0,
|
|||
|
|
WrongCount: 0,
|
|||
|
|
Proficiency: 0,
|
|||
|
|
ReviewInterval: 1,
|
|||
|
|
FirstStudiedAt: now,
|
|||
|
|
LastStudiedAt: now,
|
|||
|
|
}
|
|||
|
|
} else if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新学习统计
|
|||
|
|
progress.StudyCount++
|
|||
|
|
progress.LastStudiedAt = now
|
|||
|
|
|
|||
|
|
// 根据难度更新进度和计算下次复习时间
|
|||
|
|
nextInterval := s.calculateNextInterval(progress.ReviewInterval, difficulty, progress.StudyCount)
|
|||
|
|
|
|||
|
|
switch difficulty {
|
|||
|
|
case "forgot":
|
|||
|
|
// 完全忘记:重置
|
|||
|
|
progress.WrongCount++
|
|||
|
|
progress.Proficiency = max(0, progress.Proficiency-30)
|
|||
|
|
progress.ReviewInterval = 1
|
|||
|
|
progress.Status = "learning"
|
|||
|
|
progress.NextReviewAt = &[]time.Time{now.Add(24 * time.Hour)}[0]
|
|||
|
|
|
|||
|
|
case "hard":
|
|||
|
|
// 困难:小幅增加间隔
|
|||
|
|
progress.WrongCount++
|
|||
|
|
progress.Proficiency = max(0, progress.Proficiency-10)
|
|||
|
|
progress.ReviewInterval = max(1, nextInterval/2)
|
|||
|
|
nextReview := now.Add(time.Duration(progress.ReviewInterval) * 24 * time.Hour)
|
|||
|
|
progress.NextReviewAt = &nextReview
|
|||
|
|
|
|||
|
|
case "good":
|
|||
|
|
// 一般:正常增加
|
|||
|
|
progress.CorrectCount++
|
|||
|
|
progress.Proficiency = min(100, progress.Proficiency+15)
|
|||
|
|
progress.ReviewInterval = nextInterval
|
|||
|
|
nextReview := now.Add(time.Duration(progress.ReviewInterval) * 24 * time.Hour)
|
|||
|
|
progress.NextReviewAt = &nextReview
|
|||
|
|
|
|||
|
|
// 更新状态
|
|||
|
|
if progress.StudyCount >= 3 && progress.Proficiency >= 60 {
|
|||
|
|
progress.Status = "reviewing"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
case "easy":
|
|||
|
|
// 容易:大幅增加间隔
|
|||
|
|
progress.CorrectCount++
|
|||
|
|
progress.Proficiency = min(100, progress.Proficiency+25)
|
|||
|
|
progress.ReviewInterval = int(float64(nextInterval) * 1.5)
|
|||
|
|
nextReview := now.Add(time.Duration(progress.ReviewInterval) * 24 * time.Hour)
|
|||
|
|
progress.NextReviewAt = &nextReview
|
|||
|
|
|
|||
|
|
// 更新状态
|
|||
|
|
if progress.StudyCount >= 2 && progress.Proficiency >= 70 {
|
|||
|
|
progress.Status = "reviewing"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
case "perfect":
|
|||
|
|
// 完美:最大间隔
|
|||
|
|
progress.CorrectCount++
|
|||
|
|
progress.Proficiency = 100
|
|||
|
|
progress.ReviewInterval = nextInterval * 2
|
|||
|
|
nextReview := now.Add(time.Duration(progress.ReviewInterval) * 24 * time.Hour)
|
|||
|
|
progress.NextReviewAt = &nextReview
|
|||
|
|
|
|||
|
|
// 达到掌握标准
|
|||
|
|
if progress.StudyCount >= 5 && progress.Proficiency >= 90 {
|
|||
|
|
progress.Status = "mastered"
|
|||
|
|
progress.MasteredAt = &now
|
|||
|
|
} else {
|
|||
|
|
progress.Status = "reviewing"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存或更新
|
|||
|
|
if isNew {
|
|||
|
|
if err := s.db.Create(&progress).Error; err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
if err := s.db.Save(&progress).Error; err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新今日学习会话的计数器
|
|||
|
|
s.updateTodaySessionStats(userID)
|
|||
|
|
|
|||
|
|
return &progress, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// updateTodaySessionStats 更新今日学习会话的统计数据
|
|||
|
|
func (s *LearningSessionService) updateTodaySessionStats(userID int64) {
|
|||
|
|
now := time.Now()
|
|||
|
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|||
|
|
|
|||
|
|
// 查找今日会话
|
|||
|
|
var session models.LearningSession
|
|||
|
|
err := s.db.Where("user_id = ? AND DATE(created_at) = ?", userID, today.Format("2006-01-02")).First(&session).Error
|
|||
|
|
if err != nil {
|
|||
|
|
return // 没有会话就不更新
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 统计今日学习的单词
|
|||
|
|
var stats struct {
|
|||
|
|
NewWords int64
|
|||
|
|
ReviewWords int64
|
|||
|
|
MasteredWords int64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 今日新学单词(首次学习时间是今天)
|
|||
|
|
s.db.Model(&models.UserWordProgress{}).
|
|||
|
|
Where("user_id = ? AND DATE(first_studied_at) = ?", userID, today.Format("2006-01-02")).
|
|||
|
|
Count(&stats.NewWords)
|
|||
|
|
|
|||
|
|
// 今日复习单词(首次学习不是今天,但最后学习是今天)
|
|||
|
|
s.db.Model(&models.UserWordProgress{}).
|
|||
|
|
Where("user_id = ? AND DATE(first_studied_at) != ? AND DATE(last_studied_at) = ?",
|
|||
|
|
userID, today.Format("2006-01-02"), today.Format("2006-01-02")).
|
|||
|
|
Count(&stats.ReviewWords)
|
|||
|
|
|
|||
|
|
// 今日掌握单词(掌握时间是今天)
|
|||
|
|
s.db.Model(&models.UserWordProgress{}).
|
|||
|
|
Where("user_id = ? AND DATE(mastered_at) = ?", userID, today.Format("2006-01-02")).
|
|||
|
|
Count(&stats.MasteredWords)
|
|||
|
|
|
|||
|
|
// 更新会话
|
|||
|
|
session.NewWordsCount = int(stats.NewWords)
|
|||
|
|
session.ReviewCount = int(stats.ReviewWords)
|
|||
|
|
session.MasteredCount = int(stats.MasteredWords)
|
|||
|
|
|
|||
|
|
s.db.Save(&session)
|
|||
|
|
|
|||
|
|
// 同时更新词汇书级别的进度
|
|||
|
|
s.updateBookProgress(userID, session.BookID)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// updateBookProgress 更新词汇书级别的学习进度
|
|||
|
|
func (s *LearningSessionService) updateBookProgress(userID int64, bookID string) {
|
|||
|
|
// 统计该词汇书的总体进度
|
|||
|
|
var progress struct {
|
|||
|
|
TotalLearned int64
|
|||
|
|
TotalMastered int64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 统计已学习的单词数(该词汇书中的所有已学习单词)
|
|||
|
|
s.db.Raw(`
|
|||
|
|
SELECT COUNT(DISTINCT uwp.vocabulary_id) as total_learned,
|
|||
|
|
SUM(CASE WHEN uwp.status = 'mastered' THEN 1 ELSE 0 END) as total_mastered
|
|||
|
|
FROM ai_user_word_progress uwp
|
|||
|
|
INNER JOIN ai_vocabulary_book_words vbw ON CAST(vbw.vocabulary_id AS UNSIGNED) = uwp.vocabulary_id
|
|||
|
|
WHERE uwp.user_id = ? AND vbw.book_id = ?
|
|||
|
|
`, userID, bookID).Scan(&progress)
|
|||
|
|
|
|||
|
|
// 获取词汇书总单词数
|
|||
|
|
var totalWords int64
|
|||
|
|
s.db.Model(&models.VocabularyBookWord{}).Where("book_id = ?", bookID).Count(&totalWords)
|
|||
|
|
|
|||
|
|
// 计算进度百分比
|
|||
|
|
progressPercentage := 0.0
|
|||
|
|
if totalWords > 0 {
|
|||
|
|
progressPercentage = float64(progress.TotalLearned) / float64(totalWords) * 100
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新或创建词汇书进度记录
|
|||
|
|
now := time.Now()
|
|||
|
|
var bookProgress models.UserVocabularyBookProgress
|
|||
|
|
err := s.db.Where("user_id = ? AND book_id = ?", userID, bookID).First(&bookProgress).Error
|
|||
|
|
|
|||
|
|
if err == gorm.ErrRecordNotFound {
|
|||
|
|
// 创建新记录
|
|||
|
|
bookProgress = models.UserVocabularyBookProgress{
|
|||
|
|
UserID: userID,
|
|||
|
|
BookID: bookID,
|
|||
|
|
LearnedWords: int(progress.TotalLearned),
|
|||
|
|
MasteredWords: int(progress.TotalMastered),
|
|||
|
|
ProgressPercentage: progressPercentage,
|
|||
|
|
StartedAt: now,
|
|||
|
|
LastStudiedAt: now,
|
|||
|
|
}
|
|||
|
|
s.db.Create(&bookProgress)
|
|||
|
|
} else if err == nil {
|
|||
|
|
// 更新现有记录
|
|||
|
|
bookProgress.LearnedWords = int(progress.TotalLearned)
|
|||
|
|
bookProgress.MasteredWords = int(progress.TotalMastered)
|
|||
|
|
bookProgress.ProgressPercentage = progressPercentage
|
|||
|
|
bookProgress.LastStudiedAt = now
|
|||
|
|
s.db.Save(&bookProgress)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// calculateNextInterval 计算下次复习间隔(天数)
|
|||
|
|
func (s *LearningSessionService) calculateNextInterval(currentInterval int, difficulty string, studyCount int) int {
|
|||
|
|
// 基于SuperMemo SM-2算法的简化版本
|
|||
|
|
baseIntervals := []int{1, 3, 7, 14, 30, 60, 120, 240}
|
|||
|
|
|
|||
|
|
if studyCount <= len(baseIntervals) {
|
|||
|
|
return baseIntervals[min(studyCount-1, len(baseIntervals)-1)]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 超过基础序列后,根据难度调整
|
|||
|
|
switch difficulty {
|
|||
|
|
case "forgot":
|
|||
|
|
return 1
|
|||
|
|
case "hard":
|
|||
|
|
return max(1, int(float64(currentInterval)*0.8))
|
|||
|
|
case "good":
|
|||
|
|
return int(float64(currentInterval) * 1.5)
|
|||
|
|
case "easy":
|
|||
|
|
return int(float64(currentInterval) * 2.5)
|
|||
|
|
case "perfect":
|
|||
|
|
return currentInterval * 3
|
|||
|
|
default:
|
|||
|
|
return currentInterval
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// UpdateSessionProgress 更新学习会话进度
|
|||
|
|
func (s *LearningSessionService) UpdateSessionProgress(sessionID int64, newWords, reviewWords, masteredWords int) error {
|
|||
|
|
return s.db.Model(&models.LearningSession{}).
|
|||
|
|
Where("id = ?", sessionID).
|
|||
|
|
Updates(map[string]interface{}{
|
|||
|
|
"new_words_count": newWords,
|
|||
|
|
"review_count": reviewWords,
|
|||
|
|
"mastered_count": masteredWords,
|
|||
|
|
"completed_at": time.Now(),
|
|||
|
|
}).Error
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetLearningStatistics 获取学习统计
|
|||
|
|
func (s *LearningSessionService) GetLearningStatistics(userID int64, bookID string) (map[string]interface{}, error) {
|
|||
|
|
// 今日学习统计
|
|||
|
|
now := time.Now()
|
|||
|
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|||
|
|
|
|||
|
|
var todaySession models.LearningSession
|
|||
|
|
s.db.Where("user_id = ? AND book_id = ? AND DATE(created_at) = ?",
|
|||
|
|
userID, bookID, today.Format("2006-01-02")).First(&todaySession)
|
|||
|
|
|
|||
|
|
// 总体统计
|
|||
|
|
var stats struct {
|
|||
|
|
TotalLearned int64
|
|||
|
|
TotalMastered int64
|
|||
|
|
AvgProficiency float64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
s.db.Model(&models.UserWordProgress{}).
|
|||
|
|
Select("COUNT(*) as total_learned, SUM(CASE WHEN status = 'mastered' THEN 1 ELSE 0 END) as total_mastered, AVG(proficiency) as avg_proficiency").
|
|||
|
|
Joins("INNER JOIN ai_vocabulary_book_words vbw ON CAST(vbw.vocabulary_id AS UNSIGNED) = ai_user_word_progress.vocabulary_id").
|
|||
|
|
Where("ai_user_word_progress.user_id = ? AND vbw.book_id = ?", userID, bookID).
|
|||
|
|
Scan(&stats)
|
|||
|
|
|
|||
|
|
// 连续学习天数
|
|||
|
|
var streakDays int
|
|||
|
|
s.db.Raw(`
|
|||
|
|
SELECT COUNT(DISTINCT DATE(created_at))
|
|||
|
|
FROM ai_learning_sessions
|
|||
|
|
WHERE user_id = ? AND book_id = ?
|
|||
|
|
AND created_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
|||
|
|
`, userID, bookID).Scan(&streakDays)
|
|||
|
|
|
|||
|
|
return map[string]interface{}{
|
|||
|
|
"todayNewWords": todaySession.NewWordsCount,
|
|||
|
|
"todayReview": todaySession.ReviewCount,
|
|||
|
|
"todayMastered": todaySession.MasteredCount,
|
|||
|
|
"totalLearned": stats.TotalLearned,
|
|||
|
|
"totalMastered": stats.TotalMastered,
|
|||
|
|
"avgProficiency": stats.AvgProficiency,
|
|||
|
|
"streakDays": streakDays,
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetTodayReviewWords 获取今日需要复习的所有单词(跨所有词汇书)
|
|||
|
|
func (s *LearningSessionService) GetTodayReviewWords(userID int64) ([]map[string]interface{}, error) {
|
|||
|
|
now := time.Now()
|
|||
|
|
|
|||
|
|
// 获取所有到期需要复习的单词
|
|||
|
|
var reviewWords []models.UserWordProgress
|
|||
|
|
err := s.db.Raw(`
|
|||
|
|
SELECT uwp.*
|
|||
|
|
FROM ai_user_word_progress uwp
|
|||
|
|
WHERE uwp.user_id = ?
|
|||
|
|
AND uwp.status IN ('learning', 'reviewing')
|
|||
|
|
AND (uwp.next_review_at IS NULL OR uwp.next_review_at <= ?)
|
|||
|
|
ORDER BY uwp.next_review_at ASC
|
|||
|
|
LIMIT 100
|
|||
|
|
`, userID, now).Scan(&reviewWords).Error
|
|||
|
|
|
|||
|
|
if err != nil {
|
|||
|
|
return nil, err
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取单词详情
|
|||
|
|
result := make([]map[string]interface{}, 0)
|
|||
|
|
for _, progress := range reviewWords {
|
|||
|
|
result = append(result, map[string]interface{}{
|
|||
|
|
"vocabulary_id": progress.VocabularyID,
|
|||
|
|
"status": progress.Status,
|
|||
|
|
"proficiency": progress.Proficiency,
|
|||
|
|
"study_count": progress.StudyCount,
|
|||
|
|
"next_review_at": progress.NextReviewAt,
|
|||
|
|
"last_studied_at": progress.LastStudiedAt,
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// GetTodayOverallStatistics 获取今日总体学习统计(所有词汇书)
|
|||
|
|
func (s *LearningSessionService) GetTodayOverallStatistics(userID int64) (map[string]interface{}, error) {
|
|||
|
|
now := time.Now()
|
|||
|
|
today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
|||
|
|
|
|||
|
|
// 今日学习的单词总数
|
|||
|
|
var todayStats struct {
|
|||
|
|
NewWords int64
|
|||
|
|
ReviewWords int64
|
|||
|
|
TotalStudied int64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 统计今日新学习的单词
|
|||
|
|
s.db.Model(&models.UserWordProgress{}).
|
|||
|
|
Where("user_id = ? AND DATE(first_studied_at) = ?", userID, today.Format("2006-01-02")).
|
|||
|
|
Count(&todayStats.NewWords)
|
|||
|
|
|
|||
|
|
// 统计今日复习的单词(今天学习但不是第一次)
|
|||
|
|
s.db.Model(&models.UserWordProgress{}).
|
|||
|
|
Where("user_id = ? AND DATE(first_studied_at) != ? AND DATE(last_studied_at) = ?",
|
|||
|
|
userID, today.Format("2006-01-02"), today.Format("2006-01-02")).
|
|||
|
|
Count(&todayStats.ReviewWords)
|
|||
|
|
|
|||
|
|
todayStats.TotalStudied = todayStats.NewWords + todayStats.ReviewWords
|
|||
|
|
|
|||
|
|
// 总体统计
|
|||
|
|
var totalStats struct {
|
|||
|
|
TotalLearned int64
|
|||
|
|
TotalMastered int64
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
s.db.Model(&models.UserWordProgress{}).
|
|||
|
|
Select("COUNT(*) as total_learned, SUM(CASE WHEN status = 'mastered' THEN 1 ELSE 0 END) as total_mastered").
|
|||
|
|
Where("user_id = ?", userID).
|
|||
|
|
Scan(&totalStats)
|
|||
|
|
|
|||
|
|
// 连续学习天数(最近30天内有学习记录的天数)
|
|||
|
|
var streakDays int64
|
|||
|
|
s.db.Raw(`
|
|||
|
|
SELECT COUNT(DISTINCT DATE(last_studied_at))
|
|||
|
|
FROM ai_user_word_progress
|
|||
|
|
WHERE user_id = ?
|
|||
|
|
AND last_studied_at >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)
|
|||
|
|
`, userID).Scan(&streakDays)
|
|||
|
|
|
|||
|
|
return map[string]interface{}{
|
|||
|
|
"todayNewWords": todayStats.NewWords,
|
|||
|
|
"todayReviewWords": todayStats.ReviewWords,
|
|||
|
|
"todayTotalStudied": todayStats.TotalStudied,
|
|||
|
|
"totalLearned": totalStats.TotalLearned,
|
|||
|
|
"totalMastered": totalStats.TotalMastered,
|
|||
|
|
"streakDays": streakDays,
|
|||
|
|
}, nil
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func min(a, b int) int {
|
|||
|
|
if a < b {
|
|||
|
|
return a
|
|||
|
|
}
|
|||
|
|
return b
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func max(a, b int) int {
|
|||
|
|
if a > b {
|
|||
|
|
return a
|
|||
|
|
}
|
|||
|
|
return b
|
|||
|
|
}
|