增加任务依赖
parent
3de70760a0
commit
22069aa156
|
@ -10,6 +10,7 @@
|
||||||
* 任务执行失败重试设置
|
* 任务执行失败重试设置
|
||||||
* 任务超时设置
|
* 任务超时设置
|
||||||
* 延时任务
|
* 延时任务
|
||||||
|
* 任务依赖
|
||||||
* 任务类型
|
* 任务类型
|
||||||
* shell任务
|
* shell任务
|
||||||
> 在远程服务器上执行shell命令
|
> 在远程服务器上执行shell命令
|
||||||
|
@ -65,6 +66,7 @@
|
||||||
### 命令
|
### 命令
|
||||||
|
|
||||||
* gocron web
|
* gocron web
|
||||||
|
* --host 默认0.0.0.0
|
||||||
* -p 端口, 指定端口, 默认5920
|
* -p 端口, 指定端口, 默认5920
|
||||||
* -e 指定运行环境, dev|test|prod, dev模式下可查看更多日志信息, 默认prod
|
* -e 指定运行环境, dev|test|prod, dev模式下可查看更多日志信息, 默认prod
|
||||||
* -d 后台运行
|
* -d 后台运行
|
||||||
|
|
16
cmd/web.go
16
cmd/web.go
|
@ -31,6 +31,11 @@ var CmdWeb = cli.Command{
|
||||||
Usage: "run web server",
|
Usage: "run web server",
|
||||||
Action: runWeb,
|
Action: runWeb,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "host",
|
||||||
|
Value: "0.0.0.0",
|
||||||
|
Usage: "bind host",
|
||||||
|
},
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "port,p",
|
Name: "port,p",
|
||||||
Value: DefaultPort,
|
Value: DefaultPort,
|
||||||
|
@ -66,9 +71,10 @@ func runWeb(ctx *cli.Context) {
|
||||||
routers.Register(m)
|
routers.Register(m)
|
||||||
// 注册中间件.
|
// 注册中间件.
|
||||||
routers.RegisterMiddleware(m)
|
routers.RegisterMiddleware(m)
|
||||||
|
host := parseHost(ctx)
|
||||||
port := parsePort(ctx)
|
port := parsePort(ctx)
|
||||||
fmt.Println("server start")
|
fmt.Println("server start")
|
||||||
m.Run(port)
|
m.Run(host, port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func becomeDaemon(ctx *cli.Context) {
|
func becomeDaemon(ctx *cli.Context) {
|
||||||
|
@ -151,6 +157,14 @@ func parsePort(ctx *cli.Context) int {
|
||||||
return port
|
return port
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseHost(ctx *cli.Context) string {
|
||||||
|
if ctx.IsSet("host") {
|
||||||
|
return ctx.String("host")
|
||||||
|
}
|
||||||
|
|
||||||
|
return "0.0.0.0"
|
||||||
|
}
|
||||||
|
|
||||||
func setEnvironment(ctx *cli.Context) {
|
func setEnvironment(ctx *cli.Context) {
|
||||||
var env string = "prod"
|
var env string = "prod"
|
||||||
if ctx.IsSet("env") {
|
if ctx.IsSet("env") {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/ouqiang/gocron/modules/logger"
|
"github.com/ouqiang/gocron/modules/logger"
|
||||||
"github.com/ouqiang/gocron/modules/app"
|
"github.com/ouqiang/gocron/modules/app"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Status int8
|
type Status int8
|
||||||
|
@ -93,6 +94,7 @@ func CreateDb() *xorm.Engine {
|
||||||
engine.Logger().SetLevel(core.LOG_DEBUG)
|
engine.Logger().SetLevel(core.LOG_DEBUG)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
go keepDbAlived(engine)
|
||||||
|
|
||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
@ -139,3 +141,11 @@ func getDbConfig() map[string]string {
|
||||||
|
|
||||||
return db
|
return db
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func keepDbAlived(engine *xorm.Engine) {
|
||||||
|
t := time.Tick(180 * time.Second)
|
||||||
|
for {
|
||||||
|
<- t
|
||||||
|
engine.Ping()
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"github.com/go-xorm/xorm"
|
"github.com/go-xorm/xorm"
|
||||||
"errors"
|
"errors"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TaskProtocol int8
|
type TaskProtocol int8
|
||||||
|
@ -13,22 +14,39 @@ const (
|
||||||
TaskRPC // RPC方式执行命令
|
TaskRPC // RPC方式执行命令
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TaskLevel int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
TaskLevelParent TaskLevel = 1 // 父任务
|
||||||
|
TaskLevelChild TaskLevel = 2 // 子任务(依赖任务)
|
||||||
|
)
|
||||||
|
|
||||||
|
type TaskDependencyStatus int8
|
||||||
|
|
||||||
|
const (
|
||||||
|
TaskDependencyStatusStrong TaskDependencyStatus = 1 // 强依赖
|
||||||
|
TaskDependencyStatusWeak TaskDependencyStatus = 2 // 弱依赖
|
||||||
|
)
|
||||||
|
|
||||||
// 任务
|
// 任务
|
||||||
type Task struct {
|
type Task struct {
|
||||||
Id int `xorm:"int pk autoincr"`
|
Id int `xorm:"int pk autoincr"`
|
||||||
Name string `xorm:"varchar(32) notnull"` // 任务名称
|
Name string `xorm:"varchar(32) notnull"` // 任务名称
|
||||||
|
Level TaskLevel `xorm:"smallint notnull index default 1"` // 任务等级 1: 主任务 2: 依赖任务
|
||||||
|
DependencyTaskId string `xorm:"varchar(64) notnull default ''"` // 依赖任务ID,多个ID逗号分隔
|
||||||
|
DependencyStatus TaskDependencyStatus `xorm:"smallint notnull default 1"` // 依赖关系 1:强依赖 主任务执行成功, 依赖任务才会被执行 2:弱依赖
|
||||||
Spec string `xorm:"varchar(64) notnull"` // crontab
|
Spec string `xorm:"varchar(64) notnull"` // crontab
|
||||||
Protocol TaskProtocol `xorm:"tinyint notnull"` // 协议 1:http 2:系统命令
|
Protocol TaskProtocol `xorm:"tinyint notnull index"` // 协议 1:http 2:系统命令
|
||||||
Command string `xorm:"varchar(256) notnull"` // URL地址或shell命令
|
Command string `xorm:"varchar(256) notnull"` // URL地址或shell命令
|
||||||
Timeout int `xorm:"mediumint notnull default 0"` // 任务执行超时时间(单位秒),0不限制
|
Timeout int `xorm:"mediumint notnull default 0"` // 任务执行超时时间(单位秒),0不限制
|
||||||
Multi int8 `xorm:"tinyint notnull default 1"` // 是否允许多实例运行
|
Multi int8 `xorm:"tinyint notnull default 1"` // 是否允许多实例运行
|
||||||
RetryTimes int8 `xorm:"tinyint notnull default 0"` // 重试次数
|
RetryTimes int8 `xorm:"tinyint notnull default 0"` // 重试次数
|
||||||
HostId int16 `xorm:"smallint notnull default 0"` // RPC host id,
|
HostId int16 `xorm:"smallint notnull index default 0"` // RPC host id,
|
||||||
NotifyStatus int8 `xorm:"smallint notnull default 1"` // 任务执行结束是否通知 0: 不通知 1: 失败通知 2: 执行结束通知
|
NotifyStatus int8 `xorm:"smallint notnull default 1"` // 任务执行结束是否通知 0: 不通知 1: 失败通知 2: 执行结束通知
|
||||||
NotifyType int8 `xorm:"smallint notnull default 0"` // 通知类型 1: 邮件 2: slack
|
NotifyType int8 `xorm:"smallint notnull default 0"` // 通知类型 1: 邮件 2: slack
|
||||||
NotifyReceiverId string `xorm:"varchar(256) notnull default '' "` // 通知接受者ID, setting表主键ID,多个ID逗号分隔
|
NotifyReceiverId string `xorm:"varchar(256) notnull default '' "` // 通知接受者ID, setting表主键ID,多个ID逗号分隔
|
||||||
Remark string `xorm:"varchar(100) notnull default ''"` // 备注
|
Remark string `xorm:"varchar(100) notnull default ''"` // 备注
|
||||||
Status Status `xorm:"tinyint notnull default 0"` // 状态 1:正常 0:停止
|
Status Status `xorm:"tinyint notnull index default 0"` // 状态 1:正常 0:停止
|
||||||
Created time.Time `xorm:"datetime notnull created"` // 创建时间
|
Created time.Time `xorm:"datetime notnull created"` // 创建时间
|
||||||
Deleted time.Time `xorm:"datetime deleted"` // 删除时间
|
Deleted time.Time `xorm:"datetime deleted"` // 删除时间
|
||||||
BaseModel `xorm:"-"`
|
BaseModel `xorm:"-"`
|
||||||
|
@ -59,6 +77,7 @@ func (task *Task) Create() (insertId int, err error) {
|
||||||
func (task *Task) CreateTestTask() {
|
func (task *Task) CreateTestTask() {
|
||||||
// HTTP任务
|
// HTTP任务
|
||||||
task.Name = "测试HTTP任务"
|
task.Name = "测试HTTP任务"
|
||||||
|
task.Level = TaskLevelParent
|
||||||
task.Protocol = TaskHTTP
|
task.Protocol = TaskHTTP
|
||||||
task.Spec = "*/30 * * * * *"
|
task.Spec = "*/30 * * * * *"
|
||||||
// 查询IP地址区域信息
|
// 查询IP地址区域信息
|
||||||
|
@ -68,7 +87,9 @@ func (task *Task) CreateTestTask() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (task *Task) UpdateBean(id int) (int64, error) {
|
func (task *Task) UpdateBean(id int) (int64, error) {
|
||||||
return Db.ID(id).Cols("name,spec,protocol,command,timeout,multi,retry_times,host_id,remark,notify_status,notify_type,notify_receiver_id").Update(task)
|
return Db.ID(id).
|
||||||
|
Cols("name,spec,protocol,command,timeout,multi,retry_times,host_id,remark,notify_status,notify_type,notify_receiver_id, dependency_task_id, dependency_status").
|
||||||
|
Update(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新
|
// 更新
|
||||||
|
@ -95,7 +116,11 @@ func (task *Task) Enable(id int) (int64, error) {
|
||||||
func (task *Task) ActiveList() ([]TaskHost, error) {
|
func (task *Task) ActiveList() ([]TaskHost, error) {
|
||||||
list := make([]TaskHost, 0)
|
list := make([]TaskHost, 0)
|
||||||
fields := "t.*, host.alias,host.name,host.port"
|
fields := "t.*, host.alias,host.name,host.port"
|
||||||
err := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id").Where("t.status = ?", Enabled).Cols(fields).Find(&list)
|
err := Db.Alias("t").
|
||||||
|
Join("LEFT", hostTableName(), "t.host_id=host.id").
|
||||||
|
Where("t.status = ? AND t.level = ?", Enabled, TaskLevelParent).
|
||||||
|
Cols(fields).
|
||||||
|
Find(&list)
|
||||||
|
|
||||||
return list, err
|
return list, err
|
||||||
}
|
}
|
||||||
|
@ -104,7 +129,11 @@ func (task *Task) ActiveList() ([]TaskHost, error) {
|
||||||
func (task *Task) ActiveListByHostId(hostId int16) ([]TaskHost, error) {
|
func (task *Task) ActiveListByHostId(hostId int16) ([]TaskHost, error) {
|
||||||
list := make([]TaskHost, 0)
|
list := make([]TaskHost, 0)
|
||||||
fields := "t.*, host.alias,host.name,host.port"
|
fields := "t.*, host.alias,host.name,host.port"
|
||||||
err := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id").Where("t.status = ? AND t.host_id = ?", Enabled, hostId).Cols(fields).Find(&list)
|
err := Db.Alias("t").
|
||||||
|
Join("LEFT", hostTableName(), "t.host_id=host.id").
|
||||||
|
Where("t.status = ? AND t.host_id = ? AND t.level = ?", Enabled, hostId, TaskLevelParent).
|
||||||
|
Cols(fields).
|
||||||
|
Find(&list)
|
||||||
|
|
||||||
return list, err
|
return list, err
|
||||||
}
|
}
|
||||||
|
@ -158,6 +187,28 @@ func (task *Task) List(params CommonMap) ([]TaskHost, error) {
|
||||||
return list, err
|
return list, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取依赖任务列表
|
||||||
|
func (task *Task) GetDependencyTaskList(ids string) ([]TaskHost, error) {
|
||||||
|
list := make([]TaskHost, 0)
|
||||||
|
if ids == "" {
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
idList := strings.Split(ids, ",")
|
||||||
|
taskIds := make([]interface{}, len(idList))
|
||||||
|
for i, v := range idList {
|
||||||
|
taskIds[i] = v
|
||||||
|
}
|
||||||
|
fields := "t.*, host.alias,host.name,host.port"
|
||||||
|
err := Db.Alias("t").
|
||||||
|
Join("LEFT", hostTableName(), "t.host_id=host.id").
|
||||||
|
Where("t.level = ?", TaskLevelChild).
|
||||||
|
In("t.id", taskIds).
|
||||||
|
Cols(fields).
|
||||||
|
Find(&list)
|
||||||
|
|
||||||
|
return list, err
|
||||||
|
}
|
||||||
|
|
||||||
func (task *Task) Total(params CommonMap) (int64, error) {
|
func (task *Task) Total(params CommonMap) (int64, error) {
|
||||||
session := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id")
|
session := Db.Alias("t").Join("LEFT", hostTableName(), "t.host_id=host.id")
|
||||||
task.parseWhere(session, params)
|
task.parseWhere(session, params)
|
||||||
|
|
|
@ -18,8 +18,11 @@ import (
|
||||||
|
|
||||||
type TaskForm struct {
|
type TaskForm struct {
|
||||||
Id int
|
Id int
|
||||||
|
Level models.TaskLevel `binding:"Required;In(1,2)"`
|
||||||
|
DependencyStatus models.TaskDependencyStatus
|
||||||
|
DependencyTaskId string
|
||||||
Name string `binding:"Required;MaxSize(32)"`
|
Name string `binding:"Required;MaxSize(32)"`
|
||||||
Spec string `binding:"Required;MaxSize(64)"`
|
Spec string
|
||||||
Protocol models.TaskProtocol `binding:"In(1,2)"`
|
Protocol models.TaskProtocol `binding:"In(1,2)"`
|
||||||
Command string `binding:"Required;MaxSize(256)"`
|
Command string `binding:"Required;MaxSize(256)"`
|
||||||
Timeout int `binding:"Range(0,86400)"`
|
Timeout int `binding:"Range(0,86400)"`
|
||||||
|
@ -99,10 +102,6 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||||
json := utils.JsonResponse{}
|
json := utils.JsonResponse{}
|
||||||
taskModel := models.Task{}
|
taskModel := models.Task{}
|
||||||
var id int = form.Id
|
var id int = form.Id
|
||||||
_, err := cron.Parse(form.Spec)
|
|
||||||
if err != nil {
|
|
||||||
return json.CommonFailure("crontab表达式解析失败", err)
|
|
||||||
}
|
|
||||||
nameExists, err := taskModel.NameExist(form.Name, form.Id)
|
nameExists, err := taskModel.NameExist(form.Name, form.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return json.CommonFailure(utils.FailureContent, err)
|
return json.CommonFailure(utils.FailureContent, err)
|
||||||
|
@ -134,8 +133,11 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||||
taskModel.NotifyType = form.NotifyType - 1
|
taskModel.NotifyType = form.NotifyType - 1
|
||||||
taskModel.NotifyReceiverId = form.NotifyReceiverId
|
taskModel.NotifyReceiverId = form.NotifyReceiverId
|
||||||
taskModel.Spec = form.Spec
|
taskModel.Spec = form.Spec
|
||||||
|
taskModel.Level = form.Level
|
||||||
|
taskModel.DependencyStatus = form.DependencyStatus
|
||||||
|
taskModel.DependencyTaskId = strings.TrimSpace(form.DependencyTaskId)
|
||||||
if taskModel.NotifyStatus > 0 && taskModel.NotifyReceiverId == "" {
|
if taskModel.NotifyStatus > 0 && taskModel.NotifyReceiverId == "" {
|
||||||
return json.CommonFailure("请至少选择一个接收者")
|
return json.CommonFailure("至少选择一个通知接收者")
|
||||||
}
|
}
|
||||||
if taskModel.Protocol == models.TaskHTTP {
|
if taskModel.Protocol == models.TaskHTTP {
|
||||||
command := strings.ToLower(taskModel.Command)
|
command := strings.ToLower(taskModel.Command)
|
||||||
|
@ -151,6 +153,27 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||||
return json.CommonFailure("任务重试次数取值0-10")
|
return json.CommonFailure("任务重试次数取值0-10")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (taskModel.DependencyStatus != models.TaskDependencyStatusStrong &&
|
||||||
|
taskModel.DependencyStatus != models.TaskDependencyStatusWeak) {
|
||||||
|
return json.CommonFailure("请选择依赖关系")
|
||||||
|
}
|
||||||
|
|
||||||
|
if taskModel.Level == models.TaskLevelParent {
|
||||||
|
_, err = cron.Parse(form.Spec)
|
||||||
|
if err != nil {
|
||||||
|
return json.CommonFailure("crontab表达式解析失败", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
taskModel.DependencyTaskId = ""
|
||||||
|
taskModel.Spec = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if id > 0 && taskModel.DependencyTaskId != "" {
|
||||||
|
dependencyTaskIds := strings.Split(taskModel.DependencyTaskId, ",")
|
||||||
|
if utils.InStringSlice(dependencyTaskIds, strconv.Itoa(id)) {
|
||||||
|
return json.CommonFailure("不允许设置当前任务为子任务")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if id == 0 {
|
if id == 0 {
|
||||||
// 任务添加后开始调度执行
|
// 任务添加后开始调度执行
|
||||||
|
@ -165,7 +188,7 @@ func Store(ctx *macaron.Context, form TaskForm) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
status, err := taskModel.GetStatus(id)
|
status, err := taskModel.GetStatus(id)
|
||||||
if status == models.Enabled {
|
if status == models.Enabled && taskModel.Level == models.TaskLevelParent {
|
||||||
addTaskToTimer(id)
|
addTaskToTimer(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
rpcClient "github.com/ouqiang/gocron/modules/rpc/client"
|
rpcClient "github.com/ouqiang/gocron/modules/rpc/client"
|
||||||
pb "github.com/ouqiang/gocron/modules/rpc/proto"
|
pb "github.com/ouqiang/gocron/modules/rpc/proto"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 定时任务调度管理器
|
// 定时任务调度管理器
|
||||||
|
@ -47,13 +48,16 @@ func (c *TaskCount) Num() int {
|
||||||
return c.num
|
return c.num
|
||||||
}
|
}
|
||||||
|
|
||||||
// 任务ID作为Key, 不会出现并发写, 不加锁
|
// 任务ID作为Key
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
Status map[int]bool
|
Status map[int]bool
|
||||||
|
sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// 是否有任务处于运行中
|
// 是否有任务处于运行中
|
||||||
func (i *Instance) has(key int) bool {
|
func (i *Instance) has(key int) bool {
|
||||||
|
i.RLock()
|
||||||
|
defer i.RUnlock()
|
||||||
running, ok := i.Status[key]
|
running, ok := i.Status[key]
|
||||||
if ok && running {
|
if ok && running {
|
||||||
return true
|
return true
|
||||||
|
@ -63,11 +67,15 @@ func (i *Instance) has(key int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) add(key int) {
|
func (i *Instance) add(key int) {
|
||||||
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
i.Status[key] = true
|
i.Status[key] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Instance) done(key int) {
|
func (i *Instance) done(key int) {
|
||||||
i.Status[key] = false
|
i.Lock()
|
||||||
|
defer i.Unlock()
|
||||||
|
delete(i.Status, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Task struct{}
|
type Task struct{}
|
||||||
|
@ -82,7 +90,7 @@ type TaskResult struct {
|
||||||
func (task *Task) Initialize() {
|
func (task *Task) Initialize() {
|
||||||
Cron = cron.New()
|
Cron = cron.New()
|
||||||
Cron.Start()
|
Cron.Start()
|
||||||
runInstance = Instance{make(map[int]bool)}
|
runInstance = Instance{make(map[int]bool), sync.RWMutex{}}
|
||||||
TaskNum = TaskCount{0, sync.RWMutex{}}
|
TaskNum = TaskCount{0, sync.RWMutex{}}
|
||||||
|
|
||||||
taskModel := new(models.Task)
|
taskModel := new(models.Task)
|
||||||
|
@ -107,6 +115,10 @@ func (task *Task) BatchAdd(tasks []models.TaskHost) {
|
||||||
|
|
||||||
// 添加任务
|
// 添加任务
|
||||||
func (task *Task) Add(taskModel models.TaskHost) {
|
func (task *Task) Add(taskModel models.TaskHost) {
|
||||||
|
if taskModel.Level == models.TaskLevelChild {
|
||||||
|
logger.Errorf("添加任务失败#不允许添加子任务到调度器#任务Id-%d", taskModel.Id);
|
||||||
|
return
|
||||||
|
}
|
||||||
taskFunc := createJob(taskModel)
|
taskFunc := createJob(taskModel)
|
||||||
if taskFunc == nil {
|
if taskFunc == nil {
|
||||||
logger.Error("创建任务处理Job失败,不支持的任务协议#", taskModel.Protocol)
|
logger.Error("创建任务处理Job失败,不支持的任务协议#", taskModel.Protocol)
|
||||||
|
@ -239,6 +251,7 @@ func createHandler(taskModel models.TaskHost) Handler {
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 任务前置操作
|
||||||
func beforeExecJob(taskModel models.TaskHost) (taskLogId int64) {
|
func beforeExecJob(taskModel models.TaskHost) (taskLogId int64) {
|
||||||
if taskModel.Multi == 0 && runInstance.has(taskModel.Id) {
|
if taskModel.Multi == 0 && runInstance.has(taskModel.Id) {
|
||||||
createTaskLog(taskModel, models.Cancel)
|
createTaskLog(taskModel, models.Cancel)
|
||||||
|
@ -258,6 +271,7 @@ func beforeExecJob(taskModel models.TaskHost) (taskLogId int64) {
|
||||||
return taskLogId
|
return taskLogId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 任务执行后置操作
|
||||||
func afterExecJob(taskModel models.TaskHost, taskResult TaskResult, taskLogId int64) {
|
func afterExecJob(taskModel models.TaskHost, taskResult TaskResult, taskLogId int64) {
|
||||||
if taskResult.Err != nil {
|
if taskResult.Err != nil {
|
||||||
taskResult.Result = taskResult.Err.Error() + "\n" + taskResult.Result
|
taskResult.Result = taskResult.Err.Error() + "\n" + taskResult.Result
|
||||||
|
@ -267,7 +281,47 @@ func afterExecJob(taskModel models.TaskHost, taskResult TaskResult, taskLogId in
|
||||||
logger.Error("任务结束#更新任务日志失败-", err)
|
logger.Error("任务结束#更新任务日志失败-", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
SendNotification(taskModel, taskResult)
|
// 发送邮件
|
||||||
|
go SendNotification(taskModel, taskResult)
|
||||||
|
// 执行依赖任务
|
||||||
|
go execDependencyTask(taskModel, taskResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行依赖任务, 多个任务并发执行
|
||||||
|
func execDependencyTask(taskModel models.TaskHost, taskResult TaskResult) {
|
||||||
|
// 父任务才能执行子任务
|
||||||
|
if taskModel.Level != models.TaskLevelParent {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 是否存在子任务
|
||||||
|
dependencyTaskId := strings.TrimSpace(taskModel.DependencyTaskId)
|
||||||
|
if dependencyTaskId == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 父子任务关系为强依赖, 父任务执行失败, 不执行依赖任务
|
||||||
|
if taskModel.DependencyStatus == models.TaskDependencyStatusStrong && taskResult.Err != nil {
|
||||||
|
logger.Infof("父子任务为强依赖关系, 父任务执行失败, 不运行依赖任务#主任务ID-%d", taskModel.Id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取子任务
|
||||||
|
model := new(models.Task)
|
||||||
|
tasks , err := model.GetDependencyTaskList(dependencyTaskId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Errorf("获取依赖任务失败#主任务ID-%d#%s", taskModel.Id, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(tasks) == 0 {
|
||||||
|
logger.Errorf("依赖任务列表为空#主任务ID-%d", taskModel.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceTask := new(Task)
|
||||||
|
for _, task := range tasks {
|
||||||
|
task.Spec = fmt.Sprintf("依赖任务(主任务ID-%d)", taskModel.Id)
|
||||||
|
serviceTask.Run(task)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送任务结果通知
|
// 发送任务结果通知
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<th>任务ID</th>
|
<th>任务ID</th>
|
||||||
<th>任务名称</th>
|
<th>任务名称</th>
|
||||||
|
<th>任务类型</th>
|
||||||
<th>cron表达式</th>
|
<th>cron表达式</th>
|
||||||
<th>执行方式</th>
|
<th>执行方式</th>
|
||||||
<th>超时时间</th>
|
<th>超时时间</th>
|
||||||
|
@ -69,21 +70,28 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{{.Id}}}</td>
|
<td>{{{.Id}}}</td>
|
||||||
<td>{{{.Task.Name}}}</td>
|
<td>{{{.Task.Name}}}</td>
|
||||||
|
<td>{{{if eq .Level 1}}}主任务{{{else}}}子任务{{{end}}}</td>
|
||||||
<td>{{{.Spec}}}</td>
|
<td>{{{.Spec}}}</td>
|
||||||
<td>{{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SHELL {{{end}}}</td>
|
<td>{{{if eq .Protocol 1}}} HTTP {{{else if eq .Protocol 2}}} SHELL {{{end}}}</td>
|
||||||
<td>{{{if eq .Timeout -1}}}后台运行{{{else if gt .Timeout 0}}}{{{.Timeout}}}秒{{{else}}}不限制{{{end}}}</td>
|
<td>{{{if eq .Timeout -1}}}后台运行{{{else if gt .Timeout 0}}}{{{.Timeout}}}秒{{{else}}}不限制{{{end}}}</td>
|
||||||
<td>{{{.RetryTimes}}}</td>
|
<td>{{{.RetryTimes}}}</td>
|
||||||
<td>{{{if gt .Multi 0}}}否{{{else}}}是{{{end}}}</td>
|
<td>{{{if gt .Multi 0}}}否{{{else}}}是{{{end}}}</td>
|
||||||
<td>{{{.Alias}}}-{{{.Name}}}</td>
|
<td>{{{.Alias}}}-{{{.Name}}}</td>
|
||||||
<td>{{{if eq .Status 1}}}<span style="color: green;">激活</span>{{{else}}}<span style="color: red;">停止<span>{{{end}}}</td>
|
<td>
|
||||||
|
{{{if eq .Level 1}}}
|
||||||
|
{{{if eq .Status 1}}}<span style="color: green;">激活</span>{{{else}}}<span style="color: red;">停止<span>{{{end}}}
|
||||||
|
{{{end}}}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="ui buttons operation">
|
<div class="ui buttons operation">
|
||||||
<a class="ui purple button" href="/task/edit/{{{.Id}}}">编辑</a>
|
<a class="ui purple button" href="/task/edit/{{{.Id}}}">编辑</a>
|
||||||
|
{{{if eq .Level 1}}}
|
||||||
{{{if eq .Status 1}}}
|
{{{if eq .Status 1}}}
|
||||||
<button class="ui primary button" @click="changeStatus({{{.Id}}},{{{.Status}}})">停止</button>
|
<button class="ui primary button" @click="changeStatus({{{.Id}}},{{{.Status}}})">停止</button>
|
||||||
{{{else}}}
|
{{{else}}}
|
||||||
<button class="ui blue button" @click="changeStatus({{{.Id}}},{{{.Status}}})">激活 </button>
|
<button class="ui blue button" @click="changeStatus({{{.Id}}},{{{.Status}}})">激活 </button>
|
||||||
{{{end}}}
|
{{{end}}}
|
||||||
|
{{{end}}}
|
||||||
<button class="ui positive button" @click="remove({{{.Id}}})">删除</button> <br>
|
<button class="ui positive button" @click="remove({{{.Id}}})">删除</button> <br>
|
||||||
<button class="ui twitter button" @click="run({{{.Id}}})">手动运行</button>
|
<button class="ui twitter button" @click="run({{{.Id}}})">手动运行</button>
|
||||||
<a class="ui instagram button" href="/task/log?task_id={{{.Id}}}">查看日志</a>
|
<a class="ui instagram button" href="/task/log?task_id={{{.Id}}}">查看日志</a>
|
||||||
|
@ -97,7 +105,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$('.ui.checkbox').checkbox();
|
$('.ui.checkbox').checkbox();
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,52 @@
|
||||||
<div class="content">任务名称</div>
|
<div class="content">任务名称</div>
|
||||||
</label>
|
</label>
|
||||||
<div class="ui small input">
|
<div class="ui small input">
|
||||||
<input type="text" name="name" value="{{{.Task.Task.Name}}}">
|
<input type="text" name="name" placeholder="订单量统计" value="{{{.Task.Task.Name}}}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>
|
||||||
|
<div class="content">任务类型</div>
|
||||||
|
<div class="ui message">
|
||||||
|
主任务可以配置多个子任务, 当主任务执行完成后,自动执行子任务<br>
|
||||||
|
任务类型新增后不能变更
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<select name="level" id="level" {{{if .Task}}}disabled="disabled"{{{end}}}>
|
||||||
|
<option value="1" {{{if .Task}}} {{{if eq .Task.Level 1}}}selected{{{end}}} {{{end}}}>主任务</option>
|
||||||
|
<option value="2" {{{if .Task}}} {{{if eq .Task.Level 2}}}selected{{{end}}} {{{end}}}>子任务</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="parent-task">
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>
|
||||||
|
<div class="content">依赖关系</div>
|
||||||
|
<div class="ui message">
|
||||||
|
强依赖: 主任务执行成功,才会运行子任务 <br>
|
||||||
|
弱依赖: 无论主任务执行是否成功,都会运行子任务
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<select name="dependency_status" id="dependency_status">
|
||||||
|
<option value="1" {{{if .Task}}} {{{if eq .Task.DependencyStatus 1}}}selected{{{end}}} {{{end}}}>强依赖</option>
|
||||||
|
<option value="2" {{{if .Task}}} {{{if eq .Task.DependencyStatus 2}}}selected{{{end}}} {{{end}}}>弱依赖</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="two fields">
|
||||||
|
<div class="field">
|
||||||
|
<label>
|
||||||
|
<div class="content">子任务ID</div>
|
||||||
|
<div class="ui message">
|
||||||
|
多个任务ID逗号分隔 <br>
|
||||||
|
子任务并发执行
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<div class="ui small input">
|
||||||
|
<input type="text" name="dependency_task_id" placeholder="可选" value="{{{.Task.DependencyTaskId}}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -35,6 +80,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div class="three fields">
|
<div class="three fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>执行方式</label>
|
<label>执行方式</label>
|
||||||
|
@ -46,7 +92,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="three fields" id="hostField">
|
<div class="three fields" id="hostField">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>主机</label>
|
<label>任务执行器</label>
|
||||||
<select name="host_id" id="hostId">
|
<select name="host_id" id="hostId">
|
||||||
<option value="">选择主机</option>
|
<option value="">选择主机</option>
|
||||||
{{{range $i, $v := .Hosts}}}
|
{{{range $i, $v := .Hosts}}}
|
||||||
|
@ -64,13 +110,13 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="six fields">
|
<div class="six fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>任务超时时间(秒)</label>
|
<label>任务超时时间(秒, 0-86400)</label>
|
||||||
<input type="text" name="timeout" placeholder="默认0, 不限制" value="{{{if .Task}}} {{{.Task.Timeout}}} {{{else}}} 0 {{{end}}}">
|
<input type="text" name="timeout" placeholder="默认0, 不限制" value="{{{if .Task}}} {{{.Task.Timeout}}} {{{else}}} 0 {{{end}}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="six fields">
|
<div class="six fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>任务失败重试次数</label>
|
<label>任务失败重试次数 (0-10)</label>
|
||||||
<input type="text" name="retry_times" placeholder="默认0, 不重试" value="{{{if .Task}}} {{{.Task.RetryTimes}}} {{{else}}} 0 {{{end}}}">
|
<input type="text" name="retry_times" placeholder="默认0, 不重试" value="{{{if .Task}}} {{{.Task.RetryTimes}}} {{{else}}} 0 {{{end}}}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,7 +153,7 @@
|
||||||
<div class="two fields">
|
<div class="two fields">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>备注</label>
|
<label>备注</label>
|
||||||
<textarea rows="5" name="remark">{{{.Task.Remark}}}</textarea>
|
<textarea rows="5" name="remark" placeholder="统计昨天的订单量">{{{.Task.Remark}}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui primary submit button">保存</div> <a class="ui button" onclick="location.href='/task';">取消</a>
|
<div class="ui primary submit button">保存</div> <a class="ui button" onclick="location.href='/task';">取消</a>
|
||||||
|
@ -144,6 +190,7 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$(function() {
|
$(function() {
|
||||||
changeCommandPlaceholder();
|
changeCommandPlaceholder();
|
||||||
|
changeLevel();
|
||||||
changeProtocol();
|
changeProtocol();
|
||||||
showNotify();
|
showNotify();
|
||||||
});
|
});
|
||||||
|
@ -153,7 +200,9 @@
|
||||||
changeProtocol();
|
changeProtocol();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#level').change(function() {
|
||||||
|
changeLevel();
|
||||||
|
});
|
||||||
|
|
||||||
$('#task-status').change(function() {
|
$('#task-status').change(function() {
|
||||||
var selected = $(this).val();
|
var selected = $(this).val();
|
||||||
|
@ -269,6 +318,19 @@
|
||||||
return receivers.join(",");
|
return receivers.join(",");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function changeLevel() {
|
||||||
|
var selected = $('#level').val();
|
||||||
|
if (selected == 1) {
|
||||||
|
// 主任务
|
||||||
|
$('#parent-task').show();
|
||||||
|
$('#child-task').hide();
|
||||||
|
} else {
|
||||||
|
// 子任务
|
||||||
|
$('#parent-task').hide();
|
||||||
|
$('#child-task').show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var $uiForm = $('.ui.form');
|
var $uiForm = $('.ui.form');
|
||||||
registerSelectFormValidation("selectProtocol", $uiForm, $('#protocol'), 'protocol');
|
registerSelectFormValidation("selectProtocol", $uiForm, $('#protocol'), 'protocol');
|
||||||
$($uiForm).form(
|
$($uiForm).form(
|
||||||
|
@ -299,19 +361,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
spec: {
|
|
||||||
identifier : 'spec',
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
type : 'empty',
|
|
||||||
prompt : '请输入crontab格式表达式'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type : 'maxLength[64]',
|
|
||||||
prompt : '长度不能超过64'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
command: {
|
command: {
|
||||||
identifier : 'command',
|
identifier : 'command',
|
||||||
rules: [
|
rules: [
|
||||||
|
|
Loading…
Reference in New Issue