├── .gitignore ├── LICENSE ├── actuator ├── docker_actuator.go └── function_actuator.go ├── callback_receiver.go ├── container ├── combination_container.go ├── memory_container │ ├── memory_container.go │ ├── orderedmap_container.go │ ├── queue_container.go │ └── redis_container.go └── persist_container │ └── persist_container.go ├── example ├── add_example │ ├── add │ │ └── add_task.go │ └── main.go ├── docker_example │ └── main.go └── videocut_example │ ├── main_demo │ └── main.go │ ├── main_web │ └── main.go │ └── video_cut │ ├── example_sql_container.go │ ├── html_document.go │ ├── video_cut_actuator.go │ ├── video_cut_service.go │ ├── video_cut_task.go │ └── web.go ├── go.mod ├── go.sum ├── readme.md ├── task.go ├── task_actuator.go ├── task_container.go ├── task_scheduler.go └── taskdata_persistencer.go /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 zhengaohong 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 | -------------------------------------------------------------------------------- /actuator/docker_actuator.go: -------------------------------------------------------------------------------- 1 | // 封装好的 docker 执行器 2 | 3 | package actuator 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "sync" 10 | "time" 11 | 12 | "github.com/docker/docker/api/types" 13 | "github.com/docker/docker/api/types/container" 14 | "github.com/docker/docker/api/types/mount" 15 | dockerclient "github.com/docker/docker/client" 16 | "github.com/docker/go-connections/nat" 17 | framework "github.com/memory-overflow/light-task-scheduler" 18 | ) 19 | 20 | // DockerTask docker 任务 21 | type DockerTask struct { 22 | Image string // 镜像 23 | Cmd []string // 容器执行命令 24 | ContainerName string // 容器名,不能重复 25 | 26 | // 资源限制 27 | MemoryLimit int64 // bytes 28 | CpuPercent int // cpu 占用百分比, 比如暂用 2 核就是 200 29 | 30 | // 可选配置 31 | ExposedPorts []string // 暴露端口 32 | Env []string // 环境变量 33 | WorkingDir string // 工作目录 34 | NetworkDisabled bool // 是否关闭网络 35 | NetworkMode string // 网络模式 36 | Privileged bool // Is the container in privileged mode 37 | VolumeBinds map[string]string // host 路径到容器路径的映射,类似于 -v 参数 38 | 39 | ContainerId string // 执行后对应的 容器 Id 40 | } 41 | 42 | // dockerActuator docker 执行器 43 | type dockerActuator struct { 44 | initFunc InitFunction // 初始函数 45 | callbackChannel chan framework.Task // 回调队列 46 | 47 | runningTask sync.Map // ContainerName -> containerId 映射 48 | datatMap sync.Map // ContainerName -> interface{} 映射 49 | } 50 | 51 | func MakeDockerActuator(initFunc InitFunction) *dockerActuator { 52 | return &dockerActuator{ 53 | initFunc: initFunc, 54 | } 55 | } 56 | 57 | // SetCallbackChannel 任务配置回调 channel 58 | func (dc *dockerActuator) SetCallbackChannel(callbackChannel chan framework.Task) { 59 | dc.callbackChannel = callbackChannel 60 | } 61 | 62 | // Init 任务在被调度前的初始化工作 63 | func (dc *dockerActuator) Init(ctx context.Context, task *framework.Task) ( 64 | newTask *framework.Task, err error) { 65 | if dc.initFunc != nil { 66 | return dc.initFunc(ctx, task) 67 | } 68 | return task, nil 69 | } 70 | 71 | // Start 执行任务 72 | func (dc *dockerActuator) Start(ctx context.Context, ftask *framework.Task) ( 73 | newTask *framework.Task, ignoreErr bool, err error) { 74 | var task DockerTask 75 | if v, ok := ftask.TaskItem.(DockerTask); ok { 76 | task = v 77 | } else if v, ok := ftask.TaskItem.(*DockerTask); ok { 78 | task = *v 79 | } else { 80 | return ftask, false, fmt.Errorf("TaskItem is not configured as DockerTask") 81 | } 82 | cli, err := dockerclient.NewClientWithOpts(dockerclient.WithAPIVersionNegotiation()) 83 | if err != nil { 84 | return ftask, false, fmt.Errorf("new docker client with error: %v", err) 85 | } 86 | exposed := nat.PortSet{} 87 | for _, port := range task.ExposedPorts { 88 | exposed[nat.Port(port)] = struct{}{} 89 | } 90 | mounts := []mount.Mount{} 91 | for src, dst := range task.VolumeBinds { 92 | mounts = append(mounts, mount.Mount{ 93 | Type: "bind", 94 | Source: src, 95 | Target: dst, 96 | }) 97 | } 98 | resp, err := cli.ContainerCreate(ctx, 99 | &container.Config{ 100 | Image: task.Image, 101 | Cmd: task.Cmd, 102 | Env: task.Env, 103 | WorkingDir: task.WorkingDir, 104 | ExposedPorts: exposed, 105 | }, 106 | &container.HostConfig{ 107 | AutoRemove: true, 108 | Mounts: mounts, 109 | Resources: container.Resources{ 110 | Memory: task.MemoryLimit, 111 | CPUPeriod: 100000, 112 | CPUQuota: int64(task.CpuPercent) * 100000 / 100, 113 | }, 114 | }, 115 | nil, nil, task.ContainerName) 116 | if err != nil { 117 | return ftask, false, fmt.Errorf("create container error: %v", err) 118 | } 119 | 120 | // 启动容器 121 | if err := cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil { 122 | return ftask, false, fmt.Errorf("start container error: %v", err) 123 | } 124 | 125 | ftask.TaskStatus = framework.TASK_STATUS_RUNNING 126 | ftask.TaskStartTime = time.Now() 127 | task.ContainerId = resp.ID 128 | ftask.TaskItem = task 129 | dc.runningTask.Store(task.ContainerName, resp.ID) 130 | 131 | return ftask, false, nil 132 | } 133 | 134 | // Stop 停止任务 135 | func (dc *dockerActuator) Stop(ctx context.Context, ftask *framework.Task) error { 136 | var task DockerTask 137 | if v, ok := ftask.TaskItem.(DockerTask); ok { 138 | task = v 139 | } else if v, ok := ftask.TaskItem.(*DockerTask); ok { 140 | task = *v 141 | } else { 142 | return fmt.Errorf("TaskItem is not configured as DockerTask") 143 | } 144 | 145 | cli, err := dockerclient.NewClientWithOpts(dockerclient.WithAPIVersionNegotiation()) 146 | if err != nil { 147 | return fmt.Errorf("new docker client error: %v", err) 148 | } 149 | if v, ok := dc.runningTask.Load(task.ContainerName); ok { 150 | id := v.(string) 151 | err := cli.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true}) 152 | if err != nil { 153 | return fmt.Errorf("remove container with id %s error: %v", id, err) 154 | } 155 | dc.runningTask.Delete(task.ContainerName) 156 | } 157 | 158 | return nil 159 | } 160 | 161 | // GetAsyncTaskStatus 批量获取任务状态 162 | func (fc *dockerActuator) GetAsyncTaskStatus(ctx context.Context, ftasks []framework.Task) ( 163 | status []framework.AsyncTaskStatus, err error) { 164 | for i, ftask := range ftasks { 165 | var task DockerTask 166 | if v, ok := ftask.TaskItem.(DockerTask); ok { 167 | task = v 168 | } else if v, ok := ftask.TaskItem.(*DockerTask); ok { 169 | task = *v 170 | } else { 171 | return nil, fmt.Errorf("tasks[%d].TaskItem is not configured as DockerTask", i) 172 | } 173 | cli, err := dockerclient.NewClientWithOpts(dockerclient.WithAPIVersionNegotiation()) 174 | if err != nil { 175 | return nil, fmt.Errorf("new docker client error: %v", err) 176 | } 177 | 178 | v, ok := fc.runningTask.Load(task.ContainerName) 179 | if !ok { 180 | status = append(status, framework.AsyncTaskStatus{ 181 | TaskStatus: framework.TASK_STATUS_FAILED, 182 | FailedReason: errors.New("同步任务未找到"), 183 | Progress: float32(0.0), 184 | }) 185 | } else { 186 | id := v.(string) 187 | stats, err := cli.ContainerInspect(ctx, id) 188 | if err != nil { 189 | return nil, fmt.Errorf("get container stats for id %s error: %v", id, err) 190 | } 191 | st := framework.AsyncTaskStatus{ 192 | TaskStatus: framework.TASK_STATUS_RUNNING, 193 | Progress: float32(0.0), 194 | } 195 | if stats.State.Status != "running" { 196 | if stats.State.ExitCode == 0 { 197 | st.TaskStatus = framework.TASK_STATUS_SUCCESS 198 | } else { 199 | st.TaskStatus = framework.TASK_STATUS_FAILED 200 | st.FailedReason = fmt.Errorf(stats.State.Error) 201 | } 202 | } 203 | if st.TaskStatus != framework.TASK_STATUS_RUNNING { 204 | fc.runningTask.Delete(ftask.TaskId) // delete task status after query if task finished 205 | cli.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true}) 206 | } 207 | status = append(status, st) 208 | } 209 | } 210 | return status, nil 211 | } 212 | 213 | // GetOutput ... 214 | func (dc *dockerActuator) GetOutput(ctx context.Context, ftask *framework.Task) ( 215 | data interface{}, err error) { 216 | var task DockerTask 217 | if v, ok := ftask.TaskItem.(DockerTask); ok { 218 | task = v 219 | } else if v, ok := ftask.TaskItem.(*DockerTask); ok { 220 | task = *v 221 | } else { 222 | return nil, fmt.Errorf("TaskItem is not configured as DockerTask") 223 | } 224 | if v, ok := dc.datatMap.LoadAndDelete(task.ContainerName); ok { 225 | return v, nil 226 | } else { 227 | return nil, fmt.Errorf("task data not found") 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /actuator/function_actuator.go: -------------------------------------------------------------------------------- 1 | // 封装好的函数执行器 2 | 3 | package actuator 4 | 5 | import ( 6 | "context" 7 | "errors" 8 | "fmt" 9 | "runtime/debug" 10 | "sync" 11 | "time" 12 | 13 | framework "github.com/memory-overflow/light-task-scheduler" 14 | ) 15 | 16 | // fucntionActuator 函数执行器,同步任务异步化的示例 17 | type fucntionActuator struct { 18 | runFunc RunFunction // 执行函数 19 | initFunc InitFunction // 初始函数 20 | callbackChannel chan framework.Task // 回调队列 21 | 22 | runningTask sync.Map // taskId -> [framework.AsyncTaskStatus, cancel function] 映射 23 | datatMap sync.Map // taskId -> interface{} 映射 24 | 25 | } 26 | 27 | // RunFunction 执行函数的定义,实现该函数的时候,建议使用传入 ctx 进行超时和退出处理 28 | // data 任务执行完成返回的数据 29 | type RunFunction func(ctx context.Context, task *framework.Task) (data interface{}, err error) 30 | 31 | // InitFunction 任务执行前的初始工作 32 | type InitFunction func(ctx context.Context, task *framework.Task) (newTask *framework.Task, err error) 33 | 34 | // runFunc 待调度的执行函数,注意实现该函数的时候,需要使用传入 ctx 进行超时和退出处理,框架否则无法控制超时时间 35 | // callbackChannel 用来执行器进行任务回调,返回已经完成的任务,如果不需要回调,传入 nil 即可 36 | func MakeFucntionActuator(runFunc RunFunction, initFunc InitFunction) (*fucntionActuator, error) { 37 | if runFunc == nil { 38 | return nil, fmt.Errorf("runFunc is nil") 39 | } 40 | return &fucntionActuator{ 41 | runFunc: runFunc, 42 | initFunc: initFunc, 43 | }, nil 44 | } 45 | 46 | // SetCallbackChannel 任务配置回调 channel 47 | func (fc *fucntionActuator) SetCallbackChannel(callbackChannel chan framework.Task) { 48 | fc.callbackChannel = callbackChannel 49 | } 50 | 51 | // Init 任务在被调度前的初始化工作 52 | func (fc *fucntionActuator) Init(ctx context.Context, task *framework.Task) ( 53 | newTask *framework.Task, err error) { 54 | if fc.initFunc != nil { 55 | return fc.initFunc(ctx, task) 56 | } 57 | return task, nil 58 | } 59 | 60 | // Start 执行任务 61 | func (fc *fucntionActuator) Start(ctx context.Context, ftask *framework.Task) ( 62 | newTask *framework.Task, ignoreErr bool, err error) { 63 | if st, ok := fc.runningTask.Load(ftask.TaskId); ok { 64 | status := st.([]interface{})[0].(framework.AsyncTaskStatus).TaskStatus 65 | if status == framework.TASK_STATUS_RUNNING || status == framework.TASK_STATUS_SUCCESS { 66 | // 任务已经在执行中,不能重复执行 67 | return ftask, false, fmt.Errorf("task %s is running", ftask.TaskId) 68 | } 69 | } 70 | runCtx, cancel := context.WithCancel(ctx) 71 | 72 | ftask.TaskStatus = framework.TASK_STATUS_RUNNING 73 | ftask.TaskStartTime = time.Now() 74 | fc.datatMap.Delete(ftask.TaskId) 75 | fc.runningTask.Store(ftask.TaskId, 76 | []interface{}{ 77 | framework.AsyncTaskStatus{ 78 | TaskStatus: framework.TASK_STATUS_RUNNING, 79 | Progress: 0.0, 80 | }, cancel}) 81 | 82 | go func() { 83 | data, err := func() (data interface{}, err error) { 84 | defer func() { 85 | if p := recover(); p != nil { 86 | data = nil 87 | 88 | err = fmt.Errorf("panic: %v, stacktrace: %s", p, debug.Stack()) 89 | } 90 | }() 91 | return fc.runFunc(runCtx, ftask) 92 | }() 93 | st, ok := fc.runningTask.Load(ftask.TaskId) 94 | if !ok { 95 | // 任务可能因为超时被删除,或者手动暂停、不处理 96 | return 97 | } 98 | if st.([]interface{})[1] != nil { 99 | (st.([]interface{})[1].(context.CancelFunc))() // 任务执行完,也要执行对应的 cancel 函数 100 | } 101 | var newStatus framework.AsyncTaskStatus 102 | if err != nil { 103 | newStatus = framework.AsyncTaskStatus{ 104 | TaskStatus: framework.TASK_STATUS_FAILED, 105 | Progress: 0.0, 106 | FailedReason: err, 107 | } 108 | } else { 109 | newStatus = framework.AsyncTaskStatus{ 110 | TaskStatus: framework.TASK_STATUS_SUCCESS, 111 | Progress: 100.0, 112 | } 113 | fc.datatMap.Store(ftask.TaskId, data) // 先存结果 114 | } 115 | fc.runningTask.Store(ftask.TaskId, []interface{}{newStatus, nil}) 116 | if fc.callbackChannel != nil { 117 | // 如果需要回调 118 | callbackTask := *ftask 119 | callbackTask.TaskStatus = newStatus.TaskStatus 120 | callbackTask.TaskEnbTime = time.Now() 121 | if newStatus.FailedReason != nil { 122 | callbackTask.FailedReason = newStatus.FailedReason.Error() 123 | } 124 | fc.callbackChannel <- callbackTask 125 | } 126 | }() 127 | 128 | return ftask, false, nil 129 | } 130 | 131 | func (fc *fucntionActuator) clear(taskId string) { 132 | fc.runningTask.Delete(taskId) 133 | fc.datatMap.Delete(taskId) 134 | } 135 | 136 | // Stop 停止任务 137 | func (fc *fucntionActuator) Stop(ctx context.Context, ftask *framework.Task) error { 138 | st, ok := fc.runningTask.Load(ftask.TaskId) 139 | if !ok { 140 | // 未找到任务 141 | fc.datatMap.Delete(ftask.TaskId) 142 | return nil 143 | } 144 | fc.clear(ftask.TaskId) 145 | if st.([]interface{})[1] != nil { 146 | (st.([]interface{})[1].(context.CancelFunc))() // cancel 函数 ctx 147 | } 148 | return nil 149 | } 150 | 151 | // GetAsyncTaskStatus 批量获取任务状态 152 | func (fc *fucntionActuator) GetAsyncTaskStatus(ctx context.Context, ftasks []framework.Task) ( 153 | status []framework.AsyncTaskStatus, err error) { 154 | for _, ftask := range ftasks { 155 | fstatus, ok := fc.runningTask.Load(ftask.TaskId) 156 | if !ok { 157 | status = append(status, framework.AsyncTaskStatus{ 158 | TaskStatus: framework.TASK_STATUS_FAILED, 159 | FailedReason: errors.New("同步任务未找到"), 160 | Progress: float32(0.0), 161 | }) 162 | } else { 163 | st := fstatus.([]interface{})[0].(framework.AsyncTaskStatus) 164 | if st.TaskStatus != framework.TASK_STATUS_RUNNING { 165 | fc.runningTask.Delete(ftask.TaskId) // delete task status after query if task finished 166 | } 167 | status = append(status, st) 168 | } 169 | } 170 | return status, nil 171 | } 172 | 173 | // GetOutput ... 174 | func (fc *fucntionActuator) GetOutput(ctx context.Context, ftask *framework.Task) ( 175 | data interface{}, err error) { 176 | res, ok := fc.datatMap.Load(ftask.TaskId) 177 | if !ok { 178 | return nil, fmt.Errorf("not found result for task %s", ftask.TaskId) 179 | } 180 | fc.clear(ftask.TaskId) 181 | return res, nil 182 | } 183 | -------------------------------------------------------------------------------- /callback_receiver.go: -------------------------------------------------------------------------------- 1 | package lighttaskscheduler 2 | 3 | import "context" 4 | 5 | // CallbackReceiver 任务状态回调接收器 6 | type CallbackReceiver interface { 7 | GetCallbackChannel(ctx context.Context) (taskChannel chan Task) 8 | } 9 | -------------------------------------------------------------------------------- /container/combination_container.go: -------------------------------------------------------------------------------- 1 | package container 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | lighttaskscheduler "github.com/memory-overflow/light-task-scheduler" 8 | memeorycontainer "github.com/memory-overflow/light-task-scheduler/container/memory_container" 9 | persistcontainer "github.com/memory-overflow/light-task-scheduler/container/persist_container" 10 | ) 11 | 12 | // combinationContainer 组合 MemeoryContainer 和 Persistcontainer 的容器 13 | // 整合两种容器的优点,既能够通过内存实现快写快读,又能够通过DB实现可持久化 14 | type combinationContainer struct { 15 | memeoryContainer memeorycontainer.MemeoryContainer 16 | persistContainer persistcontainer.PersistContainer 17 | } 18 | 19 | // MakeCombinationContainer 构造队列型任务容器 20 | func MakeCombinationContainer(memeoryContainer memeorycontainer.MemeoryContainer, 21 | persistContainer persistcontainer.PersistContainer) *combinationContainer { 22 | com := &combinationContainer{ 23 | memeoryContainer: memeoryContainer, 24 | persistContainer: persistContainer, 25 | } 26 | ctx := context.Background() 27 | if tasks, err := persistContainer.GetRunningTask(ctx); err == nil { 28 | for _, t := range tasks { 29 | memeoryContainer.AddRunningTask(ctx, t) 30 | } 31 | } 32 | for { 33 | batchSize := 1000 34 | if tasks, err := persistContainer.GetWaitingTask(ctx, int32(batchSize)); err == nil { 35 | for _, t := range tasks { 36 | memeoryContainer.AddTask(ctx, t) 37 | } 38 | if len(tasks) < batchSize { 39 | break 40 | } 41 | } else { 42 | break 43 | } 44 | } 45 | return com 46 | } 47 | 48 | // AddTask 添加任务 49 | func (c *combinationContainer) AddTask(ctx context.Context, task lighttaskscheduler.Task) (err error) { 50 | if err = c.memeoryContainer.AddTask(ctx, task); err != nil { 51 | return fmt.Errorf("memeoryContainer AddTask error: %v", err) 52 | } 53 | defer func() { 54 | if err != nil { 55 | c.memeoryContainer.ToDeleteStatus(ctx, &task) 56 | } 57 | }() 58 | if err = c.persistContainer.AddTask(ctx, task); err != nil { 59 | return fmt.Errorf("persistContainer AddTask error: %v", err) 60 | } 61 | return nil 62 | } 63 | 64 | // GetRunningTask 获取运行中的任务 65 | func (c *combinationContainer) GetRunningTask(ctx context.Context) (tasks []lighttaskscheduler.Task, err error) { 66 | return c.memeoryContainer.GetRunningTask(ctx) 67 | } 68 | 69 | // GetRunningTaskCount 获取运行中的任务数 70 | func (c *combinationContainer) GetRunningTaskCount(ctx context.Context) (count int32, err error) { 71 | return c.memeoryContainer.GetRunningTaskCount(ctx) 72 | } 73 | 74 | // GetWaitingTask 获取等待中的任务 75 | func (c *combinationContainer) GetWaitingTask(ctx context.Context, limit int32) (tasks []lighttaskscheduler.Task, err error) { 76 | return c.memeoryContainer.GetWaitingTask(ctx, limit) 77 | } 78 | 79 | // ToRunningStatus 转移到运行中的状态 80 | func (c *combinationContainer) ToRunningStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 81 | newTask *lighttaskscheduler.Task, err error) { 82 | if newTask, err = c.persistContainer.ToRunningStatus(ctx, task); err != nil { 83 | return newTask, fmt.Errorf("persistContainer ToRunningStatus error: %v", err) 84 | } 85 | if newTask, err = c.memeoryContainer.ToRunningStatus(ctx, task); err != nil { 86 | return newTask, fmt.Errorf("memeoryContainer ToRunningStatus error: %v", err) 87 | } 88 | return newTask, nil 89 | } 90 | 91 | // ToExportStatus 转移到停止状态 92 | func (c *combinationContainer) ToStopStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 93 | newTask *lighttaskscheduler.Task, err error) { 94 | 95 | if newTask, err = c.persistContainer.ToStopStatus(ctx, task); err != nil { 96 | return newTask, fmt.Errorf("persistContainer ToStopStatus error: %v", err) 97 | } 98 | if newTask, err = c.memeoryContainer.ToStopStatus(ctx, task); err != nil { 99 | return newTask, fmt.Errorf("memeoryContainer ToRunningStatus error: %v", err) 100 | } 101 | return newTask, nil 102 | } 103 | 104 | // ToExportStatus 转移到删除状态 105 | func (c *combinationContainer) ToDeleteStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 106 | newTask *lighttaskscheduler.Task, err error) { 107 | if newTask, err = c.persistContainer.ToDeleteStatus(ctx, task); err != nil { 108 | return newTask, fmt.Errorf("persistContainer ToStopStatus error: %v", err) 109 | } 110 | if newTask, err = c.memeoryContainer.ToDeleteStatus(ctx, task); err != nil { 111 | return newTask, fmt.Errorf("memeoryContainer ToRunningStatus error: %v", err) 112 | } 113 | return newTask, nil 114 | } 115 | 116 | // ToFailedStatus 转移到失败状态 117 | func (c *combinationContainer) ToFailedStatus(ctx context.Context, task *lighttaskscheduler.Task, reason error) ( 118 | newTask *lighttaskscheduler.Task, err error) { 119 | if newTask, err = c.persistContainer.ToFailedStatus(ctx, task, reason); err != nil { 120 | return newTask, fmt.Errorf("persistContainer ToFailedStatus error: %v", err) 121 | } 122 | if newTask, err = c.memeoryContainer.ToFailedStatus(ctx, task, reason); err != nil { 123 | return newTask, fmt.Errorf("memeoryContainer ToFailedStatus error: %v", err) 124 | } 125 | return newTask, nil 126 | } 127 | 128 | // ToExportStatus 转移到数据导出状态 129 | func (c *combinationContainer) ToExportStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 130 | newTask *lighttaskscheduler.Task, err error) { 131 | if newTask, err = c.persistContainer.ToExportStatus(ctx, task); err != nil { 132 | return newTask, fmt.Errorf("persistContainer ToExportStatus error: %v", err) 133 | } 134 | if newTask, err = c.memeoryContainer.ToExportStatus(ctx, task); err != nil { 135 | return newTask, fmt.Errorf("memeoryContainer ToExportStatus error: %v", err) 136 | } 137 | return newTask, nil 138 | } 139 | 140 | // ToSuccessStatus 转移到执行成功状态 141 | func (c *combinationContainer) ToSuccessStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 142 | newTask *lighttaskscheduler.Task, err error) { 143 | if newTask, err = c.persistContainer.ToSuccessStatus(ctx, task); err != nil { 144 | return newTask, fmt.Errorf("persistContainer ToSuccessStatus error: %v", err) 145 | } 146 | if newTask, err = c.memeoryContainer.ToSuccessStatus(ctx, task); err != nil { 147 | return newTask, fmt.Errorf("memeoryContainer ToSuccessStatus error: %v", err) 148 | } 149 | return newTask, nil 150 | } 151 | 152 | // UpdateRunningTaskStatus 更新执行中的任务状态 153 | func (c *combinationContainer) UpdateRunningTaskStatus(ctx context.Context, 154 | task *lighttaskscheduler.Task, status lighttaskscheduler.AsyncTaskStatus) error { 155 | if err := c.persistContainer.UpdateRunningTaskStatus(ctx, task, status); err != nil { 156 | return fmt.Errorf("persistContainer UpdateRunningTaskStatus error: %v", err) 157 | } 158 | if err := c.memeoryContainer.UpdateRunningTaskStatus(ctx, task, status); err != nil { 159 | return fmt.Errorf("memeoryContainer UpdateRunningTaskStatus error: %v", err) 160 | } 161 | return nil 162 | } 163 | -------------------------------------------------------------------------------- /container/memory_container/memory_container.go: -------------------------------------------------------------------------------- 1 | package memeorycontainer 2 | 3 | import ( 4 | "context" 5 | 6 | lighttaskscheduler "github.com/memory-overflow/light-task-scheduler" 7 | ) 8 | 9 | // MemeoryContainer 内存型任务容器,优先:快读快写,缺点:不可持久化, 10 | type MemeoryContainer interface { 11 | lighttaskscheduler.TaskContainer 12 | 13 | // AddRunningTask 向容器添加正在运行中的任务 14 | // 对于某些可持久化任务,调度器如果因为某些原因退出,需要从 db 中恢复状态,这个接口用来向容器中添加恢复前还在执行中的任务 15 | AddRunningTask(ctx context.Context, task lighttaskscheduler.Task) (err error) 16 | } 17 | -------------------------------------------------------------------------------- /container/memory_container/orderedmap_container.go: -------------------------------------------------------------------------------- 1 | package memeorycontainer 2 | 3 | import ( 4 | "context" 5 | 6 | lighttaskscheduler "github.com/memory-overflow/light-task-scheduler" 7 | ) 8 | 9 | // orderedMapContainer OrderedMap 作为容器,支持任务优先级,多进程数据无法共享数据 10 | type orderedMapContainer struct { 11 | MemeoryContainer 12 | // TODO 13 | } 14 | 15 | // MakeOrderedMapContainer 构造队列型任务容器 16 | func MakeOrderedMapContainer() *orderedMapContainer { 17 | // TODO 18 | return &orderedMapContainer{} 19 | } 20 | 21 | // AddTask 添加任务 22 | func (o *orderedMapContainer) AddTask(ctx context.Context, task lighttaskscheduler.Task) (err error) { 23 | // TODO 24 | return nil 25 | } 26 | 27 | // GetRunningTask 获取运行中的任务 28 | func (o *orderedMapContainer) GetRunningTask(ctx context.Context) (tasks []lighttaskscheduler.Task, err error) { 29 | // TODO 30 | return tasks, err 31 | } 32 | 33 | // GetRunningTaskCount 获取运行中的任务数 34 | func (o *orderedMapContainer) GetRunningTaskCount(ctx context.Context) (count int32, err error) { 35 | // TODO 36 | return 0, nil 37 | } 38 | 39 | // GetWaitingTask 获取等待中的任务 40 | func (o *orderedMapContainer) GetWaitingTask(ctx context.Context, limit int32) (tasks []lighttaskscheduler.Task, err error) { 41 | // TODO 42 | return tasks, nil 43 | } 44 | 45 | // ToRunningStatus 转移到运行中的状态 46 | func (o *orderedMapContainer) ToRunningStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 47 | newTask *lighttaskscheduler.Task, err error) { 48 | // TODO 49 | return task, nil 50 | } 51 | 52 | // ToExportStatus 转移到停止状态 53 | func (o *orderedMapContainer) ToStopStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 54 | newTask *lighttaskscheduler.Task, err error) { 55 | // TODO 56 | return task, nil 57 | } 58 | 59 | // ToExportStatus 转移到删除状态 60 | func (o *orderedMapContainer) ToDeleteStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 61 | newTask *lighttaskscheduler.Task, err error) { 62 | // TODO 63 | return task, nil 64 | } 65 | 66 | // ToFailedStatus 转移到失败状态 67 | func (o *orderedMapContainer) ToFailedStatus(ctx context.Context, task *lighttaskscheduler.Task, reason error) ( 68 | newTask *lighttaskscheduler.Task, err error) { 69 | // TODO 70 | return 71 | } 72 | 73 | // ToExportStatus 转移到数据导出状态 74 | func (o *orderedMapContainer) ToExportStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 75 | newTask *lighttaskscheduler.Task, err error) { 76 | // TODO 77 | return task, nil 78 | } 79 | 80 | // ToSuccessStatus 转移到执行成功状态 81 | func (o *orderedMapContainer) ToSuccessStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 82 | newTask *lighttaskscheduler.Task, err error) { 83 | // TODO 84 | return task, nil 85 | } 86 | 87 | // UpdateRunningTaskStatus 更新执行中的任务状态 88 | func (o *orderedMapContainer) UpdateRunningTaskStatus(ctx context.Context, 89 | task *lighttaskscheduler.Task, status lighttaskscheduler.AsyncTaskStatus) error { 90 | // TODO 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /container/memory_container/queue_container.go: -------------------------------------------------------------------------------- 1 | package memeorycontainer 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "sync" 7 | "sync/atomic" 8 | "time" 9 | 10 | lighttaskscheduler "github.com/memory-overflow/light-task-scheduler" 11 | ) 12 | 13 | // queueContainer 队列型容器,任务无状态,无优先级,先进先出,任务数据,多进程数据无法共享数据 14 | type queueContainer struct { 15 | MemeoryContainer 16 | 17 | stopedTaskMap sync.Map // 需要停止的任务 map,用来标记等待中的哪些任务被停职了,taskId -> lighttaskscheduler.Task 18 | runningTaskMap sync.Map // 运行中的任务的 map, taskId -> lighttaskscheduler.Task 19 | runningTaskCount int32 // 运行中的任务总数 20 | 21 | waitingTasks chan lighttaskscheduler.Task 22 | timeout time.Duration 23 | } 24 | 25 | // MakeQueueContainer 构造队列型任务容器, size 表示队列的大小, timeout 表示队列读取的超时时间 26 | func MakeQueueContainer(size uint32, timeout time.Duration) *queueContainer { 27 | return &queueContainer{ 28 | waitingTasks: make(chan lighttaskscheduler.Task, size), 29 | timeout: timeout, 30 | } 31 | } 32 | 33 | // AddTask 添加任务 34 | func (q *queueContainer) AddTask(ctx context.Context, task lighttaskscheduler.Task) (err error) { 35 | // 如果是之前已经暂停,那么直接删除暂停状态 36 | if _, ok := q.stopedTaskMap.LoadAndDelete(task.TaskId); ok { 37 | return 38 | } 39 | task.TaskStatus = lighttaskscheduler.TASK_STATUS_WAITING 40 | select { 41 | case q.waitingTasks <- task: 42 | return 43 | case <-time.After(q.timeout): 44 | return fmt.Errorf("add task timeout") 45 | } 46 | } 47 | 48 | // AddRunningTask 添加正在分析中的任务,用于从持久化容器中恢复数据 49 | func (q *queueContainer) AddRunningTask(ctx context.Context, task lighttaskscheduler.Task) (err error) { 50 | // 如果任务没有在执行列表中,加入执行列表 51 | if _, ok := q.runningTaskMap.LoadOrStore(task.TaskId, task); !ok { 52 | atomic.AddInt32(&q.runningTaskCount, 1) 53 | } 54 | return nil 55 | } 56 | 57 | // GetRunningTask 获取运行中的任务 58 | func (q *queueContainer) GetRunningTask(ctx context.Context) (tasks []lighttaskscheduler.Task, err error) { 59 | q.runningTaskMap.Range( 60 | func(key, value interface{}) bool { 61 | task := value.(lighttaskscheduler.Task) 62 | tasks = append(tasks, task) 63 | return true 64 | }) 65 | // if len(tasks) > 0 { 66 | // log.Printf("get %d task success \n", len(tasks)) 67 | // } 68 | return tasks, err 69 | } 70 | 71 | // GetRunningTaskCount 获取运行中的任务数 72 | func (q *queueContainer) GetRunningTaskCount(ctx context.Context) (count int32, err error) { 73 | return q.runningTaskCount, nil 74 | } 75 | 76 | // GetWaitingTask 获取等待中的任务 77 | func (q *queueContainer) GetWaitingTask(ctx context.Context, limit int32) (tasks []lighttaskscheduler.Task, err error) { 78 | for i := 0; i < int(limit); i++ { 79 | select { 80 | case task := <-q.waitingTasks: 81 | // 暂停的任务直接忽略 82 | if _, ok := q.stopedTaskMap.LoadAndDelete(task.TaskId); !ok { 83 | tasks = append(tasks, task) 84 | } 85 | case <-time.After(q.timeout): 86 | return tasks, nil 87 | } 88 | } 89 | return tasks, nil 90 | } 91 | 92 | // ToRunningStatus 转移到运行中的状态 93 | func (q *queueContainer) ToRunningStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 94 | newTask *lighttaskscheduler.Task, err error) { 95 | task.TaskStartTime = time.Now() 96 | task.TaskStatus = lighttaskscheduler.TASK_STATUS_RUNNING 97 | t, ok := q.runningTaskMap.LoadOrStore(task.TaskId, *task) 98 | if !ok { 99 | atomic.AddInt32(&q.runningTaskCount, 1) 100 | } else { 101 | nt := t.(lighttaskscheduler.Task) 102 | nt.TaskAttemptsTime = task.TaskAttemptsTime 103 | q.runningTaskMap.Store(task.TaskId, nt) 104 | } 105 | return task, nil 106 | } 107 | 108 | // ToExportStatus 转移到停止状态 109 | func (q *queueContainer) ToStopStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 110 | newTask *lighttaskscheduler.Task, err error) { 111 | // 如果任务已经在执行中,删除执行中的任务 112 | if _, ok := q.runningTaskMap.LoadAndDelete(task.TaskId); ok { 113 | atomic.AddInt32(&q.runningTaskCount, -1) 114 | } else { 115 | // 任务在等待队列中,任务加入到停止列表,待调度到的时候 pass 掉 116 | q.stopedTaskMap.Store(task.TaskId, struct{}{}) 117 | } 118 | task.TaskStatus = lighttaskscheduler.TASK_STATUS_STOPED 119 | return task, nil 120 | } 121 | 122 | // ToExportStatus 转移到删除状态 123 | func (q *queueContainer) ToDeleteStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 124 | newTask *lighttaskscheduler.Task, err error) { 125 | // 如果任务已经在执行中,删除执行中的任务 126 | if _, ok := q.runningTaskMap.LoadAndDelete(task.TaskId); ok { 127 | atomic.AddInt32(&q.runningTaskCount, -1) 128 | } else { 129 | // 任务在等待队列中,任务加入到暂停列表,待调度到的时候 pass 掉 130 | q.stopedTaskMap.Store(task.TaskId, struct{}{}) 131 | } 132 | task.TaskStatus = lighttaskscheduler.TASK_STATUS_DELETE 133 | return task, nil 134 | } 135 | 136 | // ToFailedStatus 转移到失败状态 137 | func (q *queueContainer) ToFailedStatus(ctx context.Context, task *lighttaskscheduler.Task, reason error) ( 138 | newTask *lighttaskscheduler.Task, err error) { 139 | if _, ok := q.runningTaskMap.LoadAndDelete(task.TaskId); ok { 140 | atomic.AddInt32(&q.runningTaskCount, -1) 141 | } 142 | task.TaskStatus = lighttaskscheduler.TASK_STATUS_FAILED 143 | task.FailedReason = reason.Error() 144 | return task, nil 145 | } 146 | 147 | // ToExportStatus 转移到数据导出状态 148 | func (q *queueContainer) ToExportStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 149 | newTask *lighttaskscheduler.Task, err error) { 150 | // 删除执行中的任务,增加一个任务调度空位 151 | if _, ok := q.runningTaskMap.LoadAndDelete(task.TaskId); ok { 152 | atomic.AddInt32(&q.runningTaskCount, -1) 153 | } 154 | task.TaskStatus = lighttaskscheduler.TASK_STATUS_EXPORTING 155 | return task, nil 156 | } 157 | 158 | // ToSuccessStatus 转移到执行成功状态 159 | func (q *queueContainer) ToSuccessStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 160 | newTask *lighttaskscheduler.Task, err error) { 161 | if _, ok := q.runningTaskMap.LoadAndDelete(task.TaskId); ok { 162 | atomic.AddInt32(&q.runningTaskCount, -1) 163 | } 164 | task.TaskStatus = lighttaskscheduler.TASK_STATUS_SUCCESS 165 | return task, nil 166 | } 167 | 168 | // UpdateRunningTaskStatus 更新执行中的任务状态 169 | func (q *queueContainer) UpdateRunningTaskStatus(ctx context.Context, 170 | task *lighttaskscheduler.Task, status lighttaskscheduler.AsyncTaskStatus) error { 171 | return nil 172 | } 173 | -------------------------------------------------------------------------------- /container/memory_container/redis_container.go: -------------------------------------------------------------------------------- 1 | package memeorycontainer 2 | 3 | import ( 4 | "context" 5 | 6 | lighttaskscheduler "github.com/memory-overflow/light-task-scheduler" 7 | ) 8 | 9 | // redisContainer redis 作为容器,支持任务优先级,并且可以多进程,多副本共享数据 10 | type redisContainer struct { 11 | MemeoryContainer 12 | // TODO 13 | } 14 | 15 | // MakeredisContainer 构造队列型任务容器 16 | func MakeredisContainer() *redisContainer { 17 | // TODO 18 | return &redisContainer{} 19 | } 20 | 21 | // AddTask 添加任务 22 | func (r *redisContainer) AddTask(ctx context.Context, task lighttaskscheduler.Task) (err error) { 23 | // TODO 24 | return nil 25 | } 26 | 27 | // GetRunningTask 获取运行中的任务 28 | func (r *redisContainer) GetRunningTask(ctx context.Context) (tasks []lighttaskscheduler.Task, err error) { 29 | // TODO 30 | return tasks, err 31 | } 32 | 33 | // GetRunningTaskCount 获取运行中的任务数 34 | func (r *redisContainer) GetRunningTaskCount(ctx context.Context) (count int32, err error) { 35 | // TODO 36 | return 0, nil 37 | } 38 | 39 | // GetWaitingTask 获取等待中的任务 40 | func (r *redisContainer) GetWaitingTask(ctx context.Context, limit int32) (tasks []lighttaskscheduler.Task, err error) { 41 | // TODO 42 | return tasks, nil 43 | } 44 | 45 | // ToRunningStatus 转移到运行中的状态 46 | func (r *redisContainer) ToRunningStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 47 | newTask *lighttaskscheduler.Task, err error) { 48 | // TODO 49 | return task, nil 50 | } 51 | 52 | // ToExportStatus 转移到停止状态 53 | func (r *redisContainer) ToStopStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 54 | newTask *lighttaskscheduler.Task, err error) { 55 | // TODO 56 | return task, nil 57 | } 58 | 59 | // ToExportStatus 转移到删除状态 60 | func (r *redisContainer) ToDeleteStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 61 | newTask *lighttaskscheduler.Task, err error) { 62 | // TODO 63 | return task, nil 64 | } 65 | 66 | // ToFailedStatus 转移到失败状态 67 | func (r *redisContainer) ToFailedStatus(ctx context.Context, task *lighttaskscheduler.Task, reason error) ( 68 | newTask *lighttaskscheduler.Task, err error) { 69 | // TODO 70 | return 71 | } 72 | 73 | // ToExportStatus 转移到数据导出状态 74 | func (r *redisContainer) ToExportStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 75 | newTask *lighttaskscheduler.Task, err error) { 76 | // TODO 77 | return task, nil 78 | } 79 | 80 | // ToSuccessStatus 转移到执行成功状态 81 | func (r *redisContainer) ToSuccessStatus(ctx context.Context, task *lighttaskscheduler.Task) ( 82 | newTask *lighttaskscheduler.Task, err error) { 83 | // TODO 84 | return task, nil 85 | } 86 | 87 | // UpdateRunningTaskStatus 更新执行中的任务状态 88 | func (r *redisContainer) UpdateRunningTaskStatus(ctx context.Context, 89 | task *lighttaskscheduler.Task, status lighttaskscheduler.AsyncTaskStatus) error { 90 | // TODO 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /container/persist_container/persist_container.go: -------------------------------------------------------------------------------- 1 | package persistcontainer 2 | 3 | import lighttaskscheduler "github.com/memory-overflow/light-task-scheduler" 4 | 5 | // PersistContainer 可持久化任务容器,优点:可持久化存储, 6 | // 缺点:依赖db、需要扫描表获取任务队列,对 db 压力比较大。 7 | type PersistContainer lighttaskscheduler.TaskContainer 8 | -------------------------------------------------------------------------------- /example/add_example/add/add_task.go: -------------------------------------------------------------------------------- 1 | package add 2 | 3 | import "time" 4 | 5 | // AddTask add 任务结构 6 | type AddTask struct { 7 | StartTime time.Time 8 | A, B int32 9 | } 10 | -------------------------------------------------------------------------------- /example/add_example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "math/rand" 8 | "strconv" 9 | "time" 10 | 11 | lighttaskscheduler "github.com/memory-overflow/light-task-scheduler" 12 | "github.com/memory-overflow/light-task-scheduler/actuator" 13 | memeorycontainer "github.com/memory-overflow/light-task-scheduler/container/memory_container" 14 | ) 15 | 16 | // AddTask add 任务结构 17 | type AddTask struct { 18 | StartTime time.Time 19 | A, B int32 20 | } 21 | 22 | func add(ctx context.Context, ftask *lighttaskscheduler.Task) (data interface{}, err error) { 23 | task, ok := ftask.TaskItem.(AddTask) 24 | if !ok { 25 | return nil, fmt.Errorf("TaskItem not be set to AddTask") 26 | } 27 | log.Printf("start run task %s, Attempts: %d\n", ftask.TaskId, ftask.TaskAttemptsTime) 28 | // 模拟 25 % 的概率出错 29 | if rand.Intn(4) == 0 { 30 | return nil, fmt.Errorf("error test") 31 | } 32 | // 模拟 25% 概率超时 33 | time.Sleep(time.Duration(rand.Intn(4000))*time.Millisecond + 2*time.Second) 34 | 35 | return task.A + task.B, nil 36 | } 37 | 38 | type callbackReceiver struct { 39 | } 40 | 41 | // modePolling 默认模式,状态轮询模式 42 | func modePolling() *lighttaskscheduler.TaskScheduler { 43 | container := memeorycontainer.MakeQueueContainer(10000, 100*time.Millisecond) 44 | actuator, err := actuator.MakeFucntionActuator(add, nil) 45 | if err != nil { 46 | log.Fatal("make fucntionActuato error: ", err) 47 | } 48 | sch, err := lighttaskscheduler.MakeScheduler( 49 | container, actuator, nil, 50 | lighttaskscheduler.Config{ 51 | TaskLimit: 2, 52 | TaskTimeout: 5 * time.Second, // 5s 超时 53 | EnableFinshedTaskList: true, // 开启已完成任务返回功能 54 | MaxFailedAttempts: 3, // 失败重试次数 55 | SchedulingPollInterval: 0, 56 | StatePollInterval: 0, 57 | }, 58 | ) 59 | if err != nil { 60 | log.Fatal("make scheduler error: ", err) 61 | } 62 | return sch 63 | } 64 | 65 | type addCallbackReceiver struct { 66 | taskChannel chan lighttaskscheduler.Task 67 | } 68 | 69 | func (rec addCallbackReceiver) GetCallbackChannel(ctx context.Context) chan lighttaskscheduler.Task { 70 | return rec.taskChannel 71 | } 72 | 73 | // modeCallback 仅回调模式 74 | func modeCallback() *lighttaskscheduler.TaskScheduler { 75 | container := memeorycontainer.MakeQueueContainer(10000, 100*time.Millisecond) 76 | taskChannel := make(chan lighttaskscheduler.Task, 10000) 77 | actuator, err := actuator.MakeFucntionActuator(add, nil) 78 | actuator.SetCallbackChannel(taskChannel) 79 | if err != nil { 80 | log.Fatal("make fucntionActuato error: ", err) 81 | } 82 | receiver := addCallbackReceiver{ 83 | taskChannel: taskChannel, 84 | } 85 | sch, err := lighttaskscheduler.MakeScheduler( 86 | container, actuator, nil, 87 | lighttaskscheduler.Config{ 88 | TaskLimit: 2, 89 | TaskTimeout: 5 * time.Second, // 5s 超时 90 | EnableFinshedTaskList: true, // 开启已完成任务返回功能 91 | MaxFailedAttempts: 3, // 失败重试次数 92 | SchedulingPollInterval: 0, 93 | DisableStatePoll: true, 94 | EnableStateCallback: true, 95 | CallbackReceiver: receiver, 96 | }, 97 | ) 98 | if err != nil { 99 | log.Fatal("make scheduler error: ", err) 100 | } 101 | return sch 102 | } 103 | 104 | // modeCallback 轮询 + 回调模式 105 | func modePollingCallback() *lighttaskscheduler.TaskScheduler { 106 | container := memeorycontainer.MakeQueueContainer(10000, 100*time.Millisecond) 107 | taskChannel := make(chan lighttaskscheduler.Task, 10000) 108 | actuator, err := actuator.MakeFucntionActuator(add, nil) 109 | actuator.SetCallbackChannel(taskChannel) 110 | if err != nil { 111 | log.Fatal("make fucntionActuato error: ", err) 112 | } 113 | receiver := addCallbackReceiver{ 114 | taskChannel: taskChannel, 115 | } 116 | sch, err := lighttaskscheduler.MakeScheduler( 117 | container, actuator, nil, 118 | lighttaskscheduler.Config{ 119 | TaskLimit: 2, 120 | TaskTimeout: 5 * time.Second, // 5s 超时 121 | EnableFinshedTaskList: true, // 开启已完成任务返回功能 122 | MaxFailedAttempts: 3, // 失败重试次数 123 | SchedulingPollInterval: 0, 124 | DisableStatePoll: false, 125 | StatePollInterval: 0, 126 | EnableStateCallback: true, 127 | CallbackReceiver: receiver, 128 | }, 129 | ) 130 | if err != nil { 131 | log.Fatal("make scheduler error: ", err) 132 | } 133 | return sch 134 | } 135 | 136 | func main() { 137 | // 模式1, 默认模式,状态轮询模式 138 | // sch := modePolling() 139 | 140 | // 模式2, 回调模式 141 | // sch := modeCallback() 142 | 143 | // 模式3,轮询 + 回调模式 144 | sch := modePollingCallback() 145 | 146 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 147 | for i := 0; i < 10; i++ { 148 | sch.AddTask(context.Background(), 149 | lighttaskscheduler.Task{ 150 | TaskId: strconv.Itoa(i), 151 | TaskItem: AddTask{ 152 | A: r.Int31() % 1000, 153 | B: r.Int31() % 1000, 154 | }, 155 | }) 156 | } 157 | for task := range sch.FinshedTasks() { 158 | if task.TaskStatus == lighttaskscheduler.TASK_STATUS_FAILED { 159 | log.Printf("failed task %s, reason: %s, timecost: %dms, attempt times: %d\n", 160 | task.TaskId, task.FailedReason, task.TaskEnbTime.Sub(task.TaskStartTime).Milliseconds(), task.TaskAttemptsTime) 161 | } else if task.TaskStatus == lighttaskscheduler.TASK_STATUS_SUCCESS { 162 | log.Printf("success task %s, timecost: %dms, attempt times: %d\n", task.TaskId, 163 | task.TaskEnbTime.Sub(task.TaskStartTime).Milliseconds(), task.TaskAttemptsTime) 164 | } 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /example/docker_example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "strconv" 6 | "time" 7 | 8 | "context" 9 | 10 | lighttaskscheduler "github.com/memory-overflow/light-task-scheduler" 11 | "github.com/memory-overflow/light-task-scheduler/actuator" 12 | memeorycontainer "github.com/memory-overflow/light-task-scheduler/container/memory_container" 13 | ) 14 | 15 | func main() { 16 | container := memeorycontainer.MakeQueueContainer(10000, 100*time.Millisecond) 17 | scanInterval := 50 * time.Millisecond 18 | // 构建裁剪任务执行器 19 | act := actuator.MakeDockerActuator(nil) 20 | sch, _ := lighttaskscheduler.MakeScheduler( 21 | container, act, nil, 22 | lighttaskscheduler.Config{ 23 | TaskLimit: 2, // 2 并发 24 | TaskTimeout: 60 * time.Second, // 60s 超时时间 25 | SchedulingPollInterval: scanInterval, 26 | StatePollInterval: scanInterval, 27 | EnableFinshedTaskList: true, 28 | }, 29 | ) 30 | 31 | for i := 0; i < 10; i++ { 32 | sch.AddTask(context.Background(), 33 | lighttaskscheduler.Task{ 34 | TaskId: strconv.Itoa(i), 35 | TaskItem: actuator.DockerTask{ 36 | Image: "centos:7", 37 | Cmd: []string{"sh", "-c", "echo 'helloworld'; sleep 1"}, 38 | ContainerName: "helloworld-" + strconv.Itoa(i), 39 | VolumeBinds: map[string]string{ 40 | "/home": "/data", 41 | }, 42 | MemoryLimit: 10 * 1024 * 1024, // 10 MB 43 | CpuPercent: 100, 44 | }, 45 | }) 46 | } 47 | 48 | for task := range sch.FinshedTasks() { 49 | if task.TaskStatus == lighttaskscheduler.TASK_STATUS_FAILED { 50 | log.Printf("failed task %s, reason: %s, timecost: %dms, attempt times: %d\n", 51 | task.TaskId, task.FailedReason, task.TaskEnbTime.Sub(task.TaskStartTime).Milliseconds(), task.TaskAttemptsTime) 52 | } else if task.TaskStatus == lighttaskscheduler.TASK_STATUS_SUCCESS { 53 | log.Printf("success task %s, timecost: %dms, attempt times: %d\n", task.TaskId, 54 | task.TaskEnbTime.Sub(task.TaskStartTime).Milliseconds(), task.TaskAttemptsTime) 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /example/videocut_example/main_demo/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | "strconv" 8 | "time" 9 | 10 | lighttaskscheduler "github.com/memory-overflow/light-task-scheduler" 11 | memeorycontainer "github.com/memory-overflow/light-task-scheduler/container/memory_container" 12 | videocut "github.com/memory-overflow/light-task-scheduler/example/videocut_example/video_cut" 13 | ) 14 | 15 | func main() { 16 | inputVideo := os.Args[1] 17 | mode := "queue" // 默认队列容器 18 | if len(os.Args) > 2 { 19 | mode = os.Args[2] // 任务容器模式 20 | } 21 | 22 | go videocut.StartServer() // start video cut microservice 23 | 24 | // 构建队列容器,队列长度 10000 25 | var container lighttaskscheduler.TaskContainer 26 | var persistencer lighttaskscheduler.TaskdataPersistencer 27 | var scanInterval time.Duration // 调度器扫描间隔时间 28 | 29 | if mode == "queue" { 30 | persistencer = nil 31 | container = memeorycontainer.MakeQueueContainer(10000, 100*time.Millisecond) 32 | scanInterval = 50 * time.Millisecond 33 | } else if mode == "sql" { 34 | var err error 35 | videocutContainer, err := videocut.MakeVideoCutSqlContainer("127.0.0.1", "3306", "root", "", "test") 36 | if err != nil { 37 | log.Fatalf("build container failed: %v\n", err) 38 | } 39 | persistencer = videocutContainer 40 | container = videocutContainer 41 | scanInterval = 2 * time.Second 42 | } 43 | 44 | // 构建裁剪任务执行器 45 | actuator := videocut.MakeVideoCutActuator() 46 | sch, err := lighttaskscheduler.MakeScheduler( 47 | container, actuator, persistencer, 48 | lighttaskscheduler.Config{ 49 | TaskLimit: 2, // 2 并发 50 | TaskTimeout: 20 * time.Second, // 20s 超时时间 51 | EnableFinshedTaskList: true, // 开启已完成任务返回功能 52 | SchedulingPollInterval: scanInterval, 53 | DisableStatePoll: false, 54 | StatePollInterval: scanInterval, 55 | }, 56 | ) 57 | if err != nil { 58 | log.Fatalf("make schedule failed: %v\n", err) 59 | } 60 | // 添加任务,把视频裁前 100s 剪成 10s 一个的视频 61 | for i := 0; i < 100; i += 10 { 62 | // 这里的任务 ID 是为了调度框架方便标识唯一任务的ID, 和微服务的任务ID不同,是映射关系 63 | taskId := "task-" + videocut.GenerateRandomString(8) 64 | if err := sch.AddTask(context.Background(), 65 | lighttaskscheduler.Task{ 66 | TaskId: taskId, 67 | TaskItem: videocut.VideoCutTask{ 68 | TaskId: taskId, 69 | InputVideo: inputVideo, 70 | CutStartTime: float32(i), 71 | CutEndTime: float32(i + 10), 72 | }, 73 | }); err != nil { 74 | log.Printf("add task TaskId %s failed: %v\n", strconv.Itoa(i), err) 75 | } 76 | } 77 | 78 | for task := range sch.FinshedTasks() { 79 | if task.TaskStatus == lighttaskscheduler.TASK_STATUS_FAILED { 80 | log.Printf("failed task %s, reason: %s, timecost: %dms\n", 81 | task.TaskId, task.FailedReason, task.TaskEnbTime.Sub(task.TaskStartTime).Milliseconds()) 82 | } else if task.TaskStatus == lighttaskscheduler.TASK_STATUS_SUCCESS { 83 | log.Printf("success task %s, timecost: %dms\n", task.TaskId, 84 | task.TaskEnbTime.Sub(task.TaskStartTime).Milliseconds()) 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /example/videocut_example/main_web/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | lighttaskscheduler "github.com/memory-overflow/light-task-scheduler" 8 | "github.com/memory-overflow/light-task-scheduler/container" 9 | memeorycontainer "github.com/memory-overflow/light-task-scheduler/container/memory_container" 10 | videocut "github.com/memory-overflow/light-task-scheduler/example/videocut_example/video_cut" 11 | ) 12 | 13 | func main() { 14 | 15 | // 构建队列容器,队列长度 10000 16 | queueContainer := memeorycontainer.MakeQueueContainer(10000, 100*time.Millisecond) 17 | scanInterval := 50 * time.Millisecond 18 | // 替换自己的数据库 19 | sqlContainer, err := videocut.MakeVideoCutSqlContainer("127.0.0.1", "3306", "root", "", "test") 20 | if err != nil { 21 | log.Fatalf("build container failed: %v\n", err) 22 | } 23 | comb := container.MakeCombinationContainer(queueContainer, sqlContainer) 24 | // 构建裁剪任务执行器 25 | actuator := videocut.MakeVideoCutActuator() 26 | sch, err := lighttaskscheduler.MakeScheduler( 27 | comb, actuator, sqlContainer, 28 | lighttaskscheduler.Config{ 29 | TaskLimit: 2, // 2 并发 30 | TaskTimeout: 60 * time.Second, // 20s 超时时间 31 | EnableFinshedTaskList: true, // 开启已完成任务返回功能 32 | SchedulingPollInterval: scanInterval, 33 | DisableStatePoll: false, 34 | StatePollInterval: scanInterval, // 开启已完成任务返回功能 35 | }, 36 | ) 37 | if err != nil { 38 | log.Fatalf("make schedule failed: %v\n", err) 39 | } 40 | go videocut.StartServer() // start video cut microservice 41 | videocut.StartWebServer(sqlContainer, sch) // start web server 42 | } 43 | -------------------------------------------------------------------------------- /example/videocut_example/video_cut/example_sql_container.go: -------------------------------------------------------------------------------- 1 | package videocut 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "log" 8 | "os" 9 | "time" 10 | 11 | framework "github.com/memory-overflow/light-task-scheduler" 12 | "gorm.io/driver/mysql" 13 | "gorm.io/gorm" 14 | "gorm.io/gorm/clause" 15 | gormlogger "gorm.io/gorm/logger" 16 | ) 17 | 18 | // videoCutSqlContainer sql db 作为容器,可以根据本实现调整自己的数据表 19 | type videoCutSqlContainer struct { 20 | db *gorm.DB // 数据库连接 21 | } 22 | 23 | // MakeVideoCutSqlContainer 构造数据库容器 24 | func MakeVideoCutSqlContainer(dbHost, dbPort, dbUser, dbPass, dbName string) ( 25 | sqlContainer *videoCutSqlContainer, err error) { 26 | 27 | // 获取 gorm 数据库连接实例 28 | connStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8&parseTime=True&loc=Local&interpolateParams=true", 29 | dbUser, dbPass, dbHost, dbPort, dbName) 30 | newLogger := gormlogger.New( 31 | log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer 32 | gormlogger.Config{ 33 | SlowThreshold: 5 * time.Second, // Slow SQL threshold 34 | LogLevel: gormlogger.Warn, // Log level 35 | Colorful: true, // enable color 36 | }, 37 | ) 38 | db, err := gorm.Open(mysql.Open(connStr), &gorm.Config{ 39 | CreateBatchSize: 500, 40 | Logger: newLogger, 41 | }) 42 | if err != nil { 43 | return nil, errors.New("Connect db failed, conn str: " + connStr + ", error: " + err.Error()) 44 | } 45 | if db == nil { 46 | return nil, errors.New("Connect db failed, conn str: " + connStr + ", unknow error") 47 | } 48 | sql, err := db.DB() 49 | if err != nil { 50 | return nil, errors.New("Connect db failed, conn str: " + connStr + ", unknow error") 51 | } 52 | // 配置 db 限制 53 | sql.SetMaxOpenConns(200) 54 | sql.SetMaxIdleConns(200) 55 | sql.SetConnMaxLifetime(time.Minute) 56 | sql.SetConnMaxIdleTime(time.Minute) 57 | // db = db.Debug() 58 | // 自动 migrate task 表 59 | err = db.Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8").AutoMigrate(&VideoCutTask{}) 60 | if err != nil { 61 | return nil, errors.New("migrate table failed: " + err.Error()) 62 | } 63 | return &videoCutSqlContainer{db: db}, nil 64 | } 65 | 66 | // AddTask 添加任务 67 | func (e *videoCutSqlContainer) AddTask(ctx context.Context, ftask framework.Task) (err error) { 68 | 69 | task, ok := ftask.TaskItem.(VideoCutTask) 70 | if !ok { 71 | return fmt.Errorf("TaskItem not be set to VideoCutTask") 72 | } 73 | db := e.db 74 | t := time.Now() 75 | task.Status = framework.TASK_STATUS_WAITING 76 | task.StartAt = &t 77 | task.EndAt = nil 78 | 79 | if err = db.Clauses(clause.OnConflict{ 80 | Columns: []clause.Column{{Name: "task_id"}}, 81 | DoUpdates: clause.AssignmentColumns([]string{"status", "start_time", "end_time"}), 82 | }).Create(&task).Error; err != nil { 83 | err = fmt.Errorf("db create error: %v", err) 84 | log.Println(err) 85 | return err 86 | } 87 | return nil 88 | } 89 | 90 | // GetRunningTask 获取运行中的任务 91 | func (e *videoCutSqlContainer) GetRunningTask(ctx context.Context) (tasks []framework.Task, err error) { 92 | db := e.db 93 | taskRecords := []VideoCutTask{} 94 | if err = db.Where("status = ?", framework.TASK_STATUS_RUNNING).Find(&taskRecords).Error; err != nil { 95 | err = fmt.Errorf("db find error: %v", err) 96 | log.Panicln(err) 97 | return nil, err 98 | } 99 | for _, taskRecord := range taskRecords { 100 | task := framework.Task{ 101 | TaskId: taskRecord.TaskId, 102 | TaskItem: taskRecord, 103 | TaskStatus: taskRecord.Status, 104 | } 105 | if taskRecord.StartAt != nil { 106 | task.TaskStartTime = *taskRecord.StartAt 107 | } 108 | tasks = append(tasks, task) 109 | } 110 | return tasks, nil 111 | } 112 | 113 | // GetRunningTaskCount 获取运行中的任务数 114 | func (e *videoCutSqlContainer) GetRunningTaskCount(ctx context.Context) (count int32, err error) { 115 | db := e.db 116 | var tot int64 117 | if err = db.Model(&VideoCutTask{}).Where("status = ?", 118 | framework.TASK_STATUS_RUNNING).Count(&tot).Error; err != nil { 119 | err = fmt.Errorf("db count error: %v", err) 120 | log.Println(err) 121 | return 0, err 122 | } 123 | return int32(tot), nil 124 | } 125 | 126 | // GetWaitingTask 获取等待中的任务 127 | func (e *videoCutSqlContainer) GetWaitingTask(ctx context.Context, limit int32) (tasks []framework.Task, err error) { 128 | db := e.db 129 | taskRecords := []VideoCutTask{} 130 | if err = db.Where("status = ?", framework.TASK_STATUS_WAITING). 131 | Order("start_time asc"). // 按照开始时间优先运行 132 | Limit(int(limit)).Find(&taskRecords).Error; err != nil { 133 | err = fmt.Errorf("db create error: %v", err) 134 | log.Println(err) 135 | return nil, err 136 | } 137 | for _, taskRecord := range taskRecords { 138 | task := framework.Task{ 139 | TaskId: taskRecord.TaskId, 140 | TaskItem: taskRecord, 141 | TaskStatus: taskRecord.Status, 142 | } 143 | if taskRecord.StartAt != nil { 144 | task.TaskStartTime = *taskRecord.StartAt 145 | } 146 | tasks = append(tasks, task) 147 | } 148 | return tasks, nil 149 | } 150 | 151 | // ToRunningStatus 转移到运行中的状态 152 | func (e *videoCutSqlContainer) ToRunningStatus(ctx context.Context, ftask *framework.Task) ( 153 | newTask *framework.Task, err error) { 154 | defer func() { 155 | if err != nil { 156 | log.Println("ToRunningStatus: ", err) 157 | } 158 | }() 159 | task, ok := ftask.TaskItem.(VideoCutTask) 160 | if !ok { 161 | return ftask, fmt.Errorf("TaskItem not be set to VideoCutTask") 162 | } 163 | db := e.db 164 | t := time.Now() 165 | sql := db.Model(&VideoCutTask{}).Where("task_id = ? and status = ?", ftask.TaskId, ftask.TaskStatus). 166 | Updates(map[string]interface{}{ 167 | "status": framework.TASK_STATUS_RUNNING, 168 | "start_time": t, 169 | "work_task_id": task.WorkTaskId, 170 | "attempts_time": ftask.TaskAttemptsTime, 171 | }) 172 | if sql.Error != nil { 173 | return ftask, fmt.Errorf("db update error: %v", sql.Error) 174 | } 175 | if sql.RowsAffected == 0 { 176 | return ftask, fmt.Errorf("task %s not found, may status has been changed", task.TaskId) 177 | } 178 | task.Status, ftask.TaskStatus = framework.TASK_STATUS_RUNNING, framework.TASK_STATUS_RUNNING 179 | task.StartAt, ftask.TaskStartTime = &t, t 180 | ftask.TaskItem = task 181 | return ftask, nil 182 | } 183 | 184 | // ToExportStatus 转移到停止状态 185 | func (e *videoCutSqlContainer) ToStopStatus(ctx context.Context, ftask *framework.Task) ( 186 | newTask *framework.Task, err error) { 187 | defer func() { 188 | if err != nil { 189 | log.Println("ToStopStatus: ", err) 190 | } 191 | }() 192 | 193 | task, ok := ftask.TaskItem.(VideoCutTask) 194 | if !ok { 195 | return ftask, fmt.Errorf("TaskItem not be set to VideoCutTask") 196 | } 197 | db := e.db 198 | sql := db.Model(&VideoCutTask{}).Where("task_id = ? and status = ?", task.TaskId, task.Status). 199 | Update("status", framework.TASK_STATUS_STOPED) 200 | if sql.Error != nil { 201 | return ftask, fmt.Errorf("db update error: %v", sql.Error) 202 | } 203 | fmt.Println(sql.RowsAffected) 204 | if sql.RowsAffected == 0 { 205 | return ftask, fmt.Errorf("task %s not found, may status has been changed", task.TaskId) 206 | } 207 | task.Status, ftask.TaskStatus = framework.TASK_STATUS_STOPED, framework.TASK_STATUS_STOPED 208 | ftask.TaskItem = task 209 | return ftask, nil 210 | } 211 | 212 | // ToExportStatus 转移到删除状态 213 | func (e *videoCutSqlContainer) ToDeleteStatus(ctx context.Context, ftask *framework.Task) ( 214 | newTask *framework.Task, err error) { 215 | defer func() { 216 | if err != nil { 217 | log.Println("ToDeleteStatus: ", err) 218 | } 219 | }() 220 | task, ok := ftask.TaskItem.(VideoCutTask) 221 | if !ok { 222 | return ftask, fmt.Errorf("TaskItem not be set to VideoCutTask") 223 | } 224 | db := e.db 225 | sql := db.Where("task_id = ? and status = ?", task.TaskId, task.Status).Delete(&VideoCutTask{}) 226 | if sql.Error != nil { 227 | return ftask, fmt.Errorf("db delete error: %v", sql.Error) 228 | } 229 | if sql.RowsAffected == 0 { 230 | return ftask, fmt.Errorf("task %s not found, may status has been changed", task.TaskId) 231 | } 232 | task.Status, ftask.TaskStatus = framework.TASK_STATUS_DELETE, framework.TASK_STATUS_DELETE 233 | ftask.TaskItem = task 234 | return ftask, nil 235 | } 236 | 237 | // ToFailedStatus 转移到失败状态 238 | func (e *videoCutSqlContainer) ToFailedStatus(ctx context.Context, ftask *framework.Task, reason error) ( 239 | newTask *framework.Task, err error) { 240 | defer func() { 241 | if err != nil { 242 | log.Println("ToFailedStatus: ", err) 243 | } 244 | }() 245 | task, ok := ftask.TaskItem.(VideoCutTask) 246 | if !ok { 247 | return ftask, fmt.Errorf("TaskItem not be set to VideoCutTask") 248 | } 249 | db := e.db 250 | t := time.Now() 251 | sql := db.Model(&VideoCutTask{}).Where("task_id = ? and status = ?", ftask.TaskId, ftask.TaskStatus). 252 | Updates(map[string]interface{}{ 253 | "status": framework.TASK_STATUS_FAILED, 254 | "end_time": t, 255 | "failed_reason": reason.Error(), 256 | }) 257 | if sql.Error != nil { 258 | return ftask, fmt.Errorf("db update error: %v", sql.Error) 259 | } 260 | if sql.RowsAffected == 0 { 261 | return ftask, fmt.Errorf("task %s not found, may status has been changed", task.TaskId) 262 | } 263 | task.Status, ftask.TaskStatus = framework.TASK_STATUS_FAILED, framework.TASK_STATUS_FAILED 264 | task.FailedReason, ftask.FailedReason = reason.Error(), reason.Error() 265 | task.EndAt, ftask.TaskEnbTime = &t, t 266 | ftask.TaskItem = task 267 | return ftask, nil 268 | } 269 | 270 | // ToExportStatus 转移到数据导出状态 271 | func (e *videoCutSqlContainer) ToExportStatus(ctx context.Context, ftask *framework.Task) ( 272 | newTask *framework.Task, err error) { 273 | defer func() { 274 | if err != nil { 275 | log.Println("ToExportStatus: ", err) 276 | } 277 | }() 278 | task, ok := ftask.TaskItem.(VideoCutTask) 279 | if !ok { 280 | return ftask, fmt.Errorf("TaskItem not be set to VideoCutTask") 281 | } 282 | db := e.db 283 | sql := db.Model(&VideoCutTask{}).Where("task_id = ? and status = ?", ftask.TaskId, ftask.TaskStatus). 284 | Update("status", framework.TASK_STATUS_EXPORTING) 285 | if sql.Error != nil { 286 | return ftask, fmt.Errorf("db update error: %v", sql.Error) 287 | } 288 | if sql.RowsAffected == 0 { 289 | return ftask, fmt.Errorf("task %s not found, may status has been changed", task.TaskId) 290 | } 291 | task.Status, ftask.TaskStatus = framework.TASK_STATUS_EXPORTING, framework.TASK_STATUS_EXPORTING 292 | ftask.TaskItem = task 293 | return ftask, nil 294 | } 295 | 296 | // ToSuccessStatus 转移到执行成功状态 297 | func (e *videoCutSqlContainer) ToSuccessStatus(ctx context.Context, ftask *framework.Task) ( 298 | newTask *framework.Task, err error) { 299 | defer func() { 300 | if err != nil { 301 | log.Println("ToSuccessStatus: ", err) 302 | } 303 | }() 304 | task, ok := ftask.TaskItem.(VideoCutTask) 305 | if !ok { 306 | return ftask, fmt.Errorf("TaskItem not be set to VideoCutTask") 307 | } 308 | db := e.db 309 | t := time.Now() 310 | sql := db.Model(&VideoCutTask{}).Where("task_id = ? and status = ?", ftask.TaskId, ftask.TaskStatus). 311 | Updates(map[string]interface{}{ 312 | "status": framework.TASK_STATUS_SUCCESS, 313 | "end_time": t, 314 | }) 315 | if sql.Error != nil { 316 | return ftask, fmt.Errorf("db update error: %v", sql.Error) 317 | } 318 | if sql.RowsAffected == 0 { 319 | return ftask, fmt.Errorf("task %s not found, may status has been changed", task.TaskId) 320 | } 321 | task.Status, ftask.TaskStatus = framework.TASK_STATUS_SUCCESS, framework.TASK_STATUS_SUCCESS 322 | task.EndAt, ftask.TaskEnbTime = &t, t 323 | ftask.TaskItem = task 324 | return ftask, nil 325 | } 326 | 327 | // UpdateRunningTaskStatus 更新执行中的任务状态 328 | func (e *videoCutSqlContainer) UpdateRunningTaskStatus(ctx context.Context, 329 | ftask *framework.Task, status framework.AsyncTaskStatus) error { 330 | return nil 331 | } 332 | 333 | // SaveData 提供一个把从任务执行器获取的任务执行的结果进行存储的机会 334 | // data 协议保持和 TaskActuator.GetOutput 一样, 一个 string 表示结果路径 335 | func (e *videoCutSqlContainer) SaveData(ctx context.Context, ftask *framework.Task, 336 | data interface{}) (err error) { 337 | 338 | return nil 339 | } 340 | 341 | // 下面的方法用来做其他的业务查询 342 | 343 | // GetTaskInfoSet 分页拉取任务列表接口 344 | func (e *videoCutSqlContainer) GetTasks(ctx context.Context, pageNumber, pageSize int) ( 345 | tasks []VideoCutTask, count int, err error) { 346 | db := e.db 347 | tempDb := db.Model(VideoCutTask{}).Where("delete_time is null") 348 | orderStr := "create_time DESC" 349 | var total int64 350 | tempDb.Count(&total) 351 | count = int(total) 352 | tempDb = tempDb.Order(orderStr).Offset((pageNumber - 1) * pageSize).Limit(pageSize) 353 | if e := tempDb.Find(&tasks).Error; e != nil { 354 | return nil, 0, e 355 | } 356 | return tasks, count, nil 357 | } 358 | 359 | // CreateTask 创建任务 360 | func (e *videoCutSqlContainer) CreateTask(ctx context.Context, task VideoCutTask) (err error) { 361 | db := e.db 362 | if err = db.Model(&task).Create(&task).Error; err != nil { 363 | err = fmt.Errorf("db create error: %v", err) 364 | log.Println(err) 365 | return err 366 | } 367 | return nil 368 | } 369 | 370 | // GetTask 根据 taskId 查询任务 371 | func (e *videoCutSqlContainer) GetTask(ctx context.Context, taskId string) ( 372 | task *VideoCutTask, err error) { 373 | db := e.db 374 | tempTask := VideoCutTask{} 375 | if err = db.Model(&tempTask).Where("task_id = ?", taskId).First(&tempTask).Error; err != nil { 376 | if err == gorm.ErrRecordNotFound { 377 | return nil, fmt.Errorf("不存在的任务") 378 | } else { 379 | err = fmt.Errorf("db first error: %v", err) 380 | log.Println(err) 381 | } 382 | return nil, err 383 | } 384 | return &tempTask, nil 385 | } 386 | 387 | func (e videoCutSqlContainer) DataPersistence( 388 | ctx context.Context, ftask *framework.Task, data interface{}) (err error) { 389 | defer func() { 390 | if err != nil { 391 | log.Println("SaveData:", err) 392 | } 393 | }() 394 | 395 | task, ok := ftask.TaskItem.(VideoCutTask) 396 | if !ok { 397 | return fmt.Errorf("TaskItem not be set to VideoCutTask") 398 | } 399 | db := e.db 400 | outputVideo, ok := data.(string) 401 | if !ok { 402 | return fmt.Errorf("data not be set to string") 403 | } 404 | sql := db.Model(&VideoCutTask{}).Where("task_id = ?", ftask.TaskId). 405 | Updates(map[string]interface{}{ 406 | "output_video": outputVideo, 407 | }) 408 | if sql.Error != nil { 409 | return fmt.Errorf("db update error: %v", sql.Error) 410 | } 411 | if sql.RowsAffected == 0 { 412 | return fmt.Errorf("task %s not found, may status has been changed", task.TaskId) 413 | } 414 | return nil 415 | } 416 | 417 | // GetPersistenceData 查询任务持久化结果 418 | func (e videoCutSqlContainer) GetPersistenceData(ctx context.Context, task *framework.Task) ( 419 | data interface{}, err error) { 420 | return nil, nil 421 | } 422 | 423 | // DeletePersistenceData 删除任务的此久化结果 424 | func (e videoCutSqlContainer) DeletePersistenceData(ctx context.Context, task *framework.Task) (err error) { 425 | return nil 426 | } 427 | -------------------------------------------------------------------------------- /example/videocut_example/video_cut/html_document.go: -------------------------------------------------------------------------------- 1 | package videocut 2 | 3 | const htmlFile = ` 4 | 5 | 6 | 7 |
8 | 9 |", { 75 | "text": "待裁剪视频", 76 | })).append($(" | ").append($(" |