Explorar o código

Update 优化定时任务模块

YueYunyun %!s(int64=2) %!d(string=hai) anos
pai
achega
b149059d06

+ 2 - 2
SERVER/VberAdmin/app/schedule/apis/job.go

@@ -1,7 +1,7 @@
 package apis
 
 import (
-	"VberAdmin/app/schedule/jobs"
+	cJobs "VberAdmin/common/jobs"
 	"VberAdmin/common/permission"
 	"VberAdmin/core/sdk"
 	"fmt"
@@ -214,7 +214,7 @@ func (e SysJobApi) GetJobKeys(c *gin.Context) {
 		e.Error(500, err, err.Error())
 		return
 	}
-	result := jobs.GetJobKeys()
+	result := cJobs.GetJobKeys()
 	e.OK(result, "获取成功")
 }
 

+ 14 - 7
SERVER/VberAdmin/app/schedule/jobs/clean-log.go

@@ -2,6 +2,8 @@ package jobs
 
 import (
 	"VberAdmin/common"
+	"VberAdmin/common/jobs"
+	"strconv"
 )
 
 // CleanLog
@@ -9,19 +11,24 @@ import (
 type CleanLog struct {
 }
 
-func (t *CleanLog) Exec(arg interface{}) error {
-	jobLogger.Infof("【CleanLog】 EXEC START")
-	err := common.CleanSysLog()
+func (t *CleanLog) Exec(job *jobs.JobCore) error {
+	if err := common.CleanSysLog(); err != nil {
+		job.Logger.Errorf("[CleanSysLog] EXEC ERROR: %s", err.Error())
+		return err
+	}
+	days, err := strconv.Atoi(job.Args)
 	if err != nil {
-		jobLogger.Errorf("[CleanSysLog] EXEC ERROR: %s", err.Error())
+		job.Logger.Errorf("CleanJobLog EXEC ERROR:%s", err.Error())
 		return err
 	}
-	err = CleanJobLog(10)
+	if days >= 0 {
+		err = jobs.CleanJobLog(days)
+	}
 	if err != nil {
-		jobLogger.Errorf("[CleanSysLog] EXEC ERROR: %s", err.Error())
+		job.Logger.Errorf("[CleanJobLog] EXEC ERROR: %s", err.Error())
 		return err
 	}
-	jobLogger.Infof("【CleanLog】 EXEC END\r\n")
+
 	return nil
 }
 

+ 6 - 84
SERVER/VberAdmin/app/schedule/jobs/init.go

@@ -1,93 +1,15 @@
 package jobs
 
-import (
-	"VberAdmin/app/schedule/models"
-	"VberAdmin/core/sdk"
-	"VberAdmin/core/sdk/pkg/cronjob"
+import "VberAdmin/common/jobs"
 
-	"gorm.io/gorm"
-)
-
-var (
-	// jobList 定义的job
-	jobList map[string]JobExec
-)
-
-// InitJob 初始化job
-func InitJob() {
-	initJobList()
-	Setup(sdk.Runtime.GetDb())
-}
-
-// 需要将定义的struct 添加到字典中;
-// 字典 key 可以配置到 自动任务 调用目标 中;
-func initJobList() {
-	jobList = map[string]JobExec{
+func init() {
+	mp := map[string]jobs.JobExec{
 		"CleanLog": &CleanLog{},
 		// ...
 	}
+	jobs.AddJobs(mp)
 }
 
-// Setup 初始化
-func Setup(dbs map[string]*gorm.DB) {
-	jobLogger.Info("STARTING...")
-	for k, db := range dbs {
-		sdk.Runtime.SetCrontab(k, cronjob.NewWithSecondsLogger(jobLogger))
-		setup(k, db)
-	}
-}
-
-func setup(key string, db *gorm.DB) {
-	crontab := sdk.Runtime.GetCrontabKey(key)
-	sysJob := models.SysJob{}
-	jobList := make([]models.SysJob, 0)
-	err := sysJob.GetList(db, &jobList)
-	if err != nil {
-		jobLogger.Errorf("INIT ERROR: %s", err.Error())
-	}
-	if len(jobList) == 0 {
-		jobLogger.Infof("TOTAL:0")
-	}
-
-	_, err = sysJob.RemoveAllEntryID(db)
-	if err != nil {
-		jobLogger.Errorf("REMOVE ALL ENTRY ERROR: %s", err.Error())
-	}
-
-	for i := 0; i < len(jobList); i++ {
-		if jobList[i].JobType == 1 {
-			j := &HttpJob{}
-			j.InvokeTarget = jobList[i].InvokeTarget
-			j.CronExpression = jobList[i].CronExpression
-			j.JobId = jobList[i].JobId
-			j.Name = jobList[i].JobName
-
-			sysJob.EntryId, err = AddJob(crontab, j)
-		} else if jobList[i].JobType == 2 {
-			j := &ExecJob{}
-			j.InvokeTarget = jobList[i].InvokeTarget
-			j.CronExpression = jobList[i].CronExpression
-			j.JobId = jobList[i].JobId
-			j.Name = jobList[i].JobName
-			j.Args = jobList[i].Args
-			sysJob.EntryId, err = AddJob(crontab, j)
-		}
-		err = sysJob.Update(db, jobList[i].JobId)
-	}
-
-	// 其中任务
-	crontab.Start()
-	jobLogger.Infof("START SUCCESS.")
-	// 关闭任务
-	defer crontab.Stop()
-	select {}
-}
-
-// GetJobKeys 获取job key列表
-func GetJobKeys() map[string]string {
-	var mp = make(map[string]string)
-	for k, v := range jobList {
-		mp[k] = v.GetName()
-	}
-	return mp
+func Start() {
+	jobs.InitJob()
 }

+ 24 - 17
SERVER/VberAdmin/app/schedule/models/job.go

@@ -2,8 +2,6 @@ package models
 
 import (
 	"VberAdmin/common/models"
-
-	"gorm.io/gorm"
 )
 
 // SysJob 定时任务实体
@@ -15,10 +13,15 @@ type SysJob struct {
 	CronExpression string `json:"cronExpression" gorm:"type:varchar(255);comment:Cron表达式"`
 	InvokeTarget   string `json:"invokeTarget" gorm:"type:varchar(255);comment:调用目标"`
 	Args           string `json:"args" gorm:"type:varchar(255);comment:目标参数"`
+	ExecCount      int    `json:"execCount" gorm:"type:bigint(20);comment:执行次数"`
+	FailCount      int    `json:"failCount" gorm:"type:bigint(20);comment:失败次数"`
+	LastExecTime   int64  `json:"lastExecTime" gorm:"type:bigint(20);comment:上次执行时间"`
+	NextExecTime   int64  `json:"nextExecTime" gorm:"type:bigint(20);comment:下次执行时间"`
 	MisfirePolicy  int    `json:"misfirePolicy" gorm:"type:bigint(20);comment:执行策略"`
 	Concurrent     int    `json:"concurrent" gorm:"type:tinyint(4);comment:是否并发"`
 	Status         int    `json:"status" gorm:"type:tinyint(4);comment:状态"`
 	EntryId        int    `json:"entryId" gorm:"type:smallint(6);comment:EntryId"`
+
 	models.ControlBy
 	models.ModelTime
 }
@@ -36,18 +39,22 @@ func (e *SysJob) GetId() interface{} {
 	return e.JobId
 }
 
-func (e *SysJob) GetList(tx *gorm.DB, list interface{}) (err error) {
-	return tx.Table(e.TableName()).Where("status = ?", 2).Find(list).Error
-}
-
-// Update 更新SysJob
-func (e *SysJob) Update(tx *gorm.DB, id interface{}) (err error) {
-	return tx.Table(e.TableName()).Where(id).Updates(&e).Error
-}
-
-func (e *SysJob) RemoveAllEntryID(tx *gorm.DB) (update SysJob, err error) {
-	if err = tx.Table(e.TableName()).Where("entry_id > ?", 0).Update("entry_id", 0).Error; err != nil {
-		return
-	}
-	return
-}
+//func (e *SysJob) Get(tx *gorm.DB, job *SysJob) (err error) {
+//	return tx.Model(e).First(job, e.JobId).Error
+//}
+//
+//func (e *SysJob) GetList(tx *gorm.DB, list interface{}) (err error) {
+//	return tx.Table(e.TableName()).Where("status = ?", 2).Find(list).Error
+//}
+//
+//// Update 更新SysJob
+//func (e *SysJob) Update(tx *gorm.DB, id interface{}) (err error) {
+//	return tx.Table(e.TableName()).Where(id).Updates(&e).Error
+//}
+//
+//func (e *SysJob) RemoveAllEntryID(tx *gorm.DB) (update SysJob, err error) {
+//	if err = tx.Table(e.TableName()).Where("entry_id > ?", 0).Update("entry_id", 0).Error; err != nil {
+//		return
+//	}
+//	return
+//}

+ 8 - 4
SERVER/VberAdmin/app/schedule/service/dto/job.go

@@ -19,10 +19,14 @@ type SysJobGetPageReq struct {
 }
 
 type SysJobOrder struct {
-	JobGroup  string `form:"jobGroupOrder"  search:"type:order;column:job_group;table:sys_job"`
-	JobType   string `form:"jobTypeOrder"  search:"type:order;column:job_type;table:sys_job"`
-	CreatedAt string `form:"createdAtOrder"  search:"type:order;column:created_at;table:sys_job"`
-	Status    string `form:"statusOrder"  search:"type:order;column:status;table:sys_job"`
+	JobGroup     string `form:"jobGroupOrder"  search:"type:order;column:job_group;table:sys_job"`
+	JobType      string `form:"jobTypeOrder"  search:"type:order;column:job_type;table:sys_job"`
+	ExecCount    string `form:"execCountOrder"  search:"type:order;column:exec_count;table:sys_job"`
+	FailCount    string `form:"failCountOrder"  search:"type:order;column:fail_count;table:sys_job"`
+	LastExecTime string `form:"lastExecTimeOrder"  search:"type:order;column:last_exec_time;table:sys_job"`
+	NextExecTime string `form:"nextExecTimeOrder"  search:"type:order;column:next_exec_time;table:sys_job"`
+	CreatedAt    string `form:"createdAtOrder"  search:"type:order;column:created_at;table:sys_job"`
+	Status       string `form:"statusOrder"  search:"type:order;column:status;table:sys_job"`
 }
 
 func (m *SysJobGetPageReq) GetNeedSearch() interface{} {

+ 10 - 6
SERVER/VberAdmin/app/schedule/service/job.go

@@ -4,10 +4,10 @@ import (
 	"errors"
 	"time"
 
-	"VberAdmin/app/schedule/jobs"
 	"VberAdmin/app/schedule/models"
 	"VberAdmin/app/schedule/service/dto"
 	cDto "VberAdmin/common/dto"
+	cJobs "VberAdmin/common/jobs"
 	"VberAdmin/common/permission"
 	"VberAdmin/core/sdk/service"
 
@@ -129,23 +129,27 @@ func (e *SysJobService) StartJob(c *dto.SysJobGetReq) error {
 	}
 
 	if data.JobType == 1 {
-		var j = &jobs.HttpJob{}
+		var j = &cJobs.HttpJob{}
 		j.InvokeTarget = data.InvokeTarget
 		j.CronExpression = data.CronExpression
 		j.JobId = data.JobId
 		j.Name = data.JobName
-		data.EntryId, err = jobs.AddJob(e.Cron, j)
+		j.Logger = cJobs.GetJobLogger()
+		j.DbKey = "*"
+		data.EntryId, err = cJobs.AddJob(e.Cron, j)
 		if err != nil {
 			e.Log.Errorf("jobs AddJob[HttpJob] error: %s", err)
 		}
 	} else {
-		var j = &jobs.ExecJob{}
+		var j = &cJobs.ExecJob{}
 		j.InvokeTarget = data.InvokeTarget
 		j.CronExpression = data.CronExpression
 		j.JobId = data.JobId
 		j.Name = data.JobName
 		j.Args = data.Args
-		data.EntryId, err = jobs.AddJob(e.Cron, j)
+		j.Logger = cJobs.GetJobLogger()
+		j.DbKey = "*"
+		data.EntryId, err = cJobs.AddJob(e.Cron, j)
 		if err != nil {
 			e.Log.Errorf("jobs AddJob[ExecJob] error: %s", err)
 		}
@@ -174,7 +178,7 @@ func (e *SysJobService) StopJob(c *dto.SysJobGetReq) error {
 		err = errors.New("当前Job是已停用。")
 		return err
 	}
-	cn := jobs.Remove(e.Cron, data.EntryId)
+	cn := cJobs.Remove(e.Cron, data.EntryId)
 
 	select {
 	case res := <-cn:

+ 42 - 0
SERVER/VberAdmin/common/jobs/call.go

@@ -0,0 +1,42 @@
+package jobs
+
+import (
+	"VberAdmin/common/jobs/models"
+	"VberAdmin/core/sdk"
+	"VberAdmin/core/tools/utils"
+	"fmt"
+
+	"github.com/robfig/cron/v3"
+)
+
+func CallExec(e JobExec, jobCore *JobCore) error {
+	job := &models.SysJob{
+		JobId: jobCore.JobId,
+	}
+	db := sdk.Runtime.GetDbByKey(jobCore.DbKey)
+	if db == nil {
+		err := fmt.Errorf("【%s】 EXEC DB ERROR:%s %s", jobCore.Name, jobCore.DbKey, "db is nil")
+		return err
+	}
+	err := job.Get(db, job)
+	if err != nil {
+		err = fmt.Errorf("【%s】 EXEC DB ERROR: %s", jobCore.Name, err.Error())
+		return err
+	}
+	jobLogger.Infof("【%s】 EXEC START", jobCore.Name)
+	job.ExecCount++
+	job.LastExecTime = utils.NowLong()
+	err = e.Exec(jobCore)
+	if err != nil {
+		job.FailCount++
+		jobLogger.Errorf("【%s】 EXEC ERROR: %s", jobCore.Name, err.Error())
+	}
+	crontab := sdk.Runtime.GetCrontabKey(jobCore.DbKey)
+	entry := crontab.Entry(cron.EntryID(job.EntryId))
+	job.NextExecTime = utils.Time2Long(entry.Next)
+	jobLogger.Infof("【%s】 EXEC END\r\n", jobCore.Name)
+	if err2 := job.Update(db, job.JobId); err2 != nil {
+		err = fmt.Errorf("【%s】 EXEC DB ERROR: %s", jobCore.Name, err2.Error())
+	}
+	return err
+}

+ 106 - 0
SERVER/VberAdmin/common/jobs/init.go

@@ -0,0 +1,106 @@
+package jobs
+
+import (
+	"VberAdmin/common/jobs/models"
+	"VberAdmin/core/sdk"
+	"VberAdmin/core/sdk/pkg/cronjob"
+
+	"gorm.io/gorm"
+)
+
+func init() {
+	jobList = make(map[string]JobExec)
+}
+
+var (
+	// jobList 定义的job
+	jobList map[string]JobExec
+)
+
+// InitJob 初始化job
+func InitJob() {
+	initJobList()
+	Setup(sdk.Runtime.GetDb())
+}
+
+// 需要将定义的struct 添加到字典中;
+// 字典 key 可以配置到 自动任务 调用目标 中;
+func initJobList() {
+
+}
+
+func AddJobs(mp map[string]JobExec) {
+	for k, v := range mp {
+		jobList[k] = v
+	}
+}
+
+// Setup 初始化
+func Setup(dbs map[string]*gorm.DB) {
+	jobLogger.Info("STARTING...")
+	for k, db := range dbs {
+		sdk.Runtime.SetCrontab(k, cronjob.NewWithSecondsLogger(jobLogger))
+		setup(k, db)
+	}
+}
+
+func setup(key string, db *gorm.DB) {
+	crontab := sdk.Runtime.GetCrontabKey(key)
+	sysJob := models.SysJob{}
+	jobList := make([]models.SysJob, 0)
+	err := sysJob.GetList(db, &jobList)
+	if err != nil {
+		jobLogger.Errorf("INIT [%s] ERROR: %s", key, err.Error())
+	}
+	if len(jobList) == 0 {
+		jobLogger.Infof("TOTAL:0")
+	}
+	_, err = sysJob.RemoveAllEntryID(db)
+	if err != nil {
+		jobLogger.Errorf("REMOVE ALL ENTRY [%s] ERROR: %s", key, err.Error())
+	}
+
+	for i := 0; i < len(jobList); i++ {
+		if jobList[i].JobType == 1 {
+			j := &HttpJob{}
+			j.InvokeTarget = jobList[i].InvokeTarget
+			j.CronExpression = jobList[i].CronExpression
+			j.JobId = jobList[i].JobId
+			j.Name = jobList[i].JobName
+			j.DbKey = key
+			j.Logger = jobLogger
+			sysJob.EntryId, err = AddJob(crontab, j)
+		} else if jobList[i].JobType == 2 {
+			j := &ExecJob{}
+			j.InvokeTarget = jobList[i].InvokeTarget
+			j.CronExpression = jobList[i].CronExpression
+			j.JobId = jobList[i].JobId
+			j.Name = jobList[i].JobName
+			j.Args = jobList[i].Args
+			j.DbKey = key
+			j.Logger = jobLogger
+			sysJob.EntryId, err = AddJob(crontab, j)
+		}
+		jobLogger.Infof("ADD JOB: %s", jobList[i].JobName)
+		err = sysJob.Update(db, jobList[i].JobId)
+	}
+
+	// 启动任务
+	crontab.Start()
+	jobLogger.Infof("START [%s] SUCCESS", key)
+	// 关闭任务
+	defer func() {
+		jobLogger.Infof("STOP [%s]\r\n\r\n", key)
+		crontab.Stop()
+	}()
+	select {}
+}
+
+// GetJobKeys 获取job key列表
+func GetJobKeys() map[string]string {
+	var mp = make(map[string]string)
+	for k, v := range jobList {
+		mp[k] = v.GetName()
+	}
+	return mp
+}

+ 21 - 15
SERVER/VberAdmin/app/schedule/jobs/job_http.go → SERVER/VberAdmin/common/jobs/job_http.go

@@ -2,7 +2,6 @@ package jobs
 
 import (
 	"VberAdmin/core/sdk/pkg"
-	"fmt"
 	"time"
 
 	"github.com/robfig/cron/v3"
@@ -15,10 +14,22 @@ type HttpJob struct {
 
 // Run http 任务接口
 func (h *HttpJob) Run() {
-
 	startTime := time.Now()
+	err := CallExec(h, &h.JobCore)
+	// 结束时间
+	endTime := time.Now()
+	// 执行时间
+	latencyTime := endTime.Sub(startTime)
+	if err != nil {
+		jobLogger.Errorf("%s EXEC ERROR , SPEND :%v, ERROR: %s", h.Name, latencyTime, err.Error())
+		return
+	}
+	jobLogger.Infof("%s EXEC SUCCESS , SPEND :%v", h.Name, latencyTime)
+	return
+}
+
+func (h *HttpJob) Exec(jobCore *JobCore) (err error) {
 	var count = 0
-	var err error
 	var str string
 	/* 循环 */
 LOOP:
@@ -27,28 +38,23 @@ LOOP:
 		str, err = pkg.Get(h.InvokeTarget)
 		if err != nil {
 			// 如果失败暂停一段时间重试
-			jobLogger.Errorf("[%s] EXEC FAILED! %s", h.Name, err.Error())
-			jobLogger.Infof("[%s]  Retry after the task fails %d seconds! %s ", h.Name, (count+1)*5, str)
-			time.Sleep(time.Duration(count+1) * 5 * time.Second)
+			jobLogger.Errorf("【%s】 EXEC FAILED! %s", h.Name, err.Error())
+			jobLogger.Infof("【%s】  RETRY AFTER  %d SECONDS [%s]", h.Name, (count+1)*15, str)
+			time.Sleep(time.Duration(count+1) * 15 * time.Second)
 			count = count + 1
 			goto LOOP
 		}
 	}
-	// 结束时间
-	endTime := time.Now()
-
-	// 执行时间
-	latencyTime := endTime.Sub(startTime)
-	//TODO: 待完善部分
-
-	jobLogger.Infof("%s EXEC SUCCESS , SPEND :%v", h.Name, latencyTime)
 	return
 }
 
+func (h *HttpJob) GetName() string {
+	return ""
+}
 func (h *HttpJob) addJob(c *cron.Cron) (int, error) {
 	id, err := c.AddJob(h.CronExpression, h)
 	if err != nil {
-		fmt.Println(time.Now().Format(timeFormat), " [ERROR] JobCore AddJob error", err)
+		jobLogger.Errorf("ADDJOB ERROR: %s", err.Error())
 		return 0, err
 	}
 	EntryId := int(id)

+ 2 - 7
SERVER/VberAdmin/app/schedule/jobs/job_sys.go → SERVER/VberAdmin/common/jobs/job_sys.go

@@ -1,7 +1,6 @@
 package jobs
 
 import (
-	"fmt"
 	"time"
 
 	"github.com/robfig/cron/v3"
@@ -18,19 +17,15 @@ func (e *ExecJob) Run() {
 		jobLogger.Warnf("[Job] ExecJob Run jobs nil [%s]", e.Name)
 		return
 	}
-	err := CallExec(obj.(JobExec), e.Args)
+	err := CallExec(obj.(JobExec), &e.JobCore)
 	if err != nil {
-		// 如果失败暂停一段时间重试
-		fmt.Println(time.Now().Format(timeFormat), " [ERROR] mission failed! ", err)
+		jobLogger.Errorf("[%s] EXEC FAILED! %s", e.Name, err.Error())
 	}
 	// 结束时间
 	endTime := time.Now()
 
 	// 执行时间
 	latencyTime := endTime.Sub(startTime)
-	//TODO: 待完善部分
-	//str := time.Now().Format(timeFormat) + " [INFO] JobCore " + string(e.EntryId) + "exec success , spend :" + latencyTime.String()
-	//ws.SendAll(str)
 	jobLogger.Infof("[%s] EXEC SUCCESS , SPEND :%v", e.Name, latencyTime)
 	return
 }

+ 0 - 0
SERVER/VberAdmin/app/schedule/jobs/jobbase.go → SERVER/VberAdmin/common/jobs/jobbase.go


+ 4 - 4
SERVER/VberAdmin/app/schedule/jobs/logger.go → SERVER/VberAdmin/common/jobs/logger.go

@@ -70,7 +70,7 @@ func CleanJobLog(days int) error {
 }
 
 func (j *JobLogger) Info(msg string, args ...interface{}) {
-	msg = "[CRON]" + msg
+	msg = "[CRON] " + msg
 	if len(args) > 0 {
 		for _, v := range args {
 			msg += fmt.Sprintf(" %v", v)
@@ -79,12 +79,12 @@ func (j *JobLogger) Info(msg string, args ...interface{}) {
 	j.Helper.Info(msg)
 }
 func (j *JobLogger) Infof(msg string, args ...interface{}) {
-	msg = "[JobCore]" + msg
+	msg = "[JobCore] " + msg
 	j.Helper.Infof(msg, args...)
 }
 
 func (j *JobLogger) Error(err error, msg string, args ...interface{}) {
-	msg = "[CRON]" + msg
+	msg = "[CRON] " + msg
 	if len(args) > 0 {
 		for _, v := range args {
 			msg += fmt.Sprintf("%v ", v)
@@ -94,6 +94,6 @@ func (j *JobLogger) Error(err error, msg string, args ...interface{}) {
 }
 
 func (j *JobLogger) Errorf(msg string, args ...interface{}) {
-	msg = "[JobCore]" + msg
+	msg = "[JobCore] " + msg
 	j.Helper.Errorf(msg, args...)
 }

+ 62 - 0
SERVER/VberAdmin/common/jobs/models/job.go

@@ -0,0 +1,62 @@
+package models
+
+import (
+	"VberAdmin/common/models"
+
+	"gorm.io/gorm"
+)
+
+// SysJob 定时任务实体
+type SysJob struct {
+	JobId          int    `json:"jobId" gorm:"type:bigint(20);primaryKey;autoIncrement;comment:编码"`
+	JobName        string `json:"jobName" gorm:"type:varchar(255);comment:名称"`
+	JobGroup       string `json:"jobGroup" gorm:"type:varchar(255);comment:任务分组"`
+	JobType        int    `json:"jobType" gorm:"type:tinyint(4);comment:调用类型"`
+	CronExpression string `json:"cronExpression" gorm:"type:varchar(255);comment:Cron表达式"`
+	InvokeTarget   string `json:"invokeTarget" gorm:"type:varchar(255);comment:调用目标"`
+	Args           string `json:"args" gorm:"type:varchar(255);comment:目标参数"`
+	ExecCount      int    `json:"execCount" gorm:"type:bigint(20);comment:执行次数"`
+	FailCount      int    `json:"failCount" gorm:"type:bigint(20);comment:失败次数"`
+	LastExecTime   int64  `json:"lastExecTime" gorm:"type:bigint(20);comment:上次执行时间"`
+	NextExecTime   int64  `json:"nextExecTime" gorm:"type:bigint(20);comment:下次执行时间"`
+	MisfirePolicy  int    `json:"misfirePolicy" gorm:"type:bigint(20);comment:执行策略"`
+	Concurrent     int    `json:"concurrent" gorm:"type:tinyint(4);comment:是否并发"`
+	Status         int    `json:"status" gorm:"type:tinyint(4);comment:状态"`
+	EntryId        int    `json:"entryId" gorm:"type:smallint(6);comment:EntryId"`
+
+	models.ControlBy
+	models.ModelTime
+}
+
+func (*SysJob) TableName() string {
+	return "sys_job"
+}
+
+func (e *SysJob) Generate() models.ActiveRecord {
+	o := *e
+	return &o
+}
+
+func (e *SysJob) GetId() interface{} {
+	return e.JobId
+}
+
+func (e *SysJob) Get(tx *gorm.DB, job *SysJob) (err error) {
+	return tx.Model(e).First(job, e.JobId).Error
+}
+
+func (e *SysJob) GetList(tx *gorm.DB, list interface{}) (err error) {
+	return tx.Table(e.TableName()).Where("status = ?", 2).Find(list).Error
+}
+
+// Update 更新SysJob
+func (e *SysJob) Update(tx *gorm.DB, id interface{}) (err error) {
+	return tx.Table(e.TableName()).Where(id).Updates(&e).Error
+}
+
+func (e *SysJob) RemoveAllEntryID(tx *gorm.DB) (update SysJob, err error) {
+	if err = tx.Table(e.TableName()).Where("entry_id > ?", 0).Update("entry_id", 0).Error; err != nil {
+		return
+	}
+	return
+}

+ 6 - 6
SERVER/VberAdmin/app/schedule/jobs/type.go → SERVER/VberAdmin/common/jobs/type.go

@@ -1,6 +1,8 @@
 package jobs
 
-import "github.com/robfig/cron/v3"
+import (
+	"github.com/robfig/cron/v3"
+)
 
 type JobCore struct {
 	InvokeTarget   string
@@ -9,6 +11,8 @@ type JobCore struct {
 	EntryId        int
 	CronExpression string
 	Args           string
+	DbKey          string
+	Logger         *JobLogger
 }
 
 type Job interface {
@@ -17,10 +21,6 @@ type Job interface {
 }
 
 type JobExec interface {
-	Exec(arg interface{}) error
+	Exec(job *JobCore) error
 	GetName() string
 }
-
-func CallExec(e JobExec, arg interface{}) error {
-	return e.Exec(arg)
-}

+ 2 - 2
SERVER/VberAdmin/config/sql/db.sql

@@ -374,8 +374,8 @@ INSERT INTO sys_dict_data VALUES (42, 0, '清空数据', '12', 'sys_oper_type',
 INSERT INTO sys_dict_data VALUES (43, 0, '刷新数据', '13', 'sys_oper_type', '', 'primary', '', '2', '', '获取验证码', 1, 1, '2024-03-14 00:00:00.000', '2024-03-14 00:00:00.000', NULL);
 
 
-INSERT INTO sys_job VALUES (1, '清理日志', 'SYSTEM', 2, '0/5 * * * * ', 'CleanLog', '', 1, 1, 2, 0, 1, 1, '2024-03-14 00:00:00.000', '2024-03-14 00:00:00.000', NULL);
-INSERT INTO sys_job VALUES (2, '接口测试', 'SYSTEM', 1, '0/5 * * * * ', 'http://localhost:6071', '', 1, 1, 1, 0, 1, 1, '2024-03-14 00:00:00.000', '2024-03-14 00:00:00.000', NULL);
+INSERT INTO sys_job VALUES (1, '接口测试', 'SYSTEM', 1, '0/5 * * * * ', 'http://localhost:6071', '', 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, '2024-03-14 00:00:00.000', '2024-03-14 00:00:00.000', NULL);
+INSERT INTO sys_job VALUES (2, '清除日志', 'SYSTEM', 2, '30 0 0 * * ?', 'CleanLog', '1', 0, 0, 0, 0, 1, 2, 2, 2, 1, 1, '2024-04-02 17:45:13.384', '2024-04-02 17:47:15.219', NULL);
 
 
 INSERT INTO sys_post VALUES (1, '首席执行官', 'CEO', 0, '2','首席执行官', 1, 1, '2024-03-14 00:00:00.000', '2024-03-14 00:00:00.000', NULL);

+ 49 - 0
SERVER/VberAdmin/core/tools/utils/map.go

@@ -0,0 +1,49 @@
+package utils
+
+import (
+	"fmt"
+	"reflect"
+)
+
+// ToMap 将数据转换为map。假设data是一个结构体。
+func ToMap(data interface{}) (map[string]interface{}, error) {
+	// 验证输入类型
+	kind := reflect.ValueOf(data).Kind()
+	if kind == reflect.Ptr {
+		if reflect.ValueOf(data).IsNil() {
+			return nil, fmt.Errorf("输入数据指针不能为空")
+		}
+		data = reflect.ValueOf(data).Elem().Interface()
+		kind = reflect.ValueOf(data).Kind()
+	}
+	if kind == reflect.Map {
+		mp := data.(map[string]interface{})
+		return mp, nil
+	}
+	if kind != reflect.Struct {
+		return nil, fmt.Errorf("输入数据类型必须是结构体或map")
+	}
+
+	mp := make(map[string]interface{})
+
+	// 使用反射遍历结构体字段并填充map
+	t := reflect.TypeOf(data)
+	v := reflect.ValueOf(data)
+
+	for i := 0; i < t.NumField(); i++ {
+		field := t.Field(i)
+		value := v.Field(i)
+
+		// 将字段名和字段值添加到map中
+		key := field.Tag.Get("json")
+		if key == "-" {
+			continue
+		}
+		if key == "" {
+			key = field.Name
+		}
+		mp[key] = value.Interface()
+	}
+
+	return mp, nil
+}

+ 36 - 0
SERVER/VberAdmin/core/tools/utils/time.go

@@ -0,0 +1,36 @@
+package utils
+
+import (
+	"strconv"
+	"time"
+)
+
+// TimeDiff 时间差计算,true表示超过时间
+func TimeDiff(tm, mat int64) bool {
+	if time.Now().Unix()-tm > mat {
+		return true
+	} else {
+		return false
+	}
+}
+
+func NowLong() int64 {
+	return Time2Long(time.Now())
+}
+
+func Time2Long(time time.Time) int64 {
+	str := time.Format("20060102150405")
+	res, err := strconv.Atoi(str)
+	if err != nil {
+		return 0
+	}
+	return int64(res)
+}
+
+func TimeStr2Long(str string, format string) int64 {
+	t, err := time.Parse(format, str)
+	if err != nil {
+		return 0
+	}
+	return Time2Long(t)
+}

+ 8 - 1
SERVER/VberAdmin/core/tools/writer/file.go

@@ -1,6 +1,7 @@
 package writer
 
 import (
+	"VberAdmin/core/sdk/pkg"
 	"errors"
 	"fmt"
 	"log"
@@ -31,6 +32,12 @@ func NewFileWriter(opts ...Option) (*FileWriter, error) {
 	for _, o := range opts {
 		o(&p.opts)
 	}
+	if !pkg.PathExist(p.opts.path) {
+		err := pkg.PathCreate(p.opts.path)
+		if err != nil {
+			log.Fatalf("[file] create dir error: %s", err.Error())
+		}
+	}
 	var filename string
 	var err error
 	for {
@@ -119,7 +126,7 @@ func (p *FileWriter) getFilename() string {
 				p.opts.suffix))
 	}
 	return filepath.Join(p.opts.path,
-		fmt.Sprintf("%s-[%d].%s",
+		fmt.Sprintf("%s_%d.%s",
 			time.Now().Format(timeFormat),
 			p.num,
 			p.opts.suffix))

+ 1 - 1
SERVER/VberAdmin/server/api.go

@@ -68,7 +68,7 @@ func Init() {
 	}
 
 	// 定时任务
-	go jobs.InitJob()
+	go jobs.Start()
 
 	if *apiCheck {
 		var routers = sdk.Runtime.GetRouter()

+ 23 - 1
UI/VAG.VUE/src/views/schedule/job/index.vue

@@ -34,7 +34,11 @@ const opts = reactive<any>({
 		{ field: "cronExpression", name: "Cron表达式", width: "auto", isSort: false, visible: true },
 		{ field: "invokeTarget", name: "调用目标", width: "auto", isSort: false, visible: true },
 		{ field: "status", name: "状态", width: 100, isSort: true, visible: true },
-		{ field: "createdAt", name: "创建时间", width: 185, isSort: true, visible: true },
+		{ field: "execCount", name: "执行次数", width: 100, isSort: true, visible: true },
+		{ field: "failCount", name: "失败次数", width: 100, isSort: true, visible: true },
+		{ field: "lastExecTime", name: "上次执行时间", width: 155, isSort: true, visible: true },
+		{ field: "nextExecTime", name: "下一次执行时间", width: 155, isSort: true, visible: true },
+		{ field: "createdAt", name: "创建时间", width: 155, isSort: true, visible: true },
 		{ field: "actions", name: `操作`, width: 150 }
 	],
 	queryParams: {
@@ -349,6 +353,24 @@ onMounted(() => setTimeout(init, 100))
 			<template #status="{ row }">
 				<DictTag :value="row.status" valueIsNumber type="sys_job_status"></DictTag>
 			</template>
+			<template #lastExecTime="{ row }">
+				<span>
+					{{
+						row.lastExecTime < 20240000000000
+							? "-"
+							: dayjs(row.lastExecTime + "", "YYYYMMDDHHmmss").format("YYYY-MM-DD HH:mm:ss")
+					}}
+				</span>
+			</template>
+			<template #nextExecTime="{ row }">
+				<span>
+					{{
+						row.nextExecTime < 20240000000000
+							? "-"
+							: dayjs(row.nextExecTime + "", "YYYYMMDDHHmmss").format("YYYY-MM-DD HH:mm:ss")
+					}}
+				</span>
+			</template>
 			<template #createdAt="{ row }">
 				<span>{{ dayjs(row.createdAt).format("YYYY-MM-DD HH:mm:ss") }}</span>
 			</template>