152 lines
4.1 KiB
Go
152 lines
4.1 KiB
Go
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)
|
|
}
|