177 lines
5.0 KiB
Go
177 lines
5.0 KiB
Go
package schedule
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/gob"
|
|
"errors"
|
|
"fmt"
|
|
"likedream-goframe/internal/cst"
|
|
"likedream-goframe/internal/db"
|
|
"likedream-goframe/internal/ent"
|
|
"likedream-goframe/internal/ent/cptask"
|
|
"likedream-goframe/internal/framework/queue"
|
|
"likedream-goframe/internal/utils"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/gctx"
|
|
"github.com/hibiken/asynq"
|
|
"github.com/samber/oops"
|
|
)
|
|
|
|
var globalScanner *TaskScanner
|
|
|
|
func initScanner() {
|
|
globalScanner = NewTaskScanner()
|
|
go globalScanner.StartScan()
|
|
}
|
|
|
|
type TaskScanner struct {
|
|
ctx context.Context
|
|
stopSign chan struct{}
|
|
cancelFunc context.CancelFunc
|
|
}
|
|
|
|
func NewTaskScanner() *TaskScanner {
|
|
ctx, cancelFunc := context.WithCancel(gctx.GetInitCtx())
|
|
return &TaskScanner{
|
|
ctx: ctx,
|
|
stopSign: make(chan struct{}),
|
|
cancelFunc: cancelFunc,
|
|
}
|
|
}
|
|
|
|
func waitWithContext(ctx context.Context, d time.Duration) bool {
|
|
t := time.NewTimer(d)
|
|
defer t.Stop()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return false
|
|
case <-t.C:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (ts *TaskScanner) StartScan() {
|
|
for {
|
|
if !waitWithContext(ts.ctx, 30*time.Second) {
|
|
close(ts.stopSign)
|
|
return
|
|
}
|
|
|
|
ts.scanCpdailyTask()
|
|
}
|
|
}
|
|
|
|
func (ts *TaskScanner) StopScan() {
|
|
ts.cancelFunc()
|
|
timeoutTimer := time.NewTimer(10 * time.Second)
|
|
defer timeoutTimer.Stop()
|
|
|
|
select {
|
|
case <-timeoutTimer.C:
|
|
g.Log().Warningf(ts.ctx, "task scanner stop timeout")
|
|
case <-ts.stopSign:
|
|
}
|
|
}
|
|
|
|
func (ts *TaskScanner) scanCpdailyTask() {
|
|
|
|
currentTime := time.Now().In(cst.TimeZoneShangHai)
|
|
|
|
// 只查询30分钟以内的任务
|
|
minExecuteTime := currentTime.Add(-30 * time.Minute).Unix()
|
|
|
|
allTasks := db.Global.CpTask.Query().
|
|
Where(cptask.NextExecuteTimeLTE(currentTime.Unix()), cptask.NextExecuteTimeNotNil(), cptask.NextExecuteTimeGT(0), cptask.StepEQ(cptask.StepComplete), cptask.TaskStatusEQ(cptask.TaskStatusNotStart)).
|
|
Select(cptask.FieldID, cptask.FieldExecuteHour, cptask.FieldExecuteMinute, cptask.FieldNextExecuteTime).
|
|
AllX(ts.ctx)
|
|
|
|
var failedIDS []int64
|
|
|
|
for _, task := range allTasks {
|
|
// 30分钟之前的任务不再执行,但是要重置下次执行时间
|
|
if *task.NextExecuteTime < minExecuteTime {
|
|
originExecuteTime := time.Unix(*task.NextExecuteTime, 0).In(cst.TimeZoneShangHai).Format("2006-01-02 15:04:05")
|
|
g.Log().Debugf(ts.ctx, "任务ID %d 已过期,原来预计执行时间:%s,跳过执行", task.ID, originExecuteTime)
|
|
continue
|
|
}
|
|
|
|
payload := &CpdailyTaskPayload{
|
|
CpTaskID: task.ID,
|
|
}
|
|
|
|
payloadWriter := bytes.NewBuffer(nil)
|
|
|
|
e := gob.NewEncoder(payloadWriter).Encode(payload)
|
|
utils.OkOrPanic(e, oops.WithContext(ts.ctx).With("payload", payload).In("CpdailyTaskScanner.scanCpdailyTask").Wrap)
|
|
|
|
year, month, day := currentTime.Date()
|
|
execTime := time.Date(year, month, day, *task.ExecuteHour, *task.ExecuteMinute, 0, 0, cst.TimeZoneShangHai)
|
|
|
|
e = utils.Catch(func() {
|
|
queue.AddTask(ts.ctx, asynq.NewTask(cst.TaskCpdaily, payloadWriter.Bytes()),
|
|
asynq.TaskID(fmt.Sprintf("cpdaily:task:%d", task.ID)),
|
|
asynq.MaxRetry(3),
|
|
asynq.Unique(10*time.Minute),
|
|
asynq.ProcessAt(execTime),
|
|
asynq.Deadline(execTime.Add(12*time.Minute)),
|
|
asynq.Queue(cst.TaskQueueCpdaily),
|
|
)
|
|
})
|
|
if e != nil {
|
|
if errors.Is(e, asynq.ErrDuplicateTask) {
|
|
continue
|
|
} else if errors.Is(e, context.Canceled) {
|
|
return
|
|
} else {
|
|
utils.LogError(ts.ctx, "扫描今日校园任务时,入列失败", oops.With("task_id", task.ID).Wrap(e))
|
|
failedIDS = append(failedIDS, task.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(failedIDS) > 0 {
|
|
allTasks = slices.DeleteFunc(allTasks, func(task *ent.CpTask) bool {
|
|
return slices.Contains(failedIDS, task.ID)
|
|
})
|
|
}
|
|
|
|
for _, task := range allTasks {
|
|
nextExecuteAt := utils.GetCpdailyNextExecuteTime(task)
|
|
|
|
e := db.Global.CpTask.UpdateOneID(task.ID).Where(cptask.NextExecuteTimeNotNil()).SetNextExecuteTime(nextExecuteAt).Exec(ts.ctx)
|
|
utils.LogError(ts.ctx, "更新任务下次执行时间失败", oops.With("task_id", task.ID, "next_execute_at", nextExecuteAt).Wrap(e))
|
|
}
|
|
|
|
// 重置30分钟之前的任务到下次执行时间
|
|
expiredTasks := db.Global.CpTask.Query().
|
|
Where(cptask.NextExecuteTimeLT(minExecuteTime), cptask.NextExecuteTimeNotNil(), cptask.NextExecuteTimeGT(0)).
|
|
Select(cptask.FieldID, cptask.FieldExecuteHour, cptask.FieldExecuteMinute).
|
|
AllX(ts.ctx)
|
|
|
|
expiredIDs := slices.Collect(func(yield func(string) bool) {
|
|
for _, task := range expiredTasks {
|
|
if !yield(strconv.FormatInt(task.ID, 10)) {
|
|
return
|
|
}
|
|
}
|
|
})
|
|
|
|
if len(expiredIDs) > 0 {
|
|
g.Log().Warningf(ts.ctx, "以下任务ID已过期,重置了下次执行时间: %v", strings.Join(expiredIDs, ", "))
|
|
}
|
|
|
|
for _, task := range expiredTasks {
|
|
nextExecuteAt := utils.GetCpdailyNextExecuteTime(task)
|
|
|
|
e := db.Global.CpTask.UpdateOneID(task.ID).Where(cptask.NextExecuteTimeNotNil()).SetNextExecuteTime(nextExecuteAt).Exec(ts.ctx)
|
|
utils.LogError(ts.ctx, "重置过期任务下次执行时间失败", oops.With("task_id", task.ID, "next_execute_at", nextExecuteAt).Wrap(e))
|
|
}
|
|
}
|