├── constants.go ├── go.mod ├── example ├── task │ ├── panic.go │ ├── test.go │ └── test2.go └── main.go ├── middleware.go ├── .gitignore ├── log.go ├── .github └── FUNDING.yml ├── log_handler.go ├── task_list.go ├── task.go ├── LICENSE ├── util.go ├── optinos.go ├── README.md ├── dto.go └── executor.go /constants.go: -------------------------------------------------------------------------------- 1 | package xxl 2 | 3 | // 响应码 4 | const ( 5 | SuccessCode = 200 6 | FailureCode = 500 7 | ) 8 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xxl-job/xxl-job-executor-go 2 | 3 | go 1.14 4 | 5 | require github.com/go-basic/ipv4 v1.0.0 6 | -------------------------------------------------------------------------------- /example/task/panic.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | xxl "github.com/xxl-job/xxl-job-executor-go" 6 | ) 7 | 8 | func Panic(cxt context.Context, param *xxl.RunReq) (msg string) { 9 | panic("test panic") 10 | return 11 | } 12 | -------------------------------------------------------------------------------- /middleware.go: -------------------------------------------------------------------------------- 1 | package xxl 2 | 3 | // Middleware 中间件构造函数 4 | type Middleware func(TaskFunc) TaskFunc 5 | 6 | func (e *executor) chain(next TaskFunc) TaskFunc { 7 | for i := range e.middlewares { 8 | next = e.middlewares[len(e.middlewares)-1-i](next) 9 | } 10 | return next 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | go.sum 17 | .idea/ 18 | -------------------------------------------------------------------------------- /example/task/test.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | xxl "github.com/xxl-job/xxl-job-executor-go" 7 | ) 8 | 9 | func Test(cxt context.Context, param *xxl.RunReq) (msg string) { 10 | fmt.Println("test one task" + param.ExecutorHandler + " param:" + param.ExecutorParams + " log_id:" + xxl.Int64ToStr(param.LogID)) 11 | return "test done" 12 | } 13 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | package xxl 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | // LogFunc 应用日志 9 | type LogFunc func(req LogReq, res *LogRes) []byte 10 | 11 | // Logger 系统日志 12 | type Logger interface { 13 | Info(format string, a ...interface{}) 14 | Error(format string, a ...interface{}) 15 | } 16 | 17 | type logger struct { 18 | } 19 | 20 | func (l *logger) Info(format string, a ...interface{}) { 21 | fmt.Println(fmt.Sprintf(format, a...)) 22 | } 23 | 24 | func (l *logger) Error(format string, a ...interface{}) { 25 | log.Println(fmt.Sprintf(format, a...)) 26 | } 27 | -------------------------------------------------------------------------------- /example/task/test2.go: -------------------------------------------------------------------------------- 1 | package task 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | xxl "github.com/xxl-job/xxl-job-executor-go" 7 | "time" 8 | ) 9 | 10 | func Test2(cxt context.Context, param *xxl.RunReq) (msg string) { 11 | num := 1 12 | for { 13 | 14 | select { 15 | case <-cxt.Done(): 16 | fmt.Println("task" + param.ExecutorHandler + "被手动终止") 17 | return 18 | default: 19 | num++ 20 | time.Sleep(10 * time.Second) 21 | fmt.Println("test one task"+param.ExecutorHandler+" param:"+param.ExecutorParams+"执行行", num) 22 | if num > 10 { 23 | fmt.Println("test one task" + param.ExecutorHandler + " param:" + param.ExecutorParams + "执行完毕!") 24 | return 25 | } 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: xxl-job 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /log_handler.go: -------------------------------------------------------------------------------- 1 | package xxl 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | /** 9 | 用来日志查询,显示到xxl-job-admin后台 10 | */ 11 | 12 | type LogHandler func(req *LogReq) *LogRes 13 | 14 | //默认返回 15 | func defaultLogHandler(req *LogReq) *LogRes { 16 | return &LogRes{Code: SuccessCode, Msg: "", Content: LogResContent{ 17 | FromLineNum: req.FromLineNum, 18 | ToLineNum: 2, 19 | LogContent: "这是日志默认返回,说明没有设置LogHandler", 20 | IsEnd: true, 21 | }} 22 | } 23 | 24 | //请求错误 25 | func reqErrLogHandler(w http.ResponseWriter, req *LogReq, err error) { 26 | res := &LogRes{Code: FailureCode, Msg: err.Error(), Content: LogResContent{ 27 | FromLineNum: req.FromLineNum, 28 | ToLineNum: 0, 29 | LogContent: err.Error(), 30 | IsEnd: true, 31 | }} 32 | str, _ := json.Marshal(res) 33 | _, _ = w.Write(str) 34 | } 35 | -------------------------------------------------------------------------------- /task_list.go: -------------------------------------------------------------------------------- 1 | package xxl 2 | 3 | import "sync" 4 | 5 | //任务列表 [JobID]执行函数,并行执行时[+LogID] 6 | type taskList struct { 7 | mu sync.RWMutex 8 | data map[string]*Task 9 | } 10 | 11 | // Set 设置数据 12 | func (t *taskList) Set(key string, val *Task) { 13 | t.mu.Lock() 14 | t.data[key] = val 15 | t.mu.Unlock() 16 | } 17 | 18 | // Get 获取数据 19 | func (t *taskList) Get(key string) *Task { 20 | t.mu.RLock() 21 | defer t.mu.RUnlock() 22 | return t.data[key] 23 | } 24 | 25 | // GetAll 获取数据 26 | func (t *taskList) GetAll() map[string]*Task { 27 | t.mu.RLock() 28 | defer t.mu.RUnlock() 29 | return t.data 30 | } 31 | 32 | // Del 设置数据 33 | func (t *taskList) Del(key string) { 34 | t.mu.Lock() 35 | delete(t.data, key) 36 | t.mu.Unlock() 37 | } 38 | 39 | // Len 长度 40 | func (t *taskList) Len() int { 41 | return len(t.data) 42 | } 43 | 44 | // Exists Key是否存在 45 | func (t *taskList) Exists(key string) bool { 46 | t.mu.RLock() 47 | defer t.mu.RUnlock() 48 | _, ok := t.data[key] 49 | return ok 50 | } 51 | -------------------------------------------------------------------------------- /task.go: -------------------------------------------------------------------------------- 1 | package xxl 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "runtime/debug" 7 | ) 8 | 9 | // TaskFunc 任务执行函数 10 | type TaskFunc func(cxt context.Context, param *RunReq) string 11 | 12 | // Task 任务 13 | type Task struct { 14 | Id int64 15 | Name string 16 | Ext context.Context 17 | Param *RunReq 18 | fn TaskFunc 19 | Cancel context.CancelFunc 20 | StartTime int64 21 | EndTime int64 22 | //日志 23 | log Logger 24 | } 25 | 26 | // Run 运行任务 27 | func (t *Task) Run(callback func(code int64, msg string)) { 28 | defer func(cancel func()) { 29 | if err := recover(); err != nil { 30 | t.log.Info(t.Info()+" panic: %v", err) 31 | debug.PrintStack() //堆栈跟踪 32 | callback(FailureCode, fmt.Sprintf("task panic:%v", err)) 33 | cancel() 34 | } 35 | }(t.Cancel) 36 | msg := t.fn(t.Ext, t.Param) 37 | callback(SuccessCode, msg) 38 | return 39 | } 40 | 41 | // Info 任务信息 42 | func (t *Task) Info() string { 43 | return fmt.Sprintf("任务ID[%d]任务名称[%s]参数:%s", t.Id, t.Name, t.Param.ExecutorParams) 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 xxl-job 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package xxl 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | ) 7 | 8 | // Int64ToStr int64 to str 9 | func Int64ToStr(i int64) string { 10 | return strconv.FormatInt(i, 10) 11 | } 12 | 13 | //执行任务回调 14 | func returnCall(req *RunReq, code int64, msg string) []byte { 15 | data := call{ 16 | &callElement{ 17 | LogID: req.LogID, 18 | LogDateTim: req.LogDateTime, 19 | ExecuteResult: &ExecuteResult{ 20 | Code: code, 21 | Msg: msg, 22 | }, 23 | HandleCode: int(code), 24 | HandleMsg: msg, 25 | }, 26 | } 27 | str, _ := json.Marshal(data) 28 | return str 29 | } 30 | 31 | //杀死任务返回 32 | func returnKill(req *killReq, code int64) []byte { 33 | msg := "" 34 | if code != SuccessCode { 35 | msg = "Task kill err" 36 | } 37 | data := res{ 38 | Code: code, 39 | Msg: msg, 40 | } 41 | str, _ := json.Marshal(data) 42 | return str 43 | } 44 | 45 | //忙碌返回 46 | func returnIdleBeat(code int64) []byte { 47 | msg := "" 48 | if code != SuccessCode { 49 | msg = "Task is busy" 50 | } 51 | data := res{ 52 | Code: code, 53 | Msg: msg, 54 | } 55 | str, _ := json.Marshal(data) 56 | return str 57 | } 58 | 59 | //通用返回 60 | func returnGeneral() []byte { 61 | data := &res{ 62 | Code: SuccessCode, 63 | Msg: "", 64 | } 65 | str, _ := json.Marshal(data) 66 | return str 67 | } 68 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | xxl "github.com/xxl-job/xxl-job-executor-go" 7 | "github.com/xxl-job/xxl-job-executor-go/example/task" 8 | "log" 9 | ) 10 | 11 | func main() { 12 | exec := xxl.NewExecutor( 13 | xxl.ServerAddr("http://127.0.0.1/xxl-job-admin"), 14 | xxl.AccessToken(""), //请求令牌(默认为空) 15 | xxl.ExecutorIp("127.0.0.1"), //可自动获取 16 | xxl.ExecutorPort("9999"), //默认9999(非必填) 17 | xxl.RegistryKey("golang-jobs"), //执行器名称 18 | xxl.SetLogger(&logger{}), //自定义日志 19 | ) 20 | exec.Init() 21 | exec.Use(customMiddleware) 22 | //设置日志查看handler 23 | exec.LogHandler(customLogHandle) 24 | //注册任务handler 25 | exec.RegTask("task.test", task.Test) 26 | exec.RegTask("task.test2", task.Test2) 27 | exec.RegTask("task.panic", task.Panic) 28 | log.Fatal(exec.Run()) 29 | } 30 | 31 | // 自定义日志处理器 32 | func customLogHandle(req *xxl.LogReq) *xxl.LogRes { 33 | return &xxl.LogRes{Code: xxl.SuccessCode, Msg: "", Content: xxl.LogResContent{ 34 | FromLineNum: req.FromLineNum, 35 | ToLineNum: 2, 36 | LogContent: "这个是自定义日志handler", 37 | IsEnd: true, 38 | }} 39 | } 40 | 41 | // xxl.Logger接口实现 42 | type logger struct{} 43 | 44 | func (l *logger) Info(format string, a ...interface{}) { 45 | fmt.Println(fmt.Sprintf("自定义日志 - "+format, a...)) 46 | } 47 | 48 | func (l *logger) Error(format string, a ...interface{}) { 49 | log.Println(fmt.Sprintf("自定义日志 - "+format, a...)) 50 | } 51 | 52 | // 自定义中间件 53 | func customMiddleware(tf xxl.TaskFunc) xxl.TaskFunc { 54 | return func(cxt context.Context, param *xxl.RunReq) string { 55 | log.Println("I am a middleware start") 56 | res := tf(cxt, param) 57 | log.Println("I am a middleware end") 58 | return res 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /optinos.go: -------------------------------------------------------------------------------- 1 | package xxl 2 | 3 | import ( 4 | "github.com/go-basic/ipv4" 5 | "time" 6 | ) 7 | 8 | type Options struct { 9 | ServerAddr string `json:"server_addr"` //调度中心地址 10 | AccessToken string `json:"access_token"` //请求令牌 11 | Timeout time.Duration `json:"timeout"` //接口超时时间 12 | ExecutorIp string `json:"executor_ip"` //本地(执行器)IP(可自行获取) 13 | ExecutorPort string `json:"executor_port"` //本地(执行器)端口 14 | RegistryKey string `json:"registry_key"` //执行器名称 15 | LogDir string `json:"log_dir"` //日志目录 16 | 17 | l Logger //日志处理 18 | } 19 | 20 | func newOptions(opts ...Option) Options { 21 | opt := Options{ 22 | ExecutorIp: ipv4.LocalIP(), 23 | ExecutorPort: DefaultExecutorPort, 24 | RegistryKey: DefaultRegistryKey, 25 | } 26 | 27 | for _, o := range opts { 28 | o(&opt) 29 | } 30 | 31 | if opt.l == nil { 32 | opt.l = &logger{} 33 | } 34 | 35 | return opt 36 | } 37 | 38 | type Option func(o *Options) 39 | 40 | var ( 41 | DefaultExecutorPort = "9999" 42 | DefaultRegistryKey = "golang-jobs" 43 | ) 44 | 45 | // ServerAddr 设置调度中心地址 46 | func ServerAddr(addr string) Option { 47 | return func(o *Options) { 48 | o.ServerAddr = addr 49 | } 50 | } 51 | 52 | // AccessToken 请求令牌 53 | func AccessToken(token string) Option { 54 | return func(o *Options) { 55 | o.AccessToken = token 56 | } 57 | } 58 | 59 | // ExecutorIp 设置执行器IP 60 | func ExecutorIp(ip string) Option { 61 | return func(o *Options) { 62 | o.ExecutorIp = ip 63 | } 64 | } 65 | 66 | // ExecutorPort 设置执行器端口 67 | func ExecutorPort(port string) Option { 68 | return func(o *Options) { 69 | o.ExecutorPort = port 70 | } 71 | } 72 | 73 | // RegistryKey 设置执行器标识 74 | func RegistryKey(registryKey string) Option { 75 | return func(o *Options) { 76 | o.RegistryKey = registryKey 77 | } 78 | } 79 | 80 | // SetLogger 设置日志处理器 81 | func SetLogger(l Logger) Option { 82 | return func(o *Options) { 83 | o.l = l 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xxl-job-executor-go 2 | 很多公司java与go开发共存,java中有xxl-job做为任务调度引擎,为此也出现了go执行器(客户端),使用起来比较简单: 3 | # 支持 4 | ``` 5 | 1.执行器注册 6 | 2.耗时任务取消 7 | 3.任务注册,像写http.Handler一样方便 8 | 4.任务panic处理 9 | 5.阻塞策略处理 10 | 6.任务完成支持返回执行备注 11 | 7.任务超时取消 (单位:秒,0为不限制) 12 | 8.失败重试次数(在参数param中,目前由任务自行处理) 13 | 9.可自定义日志 14 | 10.自定义日志查看handler 15 | 11.支持外部路由(可与gin集成) 16 | 12.支持自定义中间件 17 | ``` 18 | 19 | # Example 20 | ```go 21 | package main 22 | 23 | import ( 24 | "context" 25 | "fmt" 26 | xxl "github.com/xxl-job/xxl-job-executor-go" 27 | "github.com/xxl-job/xxl-job-executor-go/example/task" 28 | "log" 29 | ) 30 | 31 | func main() { 32 | exec := xxl.NewExecutor( 33 | xxl.ServerAddr("http://127.0.0.1/xxl-job-admin"), 34 | xxl.AccessToken(""), //请求令牌(默认为空) 35 | xxl.ExecutorIp("127.0.0.1"), //可自动获取 36 | xxl.ExecutorPort("9999"), //默认9999(非必填) 37 | xxl.RegistryKey("golang-jobs"), //执行器名称 38 | xxl.SetLogger(&logger{}), //自定义日志 39 | ) 40 | exec.Init() 41 | exec.Use(customMiddleware) 42 | //设置日志查看handler 43 | exec.LogHandler(customLogHandle) 44 | //注册任务handler 45 | exec.RegTask("task.test", task.Test) 46 | exec.RegTask("task.test2", task.Test2) 47 | exec.RegTask("task.panic", task.Panic) 48 | log.Fatal(exec.Run()) 49 | } 50 | 51 | // 自定义日志处理器 52 | func customLogHandle(req *xxl.LogReq) *xxl.LogRes { 53 | return &xxl.LogRes{Code: xxl.SuccessCode, Msg: "", Content: xxl.LogResContent{ 54 | FromLineNum: req.FromLineNum, 55 | ToLineNum: 2, 56 | LogContent: "这个是自定义日志handler", 57 | IsEnd: true, 58 | }} 59 | } 60 | 61 | // xxl.Logger接口实现 62 | type logger struct{} 63 | 64 | func (l *logger) Info(format string, a ...interface{}) { 65 | fmt.Println(fmt.Sprintf("自定义日志 - "+format, a...)) 66 | } 67 | 68 | func (l *logger) Error(format string, a ...interface{}) { 69 | log.Println(fmt.Sprintf("自定义日志 - "+format, a...)) 70 | } 71 | 72 | // 自定义中间件 73 | func customMiddleware(tf xxl.TaskFunc) xxl.TaskFunc { 74 | return func(cxt context.Context, param *xxl.RunReq) string { 75 | log.Println("I am a middleware start") 76 | res := tf(cxt, param) 77 | log.Println("I am a middleware end") 78 | return res 79 | } 80 | } 81 | 82 | ``` 83 | # 示例项目 84 | github.com/xxl-job/xxl-job-executor-go/example/ 85 | # 与gin框架集成 86 | https://github.com/gin-middleware/xxl-job-executor 87 | # xxl-job-admin配置 88 | ### 添加执行器 89 | 执行器管理->新增执行器,执行器列表如下: 90 | ``` 91 | AppName 名称 注册方式 OnLine 机器地址 操作 92 | golang-jobs golang执行器 自动注册 查看 ( 1 ) 93 | ``` 94 | 查看->注册节点 95 | ``` 96 | http://127.0.0.1:9999 97 | ``` 98 | ### 添加任务 99 | 任务管理->新增(注意,使用BEAN模式,JobHandler与RegTask名称一致) 100 | ``` 101 | 1 测试panic BEAN:task.panic * 0 * * * ? admin STOP 102 | 2 测试耗时任务 BEAN:task.test2 * * * * * ? admin STOP 103 | 3 测试golang BEAN:task.test * * * * * ? admin STOP 104 | ``` 105 | 106 | -------------------------------------------------------------------------------- /dto.go: -------------------------------------------------------------------------------- 1 | package xxl 2 | 3 | //通用响应 4 | type res struct { 5 | Code int64 `json:"code"` // 200 表示正常、其他失败 6 | Msg interface{} `json:"msg"` // 错误提示消息 7 | } 8 | 9 | /***************** 上行参数 *********************/ 10 | 11 | // Registry 注册参数 12 | type Registry struct { 13 | RegistryGroup string `json:"registryGroup"` 14 | RegistryKey string `json:"registryKey"` 15 | RegistryValue string `json:"registryValue"` 16 | } 17 | 18 | //执行器执行完任务后,回调任务结果时使用 19 | type call []*callElement 20 | 21 | type callElement struct { 22 | LogID int64 `json:"logId"` 23 | LogDateTim int64 `json:"logDateTim"` 24 | ExecuteResult *ExecuteResult `json:"executeResult"` 25 | //以下是7.31版本 v2.3.0 Release所使用的字段 26 | HandleCode int `json:"handleCode"` //200表示正常,500表示失败 27 | HandleMsg string `json:"handleMsg"` 28 | } 29 | 30 | // ExecuteResult 任务执行结果 200 表示任务执行正常,500表示失败 31 | type ExecuteResult struct { 32 | Code int64 `json:"code"` 33 | Msg interface{} `json:"msg"` 34 | } 35 | 36 | /***************** 下行参数 *********************/ 37 | 38 | //阻塞处理策略 39 | const ( 40 | serialExecution = "SERIAL_EXECUTION" //单机串行 41 | discardLater = "DISCARD_LATER" //丢弃后续调度 42 | coverEarly = "COVER_EARLY" //覆盖之前调度 43 | ) 44 | 45 | // RunReq 触发任务请求参数 46 | type RunReq struct { 47 | JobID int64 `json:"jobId"` // 任务ID 48 | ExecutorHandler string `json:"executorHandler"` // 任务标识 49 | ExecutorParams string `json:"executorParams"` // 任务参数 50 | ExecutorBlockStrategy string `json:"executorBlockStrategy"` // 任务阻塞策略 51 | ExecutorTimeout int64 `json:"executorTimeout"` // 任务超时时间,单位秒,大于零时生效 52 | LogID int64 `json:"logId"` // 本次调度日志ID 53 | LogDateTime int64 `json:"logDateTime"` // 本次调度日志时间 54 | GlueType string `json:"glueType"` // 任务模式,可选值参考 com.xxl.job.core.glue.GlueTypeEnum 55 | GlueSource string `json:"glueSource"` // GLUE脚本代码 56 | GlueUpdatetime int64 `json:"glueUpdatetime"` // GLUE脚本更新时间,用于判定脚本是否变更以及是否需要刷新 57 | BroadcastIndex int64 `json:"broadcastIndex"` // 分片参数:当前分片 58 | BroadcastTotal int64 `json:"broadcastTotal"` // 分片参数:总分片 59 | } 60 | 61 | //终止任务请求参数 62 | type killReq struct { 63 | JobID int64 `json:"jobId"` // 任务ID 64 | } 65 | 66 | //忙碌检测请求参数 67 | type idleBeatReq struct { 68 | JobID int64 `json:"jobId"` // 任务ID 69 | } 70 | 71 | // LogReq 日志请求 72 | type LogReq struct { 73 | LogDateTim int64 `json:"logDateTim"` // 本次调度日志时间 74 | LogID int64 `json:"logId"` // 本次调度日志ID 75 | FromLineNum int `json:"fromLineNum"` // 日志开始行号,滚动加载日志 76 | } 77 | 78 | // LogRes 日志响应 79 | type LogRes struct { 80 | Code int64 `json:"code"` // 200 表示正常、其他失败 81 | Msg string `json:"msg"` // 错误提示消息 82 | Content LogResContent `json:"content"` // 日志响应内容 83 | } 84 | 85 | // LogResContent 日志响应内容 86 | type LogResContent struct { 87 | FromLineNum int `json:"fromLineNum"` // 本次请求,日志开始行数 88 | ToLineNum int `json:"toLineNum"` // 本次请求,日志结束行号 89 | LogContent string `json:"logContent"` // 本次请求日志内容 90 | IsEnd bool `json:"isEnd"` // 日志是否全部加载完 91 | } 92 | -------------------------------------------------------------------------------- /executor.go: -------------------------------------------------------------------------------- 1 | package xxl 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "strings" 12 | "sync" 13 | "syscall" 14 | "time" 15 | ) 16 | 17 | // Executor 执行器 18 | type Executor interface { 19 | // Init 初始化 20 | Init(...Option) 21 | // LogHandler 日志查询 22 | LogHandler(handler LogHandler) 23 | // Use 使用中间件 24 | Use(middlewares ...Middleware) 25 | // RegTask 注册任务 26 | RegTask(pattern string, task TaskFunc) 27 | // RunTask 运行任务 28 | RunTask(writer http.ResponseWriter, request *http.Request) 29 | // KillTask 杀死任务 30 | KillTask(writer http.ResponseWriter, request *http.Request) 31 | // TaskLog 任务日志 32 | TaskLog(writer http.ResponseWriter, request *http.Request) 33 | // Beat 心跳检测 34 | Beat(writer http.ResponseWriter, request *http.Request) 35 | // IdleBeat 忙碌检测 36 | IdleBeat(writer http.ResponseWriter, request *http.Request) 37 | // Run 运行服务 38 | Run() error 39 | // Stop 停止服务 40 | Stop() 41 | } 42 | 43 | // NewExecutor 创建执行器 44 | func NewExecutor(opts ...Option) Executor { 45 | return newExecutor(opts...) 46 | } 47 | 48 | func newExecutor(opts ...Option) *executor { 49 | options := newOptions(opts...) 50 | e := &executor{ 51 | opts: options, 52 | } 53 | return e 54 | } 55 | 56 | type executor struct { 57 | opts Options 58 | address string 59 | regList *taskList //注册任务列表 60 | runList *taskList //正在执行任务列表 61 | mu sync.RWMutex 62 | log Logger 63 | 64 | logHandler LogHandler //日志查询handler 65 | middlewares []Middleware //中间件 66 | } 67 | 68 | func (e *executor) Init(opts ...Option) { 69 | for _, o := range opts { 70 | o(&e.opts) 71 | } 72 | e.log = e.opts.l 73 | e.regList = &taskList{ 74 | data: make(map[string]*Task), 75 | } 76 | e.runList = &taskList{ 77 | data: make(map[string]*Task), 78 | } 79 | e.address = e.opts.ExecutorIp + ":" + e.opts.ExecutorPort 80 | go e.registry() 81 | } 82 | 83 | // LogHandler 日志handler 84 | func (e *executor) LogHandler(handler LogHandler) { 85 | e.logHandler = handler 86 | } 87 | 88 | func (e *executor) Use(middlewares ...Middleware) { 89 | e.middlewares = middlewares 90 | } 91 | 92 | func (e *executor) Run() (err error) { 93 | // 创建路由器 94 | mux := http.NewServeMux() 95 | // 设置路由规则 96 | mux.HandleFunc("/run", e.runTask) 97 | mux.HandleFunc("/kill", e.killTask) 98 | mux.HandleFunc("/log", e.taskLog) 99 | mux.HandleFunc("/beat", e.beat) 100 | mux.HandleFunc("/idleBeat", e.idleBeat) 101 | // 创建服务器 102 | server := &http.Server{ 103 | Addr: ":" + e.opts.ExecutorPort, 104 | WriteTimeout: time.Second * 3, 105 | Handler: mux, 106 | } 107 | // 监听端口并提供服务 108 | e.log.Info("Starting server at " + e.address) 109 | go server.ListenAndServe() 110 | quit := make(chan os.Signal) 111 | signal.Notify(quit, syscall.SIGKILL, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGTERM) 112 | <-quit 113 | e.registryRemove() 114 | return nil 115 | } 116 | 117 | func (e *executor) Stop() { 118 | e.registryRemove() 119 | } 120 | 121 | // RegTask 注册任务 122 | func (e *executor) RegTask(pattern string, task TaskFunc) { 123 | var t = &Task{} 124 | t.fn = e.chain(task) 125 | e.regList.Set(pattern, t) 126 | return 127 | } 128 | 129 | // 运行一个任务 130 | func (e *executor) runTask(writer http.ResponseWriter, request *http.Request) { 131 | e.mu.Lock() 132 | defer e.mu.Unlock() 133 | 134 | req, _ := ioutil.ReadAll(request.Body) 135 | param := &RunReq{} 136 | err := json.Unmarshal(req, ¶m) 137 | if err != nil { 138 | _, _ = writer.Write(returnCall(param, FailureCode, "params err")) 139 | e.log.Error("参数解析错误:" + string(req)) 140 | return 141 | } 142 | e.log.Info("任务参数:%v", param) 143 | if !e.regList.Exists(param.ExecutorHandler) { 144 | _, _ = writer.Write(returnCall(param, FailureCode, "Task not registered")) 145 | e.log.Error("任务[" + Int64ToStr(param.JobID) + "]没有注册:" + param.ExecutorHandler) 146 | return 147 | } 148 | 149 | //阻塞策略处理 150 | if e.runList.Exists(Int64ToStr(param.JobID)) { 151 | if param.ExecutorBlockStrategy == coverEarly { //覆盖之前调度 152 | oldTask := e.runList.Get(Int64ToStr(param.JobID)) 153 | if oldTask != nil { 154 | oldTask.Cancel() 155 | e.runList.Del(Int64ToStr(oldTask.Id)) 156 | } 157 | } else { //单机串行,丢弃后续调度 都进行阻塞 158 | _, _ = writer.Write(returnCall(param, FailureCode, "There are tasks running")) 159 | e.log.Error("任务[" + Int64ToStr(param.JobID) + "]已经在运行了:" + param.ExecutorHandler) 160 | return 161 | } 162 | } 163 | 164 | cxt := context.Background() 165 | task := e.regList.Get(param.ExecutorHandler) 166 | if param.ExecutorTimeout > 0 { 167 | task.Ext, task.Cancel = context.WithTimeout(cxt, time.Duration(param.ExecutorTimeout)*time.Second) 168 | } else { 169 | task.Ext, task.Cancel = context.WithCancel(cxt) 170 | } 171 | task.Id = param.JobID 172 | task.Name = param.ExecutorHandler 173 | task.Param = param 174 | task.log = e.log 175 | 176 | e.runList.Set(Int64ToStr(task.Id), task) 177 | go task.Run(func(code int64, msg string) { 178 | e.callback(task, code, msg) 179 | }) 180 | e.log.Info("任务[" + Int64ToStr(param.JobID) + "]开始执行:" + param.ExecutorHandler) 181 | _, _ = writer.Write(returnGeneral()) 182 | } 183 | 184 | // 删除一个任务 185 | func (e *executor) killTask(writer http.ResponseWriter, request *http.Request) { 186 | e.mu.Lock() 187 | defer e.mu.Unlock() 188 | req, _ := ioutil.ReadAll(request.Body) 189 | param := &killReq{} 190 | _ = json.Unmarshal(req, ¶m) 191 | if !e.runList.Exists(Int64ToStr(param.JobID)) { 192 | _, _ = writer.Write(returnKill(param, FailureCode)) 193 | e.log.Error("任务[" + Int64ToStr(param.JobID) + "]没有运行") 194 | return 195 | } 196 | task := e.runList.Get(Int64ToStr(param.JobID)) 197 | task.Cancel() 198 | e.runList.Del(Int64ToStr(param.JobID)) 199 | _, _ = writer.Write(returnGeneral()) 200 | } 201 | 202 | // 任务日志 203 | func (e *executor) taskLog(writer http.ResponseWriter, request *http.Request) { 204 | var res *LogRes 205 | data, err := ioutil.ReadAll(request.Body) 206 | req := &LogReq{} 207 | if err != nil { 208 | e.log.Error("日志请求失败:" + err.Error()) 209 | reqErrLogHandler(writer, req, err) 210 | return 211 | } 212 | err = json.Unmarshal(data, &req) 213 | if err != nil { 214 | e.log.Error("日志请求解析失败:" + err.Error()) 215 | reqErrLogHandler(writer, req, err) 216 | return 217 | } 218 | e.log.Info("日志请求参数:%+v", req) 219 | if e.logHandler != nil { 220 | res = e.logHandler(req) 221 | } else { 222 | res = defaultLogHandler(req) 223 | } 224 | str, _ := json.Marshal(res) 225 | _, _ = writer.Write(str) 226 | } 227 | 228 | // 心跳检测 229 | func (e *executor) beat(writer http.ResponseWriter, request *http.Request) { 230 | e.log.Info("心跳检测") 231 | _, _ = writer.Write(returnGeneral()) 232 | } 233 | 234 | // 忙碌检测 235 | func (e *executor) idleBeat(writer http.ResponseWriter, request *http.Request) { 236 | e.mu.Lock() 237 | defer e.mu.Unlock() 238 | defer request.Body.Close() 239 | req, _ := ioutil.ReadAll(request.Body) 240 | param := &idleBeatReq{} 241 | err := json.Unmarshal(req, ¶m) 242 | if err != nil { 243 | _, _ = writer.Write(returnIdleBeat(FailureCode)) 244 | e.log.Error("参数解析错误:" + string(req)) 245 | return 246 | } 247 | if e.runList.Exists(Int64ToStr(param.JobID)) { 248 | _, _ = writer.Write(returnIdleBeat(FailureCode)) 249 | e.log.Error("idleBeat任务[" + Int64ToStr(param.JobID) + "]正在运行") 250 | return 251 | } 252 | e.log.Info("忙碌检测任务参数:%v", param) 253 | _, _ = writer.Write(returnGeneral()) 254 | } 255 | 256 | // 注册执行器到调度中心 257 | func (e *executor) registry() { 258 | 259 | t := time.NewTimer(time.Second * 0) //初始立即执行 260 | defer t.Stop() 261 | req := &Registry{ 262 | RegistryGroup: "EXECUTOR", 263 | RegistryKey: e.opts.RegistryKey, 264 | RegistryValue: "http://" + e.address, 265 | } 266 | param, err := json.Marshal(req) 267 | if err != nil { 268 | log.Fatal("执行器注册信息解析失败:" + err.Error()) 269 | } 270 | for { 271 | <-t.C 272 | t.Reset(time.Second * time.Duration(20)) //20秒心跳防止过期 273 | func() { 274 | result, err := e.post("/api/registry", string(param)) 275 | if err != nil { 276 | e.log.Error("执行器注册失败1:" + err.Error()) 277 | return 278 | } 279 | defer result.Body.Close() 280 | body, err := ioutil.ReadAll(result.Body) 281 | if err != nil { 282 | e.log.Error("执行器注册失败2:" + err.Error()) 283 | return 284 | } 285 | res := &res{} 286 | _ = json.Unmarshal(body, &res) 287 | if res.Code != SuccessCode { 288 | e.log.Error("执行器注册失败3:" + string(body)) 289 | return 290 | } 291 | e.log.Info("执行器注册成功:" + string(body)) 292 | }() 293 | 294 | } 295 | } 296 | 297 | // 执行器注册摘除 298 | func (e *executor) registryRemove() { 299 | t := time.NewTimer(time.Second * 0) //初始立即执行 300 | defer t.Stop() 301 | req := &Registry{ 302 | RegistryGroup: "EXECUTOR", 303 | RegistryKey: e.opts.RegistryKey, 304 | RegistryValue: "http://" + e.address, 305 | } 306 | param, err := json.Marshal(req) 307 | if err != nil { 308 | e.log.Error("执行器摘除失败:" + err.Error()) 309 | return 310 | } 311 | res, err := e.post("/api/registryRemove", string(param)) 312 | if err != nil { 313 | e.log.Error("执行器摘除失败:" + err.Error()) 314 | return 315 | } 316 | defer res.Body.Close() 317 | body, err := ioutil.ReadAll(res.Body) 318 | e.log.Info("执行器摘除成功:" + string(body)) 319 | } 320 | 321 | // 回调任务列表 322 | func (e *executor) callback(task *Task, code int64, msg string) { 323 | e.runList.Del(Int64ToStr(task.Id)) 324 | res, err := e.post("/api/callback", string(returnCall(task.Param, code, msg))) 325 | if err != nil { 326 | e.log.Error("callback err : ", err.Error()) 327 | return 328 | } 329 | defer res.Body.Close() 330 | body, err := ioutil.ReadAll(res.Body) 331 | if err != nil { 332 | e.log.Error("callback ReadAll err : ", err.Error()) 333 | return 334 | } 335 | e.log.Info("任务回调成功:" + string(body)) 336 | } 337 | 338 | // post 339 | func (e *executor) post(action, body string) (resp *http.Response, err error) { 340 | request, err := http.NewRequest("POST", e.opts.ServerAddr+action, strings.NewReader(body)) 341 | if err != nil { 342 | return nil, err 343 | } 344 | request.Header.Set("Content-Type", "application/json;charset=UTF-8") 345 | request.Header.Set("XXL-JOB-ACCESS-TOKEN", e.opts.AccessToken) 346 | client := http.Client{ 347 | Timeout: e.opts.Timeout, 348 | } 349 | return client.Do(request) 350 | } 351 | 352 | // RunTask 运行任务 353 | func (e *executor) RunTask(writer http.ResponseWriter, request *http.Request) { 354 | e.runTask(writer, request) 355 | } 356 | 357 | // KillTask 删除任务 358 | func (e *executor) KillTask(writer http.ResponseWriter, request *http.Request) { 359 | e.killTask(writer, request) 360 | } 361 | 362 | // TaskLog 任务日志 363 | func (e *executor) TaskLog(writer http.ResponseWriter, request *http.Request) { 364 | e.taskLog(writer, request) 365 | } 366 | 367 | // Beat 心跳检测 368 | func (e *executor) Beat(writer http.ResponseWriter, request *http.Request) { 369 | e.beat(writer, request) 370 | } 371 | 372 | // IdleBeat 忙碌检测 373 | func (e *executor) IdleBeat(writer http.ResponseWriter, request *http.Request) { 374 | e.idleBeat(writer, request) 375 | } 376 | --------------------------------------------------------------------------------