Files
ai_english/serve/internal/services/study_plan_service.go
2025-11-17 13:39:05 +08:00

318 lines
8.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package services
import (
"errors"
"time"
"github.com/Nanqipro/YunQue-Tech-Projects/ai_english_learning/serve/internal/models"
"gorm.io/gorm"
)
// StudyPlanService 学习计划服务
type StudyPlanService struct {
db *gorm.DB
}
func NewStudyPlanService(db *gorm.DB) *StudyPlanService {
return &StudyPlanService{db: db}
}
// CreateStudyPlan 创建学习计划
func (s *StudyPlanService) CreateStudyPlan(userID int64, planName string, description *string, dailyGoal int,
bookID *string, startDate time.Time, endDate *time.Time, remindTime *string, remindDays *string) (*models.StudyPlan, error) {
// 验证参数
if dailyGoal < 1 || dailyGoal > 200 {
return nil, errors.New("每日目标必须在1-200之间")
}
// 如果指定了词汇书,获取总单词数
totalWords := 0
if bookID != nil && *bookID != "" {
var count int64
s.db.Model(&models.VocabularyBookWord{}).Where("book_id = ?", *bookID).Count(&count)
totalWords = int(count)
}
plan := &models.StudyPlan{
UserID: userID,
BookID: bookID,
PlanName: planName,
Description: description,
DailyGoal: dailyGoal,
TotalWords: totalWords,
LearnedWords: 0,
StartDate: startDate,
EndDate: endDate,
Status: "active",
RemindTime: remindTime,
RemindDays: remindDays,
IsRemindEnabled: remindTime != nil && remindDays != nil,
StreakDays: 0,
}
if err := s.db.Create(plan).Error; err != nil {
return nil, err
}
return plan, nil
}
// GetUserStudyPlans 获取用户的学习计划列表
func (s *StudyPlanService) GetUserStudyPlans(userID int64, status string) ([]models.StudyPlan, error) {
var plans []models.StudyPlan
query := s.db.Where("user_id = ?", userID)
if status != "" && status != "all" {
query = query.Where("status = ?", status)
}
err := query.Order("created_at DESC").Find(&plans).Error
return plans, err
}
// GetStudyPlanByID 获取学习计划详情
func (s *StudyPlanService) GetStudyPlanByID(planID, userID int64) (*models.StudyPlan, error) {
var plan models.StudyPlan
err := s.db.Where("id = ? AND user_id = ?", planID, userID).First(&plan).Error
if err != nil {
return nil, err
}
return &plan, nil
}
// UpdateStudyPlan 更新学习计划
func (s *StudyPlanService) UpdateStudyPlan(planID, userID int64, updates map[string]interface{}) (*models.StudyPlan, error) {
var plan models.StudyPlan
if err := s.db.Where("id = ? AND user_id = ?", planID, userID).First(&plan).Error; err != nil {
return nil, err
}
// 验证dailyGoal
if dailyGoal, ok := updates["daily_goal"].(int); ok {
if dailyGoal < 1 || dailyGoal > 200 {
return nil, errors.New("每日目标必须在1-200之间")
}
}
if err := s.db.Model(&plan).Updates(updates).Error; err != nil {
return nil, err
}
return &plan, nil
}
// DeleteStudyPlan 删除学习计划(软删除)
func (s *StudyPlanService) DeleteStudyPlan(planID, userID int64) error {
result := s.db.Where("id = ? AND user_id = ?", planID, userID).Delete(&models.StudyPlan{})
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("计划不存在或无权删除")
}
return nil
}
// UpdatePlanStatus 更新计划状态
func (s *StudyPlanService) UpdatePlanStatus(planID, userID int64, status string) error {
validStatuses := map[string]bool{
"active": true,
"paused": true,
"completed": true,
"cancelled": true,
}
if !validStatuses[status] {
return errors.New("无效的状态")
}
updates := map[string]interface{}{
"status": status,
}
if status == "completed" {
now := time.Now()
updates["completed_at"] = now
}
result := s.db.Model(&models.StudyPlan{}).
Where("id = ? AND user_id = ?", planID, userID).
Updates(updates)
if result.Error != nil {
return result.Error
}
if result.RowsAffected == 0 {
return errors.New("计划不存在或无权修改")
}
return nil
}
// RecordStudyProgress 记录学习进度
func (s *StudyPlanService) RecordStudyProgress(planID, userID int64, wordsStudied, studyDuration int) error {
var plan models.StudyPlan
if err := s.db.Where("id = ? AND user_id = ?", planID, userID).First(&plan).Error; err != nil {
return err
}
today := time.Now().Truncate(24 * time.Hour)
// 检查今天是否已有记录
var record models.StudyPlanRecord
err := s.db.Where("plan_id = ? AND user_id = ? AND study_date = ?", planID, userID, today).First(&record).Error
if err == gorm.ErrRecordNotFound {
// 创建新记录
record = models.StudyPlanRecord{
PlanID: planID,
UserID: userID,
StudyDate: today,
WordsStudied: wordsStudied,
GoalCompleted: wordsStudied >= plan.DailyGoal,
StudyDuration: studyDuration,
}
if err := s.db.Create(&record).Error; err != nil {
return err
}
} else if err == nil {
// 更新现有记录
record.WordsStudied += wordsStudied
record.StudyDuration += studyDuration
record.GoalCompleted = record.WordsStudied >= plan.DailyGoal
if err := s.db.Save(&record).Error; err != nil {
return err
}
} else {
return err
}
// 更新计划的已学单词数
plan.LearnedWords += wordsStudied
// 更新连续学习天数
if plan.LastStudyDate != nil {
lastDate := plan.LastStudyDate.Truncate(24 * time.Hour)
yesterday := today.AddDate(0, 0, -1)
if lastDate.Equal(yesterday) {
plan.StreakDays++
} else if !lastDate.Equal(today) {
plan.StreakDays = 1
}
} else {
plan.StreakDays = 1
}
plan.LastStudyDate = &today
// 检查是否完成计划
if plan.TotalWords > 0 && plan.LearnedWords >= plan.TotalWords {
plan.Status = "completed"
now := time.Now()
plan.CompletedAt = &now
}
return s.db.Save(&plan).Error
}
// GetStudyPlanStatistics 获取学习计划统计
func (s *StudyPlanService) GetStudyPlanStatistics(planID, userID int64) (map[string]interface{}, error) {
var plan models.StudyPlan
if err := s.db.Where("id = ? AND user_id = ?", planID, userID).First(&plan).Error; err != nil {
return nil, err
}
// 统计总学习天数
var totalStudyDays int64
s.db.Model(&models.StudyPlanRecord{}).
Where("plan_id = ? AND user_id = ?", planID, userID).
Count(&totalStudyDays)
// 统计完成目标的天数
var completedDays int64
s.db.Model(&models.StudyPlanRecord{}).
Where("plan_id = ? AND user_id = ? AND goal_completed = ?", planID, userID, true).
Count(&completedDays)
// 计算平均每日学习单词数
var avgWords float64
s.db.Model(&models.StudyPlanRecord{}).
Select("AVG(words_studied) as avg_words").
Where("plan_id = ? AND user_id = ?", planID, userID).
Scan(&avgWords)
// 计算完成率
completionRate := 0.0
if plan.TotalWords > 0 {
completionRate = float64(plan.LearnedWords) / float64(plan.TotalWords) * 100
}
// 获取最近7天的学习记录
var recentRecords []models.StudyPlanRecord
s.db.Where("plan_id = ? AND user_id = ?", planID, userID).
Order("study_date DESC").
Limit(7).
Find(&recentRecords)
return map[string]interface{}{
"plan": plan,
"total_study_days": totalStudyDays,
"completed_days": completedDays,
"avg_words": avgWords,
"completion_rate": completionRate,
"recent_records": recentRecords,
"streak_days": plan.StreakDays,
}, nil
}
// GetTodayStudyPlans 获取今日需要执行的学习计划
func (s *StudyPlanService) GetTodayStudyPlans(userID int64) ([]map[string]interface{}, error) {
var plans []models.StudyPlan
today := time.Now().Truncate(24 * time.Hour)
weekday := int(time.Now().Weekday())
if weekday == 0 {
weekday = 7 // 将周日从0改为7
}
// 查找活跃的计划
err := s.db.Where("user_id = ? AND status = ? AND start_date <= ?", userID, "active", today).
Find(&plans).Error
if err != nil {
return nil, err
}
result := make([]map[string]interface{}, 0)
for _, plan := range plans {
// 检查今天是否已完成
var record models.StudyPlanRecord
err := s.db.Where("plan_id = ? AND user_id = ? AND study_date = ?", plan.ID, userID, today).
First(&record).Error
todayCompleted := err == nil && record.GoalCompleted
todayProgress := 0
if err == nil {
todayProgress = record.WordsStudied
}
// 检查是否需要提醒
needRemind := false
if plan.IsRemindEnabled && plan.RemindDays != nil {
// 简单检查这里可以根据RemindDays判断今天是否需要提醒
needRemind = true
}
result = append(result, map[string]interface{}{
"plan": plan,
"today_completed": todayCompleted,
"today_progress": todayProgress,
"need_remind": needRemind,
})
}
return result, nil
}