init
This commit is contained in:
317
serve/internal/services/study_plan_service.go
Normal file
317
serve/internal/services/study_plan_service.go
Normal file
@@ -0,0 +1,317 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user