Files
iotlcm/app/server/internal/tasks/tasks.go
T

387 lines
10 KiB
Go

package tasks
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"reflect"
"strings"
"sync"
"time"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/util/gconv"
"github.com/pkg/errors"
"github.com/robfig/cron/v3"
"go.opentelemetry.io/otel"
)
// Task 任务结构体 内置任务
type Task struct {
ID string //任务ID
TaskType string //任务类型
MethodName string //方法名
Params []interface{} //参数
Explain string //任务描述
}
type TaskOptions struct {
Uid string
Payload []byte
Expr *string // 仅周期任务
In *time.Duration // 仅一次性任务 当前时间的延迟
At *time.Time // 仅一次性任务 指定时间
Now *bool // 仅一次性任务 当前时间
Replace bool // 仅一次性任务 是否替换已存在的任务
Ctx context.Context // 仅一次性任务
MaxRetry int
Timeout int // 限制并发堵塞超时时间
Concurrent int // 0不限制并发 1限制并发
}
type taskInfo struct {
entryID int
option TaskOptions
run bool
}
type Tasks struct {
cron *cron.Cron
lock sync.Mutex
taskMap map[string]*taskInfo
}
var (
instance *Tasks
once sync.Once
)
func TasksInstance(ctx context.Context) *Tasks {
once.Do(func() {
instance, _ = NewTasks(ctx) // Initialize only once
})
return instance
}
// Once 注册一个任务以运行一次。
func (tk *Tasks) Once(ctx context.Context, taskOptions TaskOptions) error {
// return tk.worker.Once(options...)
info := &taskInfo{
entryID: 0,
option: taskOptions,
run: false,
}
spec := ""
if taskOptions.Expr != nil && *taskOptions.Expr != "" {
spec = *taskOptions.Expr
} else if taskOptions.In != nil {
spec = fmt.Sprintf("@every %s", *taskOptions.In)
} else if taskOptions.At != nil {
spec = fmt.Sprintf("@every %s", time.Until(*taskOptions.At).String())
} else if taskOptions.Now != nil && *taskOptions.Now {
go func() {
tk.process(ctx, info)
}()
g.Log().Infof(ctx, "Tasks.Once end. info: %+v", info)
return nil
} else {
return errors.New("expr is required")
}
tk.lock.Lock()
tk.taskMap[taskOptions.Uid] = info
tk.lock.Unlock()
entryID, _ := tk.cron.AddFunc(spec, func() {
tk.process(ctx, info)
tk.lock.Lock()
delete(tk.taskMap, taskOptions.Uid)
tk.lock.Unlock()
})
tk.lock.Lock()
info, ok := tk.taskMap[taskOptions.Uid]
if ok {
info.entryID = int(entryID)
}
tk.lock.Unlock()
g.Log().Infof(ctx, "Tasks.Once end. info: %+v", info)
return nil
}
// 注册一个任务以由cron表达式运行。
func (tk *Tasks) Cron(ctx context.Context, taskOptions TaskOptions) (id string, err error) {
spec := ""
if taskOptions.Expr != nil && *taskOptions.Expr != "" {
spec = *taskOptions.Expr
} else {
return "", errors.New("expr is required")
}
tk.lock.Lock()
tk.taskMap[taskOptions.Uid] = &taskInfo{
entryID: 0,
option: taskOptions,
run: false,
}
tk.lock.Unlock()
entryID, _ := tk.cron.AddFunc(spec, func() {
tk.lock.Lock()
info, ok := tk.taskMap[taskOptions.Uid]
g.Log().Infof(ctx, "Tasks.Cron AddFunc func begin. uid: %s, spec: %s, ok: %v", taskOptions.Uid, spec, ok)
if !ok {
tk.lock.Unlock()
return
}
tk.lock.Unlock()
start := time.Now()
for {
if taskOptions.Timeout > 0 && time.Since(start) > time.Second*time.Duration(taskOptions.Timeout) {
g.Log().Infof(ctx, "Tasks.Cron AddFunc func timeout. uid: %s, spec: %s, timeout: %d, start: %s", taskOptions.Uid, spec, taskOptions.Timeout, start.Format("2006-01-02 15:04:05"))
return
}
tk.lock.Lock()
if info.run && info.option.Concurrent == 1 {
tk.lock.Unlock()
time.Sleep(time.Second * 1)
continue
}
info.run = true
tk.lock.Unlock()
break
}
tk.process(ctx, info)
tk.lock.Lock()
info.run = false
tk.lock.Unlock()
})
tk.lock.Lock()
info, ok := tk.taskMap[taskOptions.Uid]
if ok {
info.entryID = int(entryID)
}
tk.lock.Unlock()
nextTime := tk.cron.Entry(entryID).Next
nextTimeStr := nextTime.Format("2006-01-02 15:04:05")
sub := nextTime.Sub(time.Now())
g.Log().Infof(ctx, "Tasks.Cron end. entryID: %d, uid: %s, spec: %s, nextTime:%s, sub:%v", info.entryID, info.option.Uid, spec, nextTimeStr, sub)
return gconv.String(info.entryID), nil
}
// 从任务队列中删除一个任务。
func (tk *Tasks) Remove(uid string) error {
tk.lock.Lock()
info, ok := tk.taskMap[uid]
if ok {
tk.cron.Remove(cron.EntryID(info.entryID))
delete(tk.taskMap, uid)
}
tk.lock.Unlock()
return nil
}
func (tk *Tasks) RemoveCron(entryID string) error {
tk.lock.Lock()
for _, info := range tk.taskMap {
if gconv.String(info.entryID) == entryID {
tk.cron.Remove(cron.EntryID(info.entryID))
delete(tk.taskMap, info.option.Uid)
break
}
}
tk.lock.Unlock()
return nil
}
func (tk *Tasks) GetTask(uid string) (task *Task, err error) {
tk.lock.Lock()
defer tk.lock.Unlock()
info, ok := tk.taskMap[uid]
if !ok {
return nil, errors.New("task not found")
}
taskRun := &Task{}
err = json.Unmarshal(info.option.Payload, &taskRun)
if err != nil {
return nil, err
}
return taskRun, nil
}
func NewTasks(ctx context.Context) (tk *Tasks, err error) {
defer func() {
e := recover()
if e != nil {
err = errors.Errorf("%v", e)
}
}()
// logger := &TaskLog{ctx: ctx}
c := cron.New(cron.WithLocation(time.Local),
cron.WithSeconds(),
// cron.WithChain(cron.SkipIfStillRunning(logger)),
)
// w := worker.New(
// worker.WithWorkerHandler(func(ctx context.Context, p worker.Payload) error {
// return process(task{
// ctx: ctx,
// payload: p,
// })
// }),
// worker.WithWorkerRedisPeriodKey("TaskSchedule"),
// )
// if w.Error != nil {
// err = errors.WithMessage(w.Error, "initialize worker failed")
// return
// }
c.Start()
tk = &Tasks{
cron: c,
taskMap: make(map[string]*taskInfo),
}
g.Log().Debug(ctx, "NewTasks initialize tasks success")
return
}
func (tk *Tasks) process(ctx context.Context, t *taskInfo) (err error) {
g.Log().Debug(ctx, "Tasks.process payload", t.option.Payload)
tr := otel.Tracer("task")
_, span := tr.Start(ctx, "Task")
defer span.End()
var taskRun Task
err = json.Unmarshal(t.option.Payload, &taskRun)
if err != nil {
return err
}
g.Log().Debug(ctx, "Tasks.process task", taskRun)
err = CallMethod(ctx, &taskRun)
if err != nil {
fmt.Println("CallMethod err:", err.Error())
return err
}
g.Log().Debug(ctx, "Tasks.process end. task", taskRun)
return
}
// CallMethod 调用任务的实际方法
func CallMethod(ctx context.Context, task *Task) (err error) {
// 获取TaskModel的反射值
taskValue := reflect.ValueOf(task)
// 准备要调用的方法的参数
var args []reflect.Value
for _, param := range task.Params {
if !reflect.ValueOf(param).IsValid() {
err = errors.New("invalid parameter")
return
}
args = append(args, reflect.ValueOf(param))
}
// 查找并执行方法
method := taskValue.MethodByName(task.MethodName)
if method.IsValid() {
if method.Type().NumIn() == 0 {
method.Call(nil)
return
} else if method.Type().NumIn() != len(args) {
err = errors.New("incorrect number of parameters")
return
}
g.Log().Debug(context.Background(), "执行的任务:", task.MethodName)
for i := 0; i < method.Type().NumIn(); i++ {
switch method.Type().In(i).Kind() {
case reflect.Float32:
args[i] = reflect.ValueOf(gconv.Float32(task.Params[i]))
case reflect.Float64:
args[i] = reflect.ValueOf(gconv.Float64(task.Params[i]))
case reflect.Uint64:
args[i] = reflect.ValueOf(gconv.Uint64(task.Params[i]))
case reflect.Int64:
args[i] = reflect.ValueOf(gconv.Int64(task.Params[i]))
case reflect.Int:
args[i] = reflect.ValueOf(gconv.Int(task.Params[i]))
case reflect.Int32:
args[i] = reflect.ValueOf(gconv.Int32(task.Params[i]))
case reflect.Int16:
args[i] = reflect.ValueOf(gconv.Int16(task.Params[i]))
case reflect.Int8:
args[i] = reflect.ValueOf(gconv.Int8(task.Params[i]))
case reflect.Uint:
args[i] = reflect.ValueOf(gconv.Uint(task.Params[i]))
case reflect.Uint32:
args[i] = reflect.ValueOf(gconv.Uint32(task.Params[i]))
case reflect.Uint16:
args[i] = reflect.ValueOf(gconv.Uint16(task.Params[i]))
case reflect.Uint8:
args[i] = reflect.ValueOf(gconv.Uint8(task.Params[i]))
case reflect.String:
args[i] = reflect.ValueOf(gconv.String(task.Params[i]))
case reflect.Bool:
args[i] = reflect.ValueOf(gconv.Bool(task.Params[i]))
default:
args[i] = reflect.ValueOf(task.Params[i])
}
}
if len(task.Params) > 0 {
method.Call(args)
} else {
method.Call(nil)
}
} else {
errInfo := fmt.Sprintf("Method not found: %s", task.MethodName)
err = errors.New(errInfo)
}
return
}
// UnmarshalTask 解析TaskJob,使用自定义JSON解析来处理类型问题
func UnmarshalTask(data []byte) (*Task, error) {
var task Task
dec := json.NewDecoder(bytes.NewReader(data))
// 使用json.Decoder逐个解析Token,自定义解析逻辑
for {
t, err := dec.Token()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
// 根据Token类型处理
switch v := t.(type) {
case float64:
// 如果是数字 ,检查是否可以转换为整数
if v == float64(int64(v)) {
// 如果可以转换为整数,则转换为int类型
task.Params = append(task.Params, int64(v))
} else {
// 如果不能转换为整数,则保留为float64类型
task.Params = append(task.Params, v)
}
case string, bool, nil:
// 对于字符串、布尔和空值,直接添加到Params中
task.Params = append(task.Params, v)
// 可以添加更多的类型处理
}
}
return &task, nil
}
// CheckFuncName 检查方法名是否存在
func (tk *Tasks) CheckFuncName(funcName string) (actualfuncName string, exists bool) {
exist := GetInnerTaskName(funcName)
g.Log().Debug(context.Background(), "CheckFuncName", exist)
if exist != "" {
return exist, true
}
return
}
// ParseParameters 解析参数
func (tk *Tasks) ParseParameters(parseData string) (params []interface{}, err error) {
parts := strings.Split(parseData, "|")
params = make([]interface{}, len(parts))
for i, part := range parts {
params[i] = part
}
return
}