// package system // @Link https://github.com/xiujiecn/xiu-admin // @Copyright Copyright (c) 2025 LiXiujie // @Author Lxj // @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE package system import ( "context" "encoding/json" "errors" "fmt" "strings" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gcode" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/dao" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/xgorm/handler" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/do" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/request" "git.5iot.com/iotlcm-public/iotlcm-app/internal/service" "git.5iot.com/iotlcm-public/iotlcm-app/internal/tasks" ) func init() { service.RegisterSysJob(NewSysJob()) } type sSysJob struct{} func NewSysJob() *sSysJob { return &sSysJob{} } func (s *sSysJob) Model(ctx context.Context, option ...*handler.Option) *gdb.Model { if len(option) == 0 { option = append(option, &handler.Option{ FilterTenant: false, FilterAuth: true, }) } return handler.Model(dao.SysJob.Ctx(ctx), option...) } func (c *sSysJob) List(ctx context.Context, query *model.SysJobListParam, pageInfo *request.PageInfo) (Data []*model.SysJobListModel, total int, err error) { m := c.Model(ctx) if query.JobName != "" { m = m.WhereLike(dao.SysJob.Columns().JobName, "%"+strings.Trim(query.JobName, " ")+"%") } if query.JobGroup != "" { m = m.Where(dao.SysJob.Columns().JobGroup, query.JobGroup) } if query.Status != "" { m = m.Where(dao.SysJob.Columns().Status, query.Status) } t, err := m.Count() if err != nil { return nil, 0, err } Data = make([]*model.SysJobListModel, 0) if err := m.Page(pageInfo.Page, pageInfo.PageSize).Order(dao.SysJob.Columns().JobId, "DESC").Scan(&Data); err != nil { return nil, 0, err } return Data, t, nil } func (c *sSysJob) View(ctx context.Context, jobId int64) (Data *model.SysJobViewModel, err error) { m := c.Model(ctx) Data = &model.SysJobViewModel{} if err := m.Where(dao.SysJob.Columns().JobId, jobId).Scan(&Data); err != nil { return nil, err } return Data, nil } func (c *sSysJob) Add(ctx context.Context, jobAdd *model.SysJobAddModel) (LastInsertId int64, err error) { claims := contexts.GetUser(ctx) if claims == nil { return 0, gerror.NewCode(gcode.CodeMissingParameter, "用户信息不存在") } //获取task目录下是否绑定对应的方法 _, exist := tasks.TasksInstance(ctx).CheckFuncName(jobAdd.InvokeTarget) if !exist { errInfo := fmt.Sprintf("没有绑定对应的方法:%s", jobAdd.InvokeTarget) return 0, gerror.New(errInfo) } jobAdd.CreatedDept = claims.BaseClaims.DeptId jobAdd.CreatedAt = gtime.Now() jobAdd.CreatedBy = claims.BaseClaims.ID jobAdd.UpdatedAt = gtime.Now() jobAdd.UpdatedBy = claims.BaseClaims.ID data := do.SysJob{} gconv.Struct(jobAdd, &data) result, err := c.Model(ctx).Data(data).Insert() if err != nil { return 0, err } lastInserId, _ := result.LastInsertId() if lastInserId > 0 && jobAdd.Status == consts.SysJobStatusNormal { job := &model.SysJobViewModel{} if err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, lastInserId).Scan(&job); err != nil { return lastInserId, err } //任务启动 err = c.taskRun(ctx, &job.SysJob) if err != nil { return lastInserId, err } } return result.LastInsertId() } func (c *sSysJob) Update(ctx context.Context, jobUpdate *model.SysJobUpdateModel) (RowsAffected int64, err error) { claims := contexts.GetUser(ctx) if claims == nil { return 0, gerror.NewCode(gcode.CodeMissingParameter, "用户信息不存在") } //获取task目录下是否绑定对应的方法 _, exist := tasks.TasksInstance(ctx).CheckFuncName(jobUpdate.InvokeTarget) if !exist { errInfo := fmt.Sprintf("没有绑定对应的方法:%s", jobUpdate.InvokeTarget) return 0, gerror.New(errInfo) } jobUpdate.UpdatedAt = gtime.Now() jobUpdate.UpdatedBy = claims.BaseClaims.ID data := do.SysJob{} gconv.Struct(jobUpdate, &data) result, err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobUpdate.JobId).Data(data).Update() if err != nil { return 0, err } row, err := result.RowsAffected() if row > 0 && jobUpdate.Status == consts.SysJobStatusNormal { job := &model.SysJobViewModel{} if err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobUpdate.JobId).Scan(&job); err != nil { return 0, err } //任务启动 err = c.taskRestart(ctx, &job.SysJob) if err != nil { return row, err } } return row, err } func (c *sSysJob) UpdateStatus(ctx context.Context, jobUpdate *model.SysJobUpdateStatusModel) (RowsAffected int64, err error) { claims := contexts.GetUser(ctx) if claims == nil { return 0, gerror.NewCode(gcode.CodeMissingParameter, "用户信息不存在") } exists := &model.SysJobViewModel{} if err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobUpdate.JobId).Scan(&exists); err != nil { return 0, err } if jobUpdate.Status == consts.SysJobStatusNormal { //任务启动 err = c.taskRun(ctx, &exists.SysJob) if err != nil { return 0, err } } if jobUpdate.Status == consts.SysJobStatusDisable { //任务停止 if err = c.taskStop(ctx, &exists.SysJob); err != nil { return 0, err } } jobUpdate.UpdatedAt = gtime.Now() jobUpdate.UpdatedBy = claims.BaseClaims.ID data := do.SysJob{} gconv.Struct(jobUpdate, &data) result, err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobUpdate.JobId).Data(data).Update() if err != nil { return 0, err } row, err := result.RowsAffected() return row, err } // 任务重启 func (c *sSysJob) taskRestart(ctx context.Context, job *entity.SysJob) (err error) { //任务停止 if err = c.taskStop(ctx, job); err != nil { return } //任务启动 err = c.taskRun(ctx, job) if err != nil { return } return } // 任务启动 func (c *sSysJob) taskRun(ctx context.Context, job *entity.SysJob) error { //获取task目录下是否绑定对应的方法 actualName, exist := tasks.TasksInstance(ctx).CheckFuncName(job.InvokeTarget) if !exist { errInfo := fmt.Sprintf("没有绑定对应的方法:%s", job.InvokeTarget) return gerror.New(errInfo) } //传参解析 paramArr, err := tasks.TasksInstance(ctx).ParseParameters(job.JobParams) if err != nil { return err } taskData := tasks.Task{ ID: fmt.Sprintf("%s-job-%d", job.InvokeTarget, job.JobId), TaskType: "Type-" + gconv.String(job.MisfirePolicy), MethodName: actualName, Params: paramArr, Explain: job.JobName, } runPayload, _ := json.Marshal(taskData) if job.MisfirePolicy == consts.SysJobMisfirePolicyMulty { // entryID, err := tasks.TasksInstance().Cron( // job.Concurrent, // worker.WithTaskCtx(context.Background()), // worker.WithTaskUid(taskData.ID), // 任务ID // worker.WithTaskGroup(taskData.MethodName), // 任务组 // worker.WithTaskExpr(job.CronExpression), // worker.WithTaskTimeout(10), // worker.WithTaskReplace(true), // worker.WithTaskPayload(runPayload), // ) entryID, err := tasks.TasksInstance(ctx).Cron(ctx, tasks.TaskOptions{ Uid: taskData.ID, Payload: runPayload, Expr: &job.CronExpression, Concurrent: job.Concurrent, }) if err != nil { g.Log().Debug(ctx, taskData.MethodName, taskData.Explain, "启动任务失败") return err } g.Log().Debug(ctx, taskData.MethodName, taskData.Explain, "启动任务成功", "entryID", entryID, "CronExpression", job.CronExpression, "JobId", job.JobId, "JobName", job.JobName) } else { now := true err := tasks.TasksInstance(ctx).Once(ctx, tasks.TaskOptions{ Uid: taskData.ID, Now: &now, Payload: runPayload, }) if err != nil { g.Log().Debug(ctx, taskData.MethodName, taskData.Explain, "启动任务失败") return err } } return nil } func (c *sSysJob) taskStop(ctx context.Context, job *entity.SysJob) error { //获取task目录下是否绑定对应的方法 _, exist := tasks.TasksInstance(ctx).CheckFuncName(job.InvokeTarget) if !exist { errInfo := fmt.Sprintf("没有绑定对应的方法:%s", job.InvokeTarget) return gerror.New(errInfo) } taskJobId := fmt.Sprintf("%s-job-%d", job.InvokeTarget, job.JobId) err := tasks.TasksInstance(ctx).Remove(taskJobId) if err != nil { g.Log().Debug(ctx, job.JobName, "启动停止失败") return err } return nil } func (c *sSysJob) Delete(ctx context.Context, jobDelete *model.SysJobDeleteModel) (RowsAffected int64, err error) { claims, err := service.SysAuth().GetCurrentUser(ctx) if err != nil { return 0, err } exists := &[]*model.SysJobViewModel{} c.Model(ctx).WhereIn(dao.SysJob.Columns().JobId, jobDelete.JobIds).Where(dao.SysJob.Columns().Status, consts.SysJobStatusNormal).Scan(exists) if len(*exists) > 0 { return 0, gerror.New("存在正常运行的任务,请先停止后再删除") } jobDelete.DeletedAt = gtime.Now() jobDelete.DeletedBy = claims.BaseClaims.ID data := do.SysJob{ DeletedAt: jobDelete.DeletedAt, DeletedBy: jobDelete.DeletedBy, } result, err := c.Model(ctx).WhereIn(dao.SysJob.Columns().JobId, jobDelete.JobIds).Data(data).Update() if err != nil { return 0, err } return result.RowsAffected() } func (c *sSysJob) Exec(ctx context.Context, jobId int64) error { exists := &model.SysJobViewModel{} err := c.Model(ctx).Where(dao.SysJob.Columns().JobId, jobId).Scan(&exists) if err != nil { return err } actualName, exist := tasks.TasksInstance(ctx).CheckFuncName(exists.InvokeTarget) if !exist { errInfo := fmt.Sprintf("没有绑定对应的方法:%s", exists.InvokeTarget) return gerror.New(errInfo) } //传参解析 paramArr, err := tasks.TasksInstance(ctx).ParseParameters(exists.JobParams) if err != nil { g.Log().Error(ctx, "sSysJob.JobRun worker.ParseParameters", err) return err } taskData := tasks.Task{ ID: fmt.Sprintf("%s-job-%d", exists.InvokeTarget, exists.JobId), TaskType: "Type-" + gconv.String(exists.MisfirePolicy), MethodName: actualName, Params: paramArr, Explain: exists.JobName, } runPayload, _ := json.Marshal(taskData) now := true err = tasks.TasksInstance(ctx).Once(ctx, tasks.TaskOptions{ Uid: taskData.ID, Payload: runPayload, Now: &now, }) // worker.WithTaskCtx(context.Background()), // worker.WithTaskUid(taskData.ID), // 任务ID // worker.WithTaskGroup(taskData.MethodName), // 任务组 // worker.WithTaskTimeout(10), // worker.WithTaskNow(true), // worker.WithTaskReplace(true), // worker.WithTaskPayload(runPayload), // ) if err != nil { errInfo := fmt.Sprintf(exists.InvokeTarget, taskData.Explain, "启动任务失败") return errors.New(errInfo) } return nil } // 查询所有的状态正常的任务列表,并进行初始注册 func (c *sSysJob) InitRegister() error { g.Log().Debug(context.Background(), "初始化任务注册") jobs := &[]*model.SysJobViewModel{} err := c.Model(context.Background()).Where(dao.SysJob.Columns().Status, consts.SysJobStatusNormal).Scan(jobs) if err != nil { return err } for _, job := range *jobs { err = c.taskRun(context.Background(), &job.SysJob) if err != nil { g.Log().Error(context.Background(), "初始化注册任务 %s 失败", job.JobName, err) continue } g.Log().Debug(context.Background(), "初始化注册任务 %s 成功", job.JobName) } g.Log().Debug(context.Background(), "初始化任务注册完成") return nil }