🐛 学习通刷课
This commit is contained in:
+2
-2
@@ -50,8 +50,8 @@ func Run() {
|
|||||||
g.Log().Info(ctx, "server is going to shut down...")
|
g.Log().Info(ctx, "server is going to shut down...")
|
||||||
api.StopServer()
|
api.StopServer()
|
||||||
g.Log().Info(ctx, "api server stopped")
|
g.Log().Info(ctx, "api server stopped")
|
||||||
//schedule.Stop()
|
schedule.Stop()
|
||||||
//g.Log().Info(ctx, "schedule stopped")
|
g.Log().Info(ctx, "schedule stopped")
|
||||||
redis.Shutdown()
|
redis.Shutdown()
|
||||||
g.Log().Info(ctx, "redis client closed")
|
g.Log().Info(ctx, "redis client closed")
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
|||||||
@@ -89,39 +89,7 @@ func (c *XuexitongCourse) AutoStudyAllCourses(ctx context.Context) error {
|
|||||||
|
|
||||||
// AutoStudyCourse 自动学习指定课程的所有章节
|
// AutoStudyCourse 自动学习指定课程的所有章节
|
||||||
func (c *XuexitongCourse) AutoStudyCourse(ctx context.Context, course entity.XueXiTCourse) error {
|
func (c *XuexitongCourse) AutoStudyCourse(ctx context.Context, course entity.XueXiTCourse) error {
|
||||||
logger.Logger.Info(fmt.Sprintf("开始刷课程: %s", course.CourseName))
|
return c.AutoStudyCourseWithOptions(ctx, course, nil)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoStudyChapter 自动学习指定章节的所有任务点
|
// AutoStudyChapter 自动学习指定章节的所有任务点
|
||||||
@@ -154,6 +122,7 @@ func (c *XuexitongCourse) AutoStudyChapter(ctx context.Context, course entity.Xu
|
|||||||
|
|
||||||
for i := range points {
|
for i := range points {
|
||||||
if err := c.checkTimeRestriction(); err != nil {
|
if err := c.checkTimeRestriction(); err != nil {
|
||||||
|
// 更新任务状态为waiting
|
||||||
logger.Logger.Info("时间限制,暂停刷课", zap.String("chapter", chapter.Name))
|
logger.Logger.Info("时间限制,暂停刷课", zap.String("chapter", chapter.Name))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -193,6 +162,7 @@ func (c *XuexitongCourse) AutoStudyChapter(ctx context.Context, course entity.Xu
|
|||||||
type AutoStudyOptions struct {
|
type AutoStudyOptions struct {
|
||||||
AIConfig *AIConfig // AI配置(用于作业答题)
|
AIConfig *AIConfig // AI配置(用于作业答题)
|
||||||
AutoSubmit bool // 是否自动提交作业
|
AutoSubmit bool // 是否自动提交作业
|
||||||
|
Thread int // 线程数
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoStudyCourseWithOptions 带选项的自动学习课程
|
// AutoStudyCourseWithOptions 带选项的自动学习课程
|
||||||
@@ -210,21 +180,90 @@ func (c *XuexitongCourse) AutoStudyCourseWithOptions(ctx context.Context, course
|
|||||||
logger.Logger.Warn("获取章节状态失败", zap.Error(err))
|
logger.Logger.Warn("获取章节状态失败", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, chapter := range chapters {
|
// 获取线程数配置,默认为1(单线程)
|
||||||
if err := c.checkTimeRestriction(); err != nil {
|
threadCount := 1
|
||||||
logger.Logger.Info("时间限制,暂停刷课", zap.String("course", course.CourseName))
|
if opts != nil && opts.Thread > 0 {
|
||||||
return err
|
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 {
|
if len(targetChapters) == 0 {
|
||||||
//logger.Logger.Info("开始处理章节", zap.String("chapter", chapter.Name))
|
logger.Logger.Info("没有需要处理的章节")
|
||||||
err := c.AutoStudyChapterWithOptions(ctx, course, chapter, opts)
|
return nil
|
||||||
if err != nil {
|
}
|
||||||
if err == ErrTimeRestriction {
|
|
||||||
return err
|
// 创建信号量控制并发数
|
||||||
|
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{
|
err = courseService.AutoStudyByCourseIDWithOptions(ctx, "258109701", &AutoStudyOptions{
|
||||||
AIConfig: config,
|
AIConfig: config,
|
||||||
AutoSubmit: true, // 默认不自动提交,仅保存
|
AutoSubmit: true, // 默认不自动提交,仅保存
|
||||||
|
Thread: 5,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.OkOrPanic(errors.NewBusinessErrorWithDetail(503, "刷课失败", err))
|
utils.OkOrPanic(errors.NewBusinessErrorWithDetail(503, "刷课失败", err))
|
||||||
|
|||||||
@@ -78,10 +78,6 @@ func (h *XuexitongTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task
|
|||||||
currentMinutes := currentHour*60 + currentMinute
|
currentMinutes := currentHour*60 + currentMinute
|
||||||
|
|
||||||
if currentMinutes < startTime || currentMinutes >= endTime {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,8 +130,12 @@ func (h *XuexitongTaskHandler) ProcessTask(ctx context.Context, task *asynq.Task
|
|||||||
startTime := 8*60 + 30 // 8:30
|
startTime := 8*60 + 30 // 8:30
|
||||||
endTime := 17*60 + 30 // 17:30
|
endTime := 17*60 + 30 // 17:30
|
||||||
currentMinutes := currentHour*60 + currentMinute
|
currentMinutes := currentHour*60 + currentMinute
|
||||||
|
needStudy := currentMinutes >= startTime && currentMinutes < endTime
|
||||||
return 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{
|
err = courseService.AutoStudyByCourseIDWithOptions(ctx, targetCourse.CourseID, &xuexitong.AutoStudyOptions{
|
||||||
AIConfig: aiConfig,
|
AIConfig: aiConfig,
|
||||||
AutoSubmit: true, // 默认不自动提交,仅保存
|
AutoSubmit: true, // 默认不自动提交,仅保存
|
||||||
|
Thread: 5,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 如果是时间限制导致的停止,不算失败
|
// 如果是时间限制导致的停止,不算失败
|
||||||
|
|||||||
Reference in New Issue
Block a user