kra-oa/internal/biz/task.go

152 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package biz
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"time"
"github.com/robfig/cron/v3"
)
type TimedTask struct {
ID uint
CreatedAt time.Time
UpdatedAt time.Time
Name, Description, Spec string
WithSeconds bool
ExecutorType, MethodName string
Params []byte
HTTPURL, HTTPMethod string
HTTPHeader []byte
HTTPBody string
HTTPAllowPrivate, Enabled bool
EnabledFilter *bool
}
type TimedTaskLog struct {
ID uint
CreatedAt time.Time
TaskID uint
TaskName, TriggerType string
StartedAt, FinishedAt time.Time
DurationMS int64
Status, ErrorMsg, Output string
}
const (
TaskExecutorMethod = "method"
TaskExecutorHTTP = "http"
TaskMethodClearDB = "ClearDB"
TaskMethodUploads = "CleanStaleUploads"
)
type TaskMethod struct {
Name string
Description string
}
var registeredTaskMethods = []TaskMethod{
{Name: TaskMethodClearDB, Description: "清理数据库过期日志操作记录、JWT黑名单、定时任务日志"},
{Name: TaskMethodUploads, Description: "清理过期大文件上传会话"},
}
func RegisteredTaskMethods() []TaskMethod {
return append([]TaskMethod(nil), registeredTaskMethods...)
}
func registeredTaskMethod(name string) bool {
for _, method := range registeredTaskMethods {
if method.Name == name {
return true
}
}
return false
}
type TaskRepo interface {
CreateTask(context.Context, *TimedTask) error
UpdateTask(context.Context, *TimedTask) error
DeleteTask(context.Context, uint) error
FindTask(context.Context, uint) (*TimedTask, error)
ListTasks(context.Context, int, int, *TimedTask) ([]*TimedTask, int64, error)
ToggleTask(context.Context, uint, bool) error
RecordTaskLog(context.Context, *TimedTaskLog) error
ListTaskLogs(context.Context, int, int, uint, string) ([]*TimedTaskLog, int64, error)
CleanupLogs(context.Context) error
TaskNameExists(context.Context, string, uint) (bool, error)
}
type TaskUsecase struct{ TaskRepo }
func NewTaskUsecase(repo TaskRepo) *TaskUsecase { return &TaskUsecase{TaskRepo: repo} }
func (uc *TaskUsecase) Validate(value *TimedTask) error {
if value.Name == "" {
return errors.New("任务名不能为空")
}
var err error
if value.WithSeconds {
_, err = cron.NewParser(cron.Second | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor).Parse(value.Spec)
} else {
_, err = cron.ParseStandard(value.Spec)
}
if err != nil {
return fmt.Errorf("cron 表达式非法: %w", err)
}
switch value.ExecutorType {
case TaskExecutorMethod:
if !registeredTaskMethod(value.MethodName) {
return errors.New("方法未注册")
}
if len(value.Params) > 0 && !json.Valid(value.Params) {
return errors.New("params 必须是合法 JSON")
}
case TaskExecutorHTTP:
parsed, parseErr := url.Parse(value.HTTPURL)
if parseErr != nil || (parsed.Scheme != "http" && parsed.Scheme != "https") || parsed.Hostname() == "" {
return errors.New("httpUrl 必须是合法的 http/https 地址")
}
if len(value.HTTPHeader) > 0 {
headers := map[string]string{}
if json.Unmarshal(value.HTTPHeader, &headers) != nil {
return errors.New("httpHeader 必须是 JSON 对象")
}
}
default:
return errors.New("executorType 必须为 method 或 http")
}
return nil
}
func (uc *TaskUsecase) Create(ctx context.Context, value *TimedTask) error {
if err := uc.Validate(value); err != nil {
return err
}
exists, err := uc.TaskNameExists(ctx, value.Name, 0)
if err != nil {
return err
}
if exists {
return fmt.Errorf("任务名 %s 已存在", value.Name)
}
return uc.CreateTask(ctx, value)
}
func (uc *TaskUsecase) Update(ctx context.Context, value *TimedTask) error {
if value.ID == 0 {
return errors.New("缺少任务 ID")
}
if err := uc.Validate(value); err != nil {
return err
}
exists, err := uc.TaskNameExists(ctx, value.Name, value.ID)
if err != nil {
return err
}
if exists {
return fmt.Errorf("任务名 %s 已存在", value.Name)
}
return uc.UpdateTask(ctx, value)
}