318 lines
8.5 KiB
Go
318 lines
8.5 KiB
Go
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
|
||
}
|