🐛 学习通刷课

This commit is contained in:
sugarBecomer
2025-12-19 15:47:51 +08:00
parent 3641fb1782
commit 0959ca117c
4 changed files with 94 additions and 53 deletions
+2 -2
View File
@@ -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)
@@ -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
}
}
}
@@ -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))
@@ -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 {
// 如果是时间限制导致的停止,不算失败