From 0959ca117cd203097706f372986eea889f1b9eae Mon Sep 17 00:00:00 2001 From: sugarBecomer Date: Fri, 19 Dec 2025 15:47:51 +0800 Subject: [PATCH] =?UTF-8?q?:bug:=20=E5=AD=A6=E4=B9=A0=E9=80=9A=E5=88=B7?= =?UTF-8?q?=E8=AF=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/server/main.go | 4 +- .../xuexitong/xuexitong_auto_study.go | 129 ++++++++++++------ .../application/xuexitong/xuexitong_test.go | 1 + .../framework/schedule/xuexitong_handler.go | 13 +- 4 files changed, 94 insertions(+), 53 deletions(-) diff --git a/cmd/server/main.go b/cmd/server/main.go index fdc3e52..51a5ba0 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -50,8 +50,8 @@ func Run() { g.Log().Info(ctx, "server is going to shut down...") api.StopServer() g.Log().Info(ctx, "api server stopped") - //schedule.Stop() - //g.Log().Info(ctx, "schedule stopped") + schedule.Stop() + g.Log().Info(ctx, "schedule stopped") redis.Shutdown() g.Log().Info(ctx, "redis client closed") os.Exit(0) diff --git a/internal/application/xuexitong/xuexitong_auto_study.go b/internal/application/xuexitong/xuexitong_auto_study.go index 7a6a738..f44ff38 100644 --- a/internal/application/xuexitong/xuexitong_auto_study.go +++ b/internal/application/xuexitong/xuexitong_auto_study.go @@ -89,39 +89,7 @@ func (c *XuexitongCourse) AutoStudyAllCourses(ctx context.Context) error { // AutoStudyCourse 自动学习指定课程的所有章节 func (c *XuexitongCourse) AutoStudyCourse(ctx context.Context, course entity.XueXiTCourse) error { - logger.Logger.Info(fmt.Sprintf("开始刷课程: %s", course.CourseName)) - - chapters := c.PullCourseChapterList(ctx, course) - if len(chapters) == 0 { - logger.Logger.Warn(fmt.Sprintf("课程 %s 没有章节", course.CourseName)) - return nil - } - - _, err := c.FetchChapterPointStatus(ctx, course, chapters) - if err != nil { - logger.Logger.Warn("获取章节状态失败", zap.Error(err)) - } - - for _, chapter := range chapters { - if err := c.checkTimeRestriction(); err != nil { - logger.Logger.Info("时间限制,暂停刷课", zap.String("course", course.CourseName)) - return err - } - - if chapter.JobCount > 0 || chapter.PointTotal > chapter.PointFinish { - err := c.AutoStudyChapter(ctx, course, chapter) - if err != nil { - if err == ErrTimeRestriction { - return err - } - logger.Logger.Error(fmt.Sprintf("刷章节失败: %s", chapter.Name), zap.Error(err)) - continue - } - } - } - - logger.Logger.Info(fmt.Sprintf("课程完成: %s", course.CourseName)) - return nil + return c.AutoStudyCourseWithOptions(ctx, course, nil) } // AutoStudyChapter 自动学习指定章节的所有任务点 @@ -154,6 +122,7 @@ func (c *XuexitongCourse) AutoStudyChapter(ctx context.Context, course entity.Xu for i := range points { if err := c.checkTimeRestriction(); err != nil { + // 更新任务状态为waiting logger.Logger.Info("时间限制,暂停刷课", zap.String("chapter", chapter.Name)) return err } @@ -193,6 +162,7 @@ func (c *XuexitongCourse) AutoStudyChapter(ctx context.Context, course entity.Xu type AutoStudyOptions struct { AIConfig *AIConfig // AI配置(用于作业答题) AutoSubmit bool // 是否自动提交作业 + Thread int // 线程数 } // AutoStudyCourseWithOptions 带选项的自动学习课程 @@ -210,21 +180,90 @@ func (c *XuexitongCourse) AutoStudyCourseWithOptions(ctx context.Context, course logger.Logger.Warn("获取章节状态失败", zap.Error(err)) } - for _, chapter := range chapters { - if err := c.checkTimeRestriction(); err != nil { - logger.Logger.Info("时间限制,暂停刷课", zap.String("course", course.CourseName)) - return err + // 获取线程数配置,默认为1(单线程) + threadCount := 1 + if opts != nil && opts.Thread > 0 { + threadCount = opts.Thread + } + + // 如果是单线程,使用原有逻辑 + if threadCount == 1 { + for _, chapter := range chapters { + if err := c.checkTimeRestriction(); err != nil { + logger.Logger.Info("时间限制,暂停刷课", zap.String("course", course.CourseName)) + return err + } + + if chapter.JobCount > 0 || chapter.PointTotal > chapter.PointFinish { + err := c.AutoStudyChapterWithOptions(ctx, course, chapter, opts) + if err != nil { + if err == ErrTimeRestriction { + return err + } + logger.Logger.Error(fmt.Sprintf("刷章节失败: %s", chapter.Name), zap.Error(err)) + continue + } + } + } + } else { + // 多线程并发处理 + logger.Logger.Info(fmt.Sprintf("使用 %d 线程并发刷课", threadCount)) + + // 筛选需要处理的章节 + var targetChapters []entity.XueXiTCourseChapter + for _, chapter := range chapters { + if chapter.JobCount > 0 || chapter.PointTotal > chapter.PointFinish { + targetChapters = append(targetChapters, chapter) + } } - if chapter.JobCount > 0 && chapter.PointTotal > chapter.PointFinish { - //logger.Logger.Info("开始处理章节", zap.String("chapter", chapter.Name)) - err := c.AutoStudyChapterWithOptions(ctx, course, chapter, opts) - if err != nil { - if err == ErrTimeRestriction { - return err + if len(targetChapters) == 0 { + logger.Logger.Info("没有需要处理的章节") + return nil + } + + // 创建信号量控制并发数 + semaphore := make(chan struct{}, threadCount) + errChan := make(chan error, len(targetChapters)) + doneChan := make(chan bool, len(targetChapters)) + + // 并发处理章节 + for _, chapter := range targetChapters { + semaphore <- struct{}{} // 获取信号量 + + go func(ch entity.XueXiTCourseChapter) { + defer func() { + <-semaphore // 释放信号量 + doneChan <- true + }() + + if err := c.checkTimeRestriction(); err != nil { + logger.Logger.Info("时间限制,暂停刷课", zap.String("chapter", ch.Name)) + errChan <- err + return } - logger.Logger.Error(fmt.Sprintf("刷章节失败: %s", chapter.Name), zap.Error(err)) - continue + + err := c.AutoStudyChapterWithOptions(ctx, course, ch, opts) + if err != nil { + if err == ErrTimeRestriction { + errChan <- err + return + } + logger.Logger.Error(fmt.Sprintf("刷章节失败: %s", ch.Name), zap.Error(err)) + } + }(chapter) + } + + // 等待所有任务完成 + for i := 0; i < len(targetChapters); i++ { + <-doneChan + } + close(errChan) + + // 检查是否有时间限制错误 + for err := range errChan { + if err == ErrTimeRestriction { + return err } } } diff --git a/internal/application/xuexitong/xuexitong_test.go b/internal/application/xuexitong/xuexitong_test.go index 91bfc44..c02d107 100644 --- a/internal/application/xuexitong/xuexitong_test.go +++ b/internal/application/xuexitong/xuexitong_test.go @@ -56,6 +56,7 @@ func TestSchedule(t *testing.T) { err = courseService.AutoStudyByCourseIDWithOptions(ctx, "258109701", &AutoStudyOptions{ AIConfig: config, AutoSubmit: true, // 默认不自动提交,仅保存 + Thread: 5, }) if err != nil { utils.OkOrPanic(errors.NewBusinessErrorWithDetail(503, "刷课失败", err)) diff --git a/internal/framework/schedule/xuexitong_handler.go b/internal/framework/schedule/xuexitong_handler.go index b250f23..8296cde 100644 --- a/internal/framework/schedule/xuexitong_handler.go +++ b/internal/framework/schedule/xuexitong_handler.go @@ -78,10 +78,6 @@ func (h *XuexitongTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task currentMinutes := currentHour*60 + currentMinute if currentMinutes < startTime || currentMinutes >= endTime { - // logger.Logger.Debug("非暴力模式,当前时间不在执行时间范围内(8:30-17:30),跳过执行", - // zap.Int64("courseID", targetCourse.ID), - // zap.Int("currentHour", currentHour), - // zap.Int("currentMinute", currentMinute)) return nil } } @@ -134,8 +130,12 @@ func (h *XuexitongTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task startTime := 8*60 + 30 // 8:30 endTime := 17*60 + 30 // 17:30 currentMinutes := currentHour*60 + currentMinute - - return currentMinutes >= startTime && currentMinutes < endTime + needStudy := currentMinutes >= startTime && currentMinutes < endTime + if !needStudy { + // 更新状态为等待中 + db.Global.CourseEntity.UpdateOneID(taskPayload.CourseID).SetStatus(courseentity.StatusWaiting).ExecX(ctx) + } + return needStudy }) } @@ -193,6 +193,7 @@ func (h *XuexitongTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task err = courseService.AutoStudyByCourseIDWithOptions(ctx, targetCourse.CourseID, &xuexitong.AutoStudyOptions{ AIConfig: aiConfig, AutoSubmit: true, // 默认不自动提交,仅保存 + Thread: 5, }) if err != nil { // 如果是时间限制导致的停止,不算失败