mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2024-11-28 05:15:04 +08:00
feat: 增加任务引擎 (#5763)
This commit is contained in:
parent
f3fac23a84
commit
afcf509ec0
115
backend/app/task/task.go
Normal file
115
backend/app/task/task.go
Normal file
@ -0,0 +1,115 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/1Panel-dev/1Panel/backend/constant"
|
||||
"github.com/1Panel-dev/1Panel/backend/i18n"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ActionFunc func() error
|
||||
type RollbackFunc func()
|
||||
|
||||
type Task struct {
|
||||
Name string
|
||||
Logger *log.Logger
|
||||
SubTasks []*SubTask
|
||||
Rollbacks []RollbackFunc
|
||||
logFile *os.File
|
||||
}
|
||||
|
||||
type SubTask struct {
|
||||
Name string
|
||||
Retry int
|
||||
Timeout time.Duration
|
||||
Action ActionFunc
|
||||
Rollback RollbackFunc
|
||||
Error error
|
||||
}
|
||||
|
||||
func NewTask(name string, taskType string) (*Task, error) {
|
||||
logPath := path.Join(constant.LogDir, taskType)
|
||||
//TODO 增加插入到日志表的逻辑
|
||||
file, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0666)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open log file: %w", err)
|
||||
}
|
||||
logger := log.New(file, "", log.LstdFlags)
|
||||
return &Task{Name: name, logFile: file, Logger: logger}, nil
|
||||
}
|
||||
|
||||
func (t *Task) AddSubTask(name string, action ActionFunc, rollback RollbackFunc) {
|
||||
subTask := &SubTask{Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: rollback}
|
||||
t.SubTasks = append(t.SubTasks, subTask)
|
||||
}
|
||||
|
||||
func (t *Task) AddSubTaskWithOps(name string, action ActionFunc, rollback RollbackFunc, retry int, timeout time.Duration) {
|
||||
subTask := &SubTask{Name: name, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback}
|
||||
t.SubTasks = append(t.SubTasks, subTask)
|
||||
}
|
||||
|
||||
func (s *SubTask) Execute(logger *log.Logger) bool {
|
||||
logger.Printf(i18n.GetWithName("SubTaskStart", s.Name))
|
||||
for i := 0; i < s.Retry+1; i++ {
|
||||
if i > 0 {
|
||||
logger.Printf(i18n.GetWithName("TaskRetry", strconv.Itoa(i)))
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.Timeout)
|
||||
defer cancel()
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
done <- s.Action()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
logger.Printf(i18n.GetWithName("TaskTimeout", s.Name))
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
s.Error = err
|
||||
logger.Printf(i18n.GetWithNameAndErr("TaskFailed", s.Name, err))
|
||||
} else {
|
||||
logger.Printf(i18n.GetWithName("TaskSuccess", s.Name))
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
if i == s.Retry {
|
||||
if s.Rollback != nil {
|
||||
s.Rollback()
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
if s.Error != nil {
|
||||
s.Error = fmt.Errorf(i18n.GetWithName("TaskFailed", s.Name))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *Task) Execute() error {
|
||||
t.Logger.Printf(i18n.GetWithName("TaskStart", t.Name))
|
||||
var err error
|
||||
for _, subTask := range t.SubTasks {
|
||||
if subTask.Execute(t.Logger) {
|
||||
if subTask.Rollback != nil {
|
||||
t.Rollbacks = append(t.Rollbacks, subTask.Rollback)
|
||||
}
|
||||
} else {
|
||||
err = subTask.Error
|
||||
for _, rollback := range t.Rollbacks {
|
||||
rollback()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
t.Logger.Printf(i18n.GetWithName("TaskEnd", t.Name))
|
||||
_ = t.logFile.Close()
|
||||
return err
|
||||
}
|
@ -17,4 +17,5 @@ var (
|
||||
RuntimeDir = path.Join(DataDir, "runtime")
|
||||
RecycleBinDir = "/.1panel_clash"
|
||||
SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
|
||||
LogDir = path.Join(global.CONF.System.DataDir, "log")
|
||||
)
|
||||
|
@ -74,6 +74,39 @@ func GetMsgByKey(key string) string {
|
||||
return content
|
||||
}
|
||||
|
||||
func GetWithName(key string, name string) string {
|
||||
var (
|
||||
dataMap = make(map[string]interface{})
|
||||
)
|
||||
dataMap["name"] = name
|
||||
content, _ := global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: dataMap,
|
||||
})
|
||||
return content
|
||||
}
|
||||
|
||||
func GetWithMap(key string, dataMap map[string]string) string {
|
||||
content, _ := global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: dataMap,
|
||||
})
|
||||
return content
|
||||
}
|
||||
|
||||
func GetWithNameAndErr(key string, name string, err error) string {
|
||||
var (
|
||||
dataMap = make(map[string]interface{})
|
||||
)
|
||||
dataMap["name"] = name
|
||||
dataMap["err"] = err.Error()
|
||||
content, _ := global.I18n.Localize(&i18n.LocalizeConfig{
|
||||
MessageID: key,
|
||||
TemplateData: dataMap,
|
||||
})
|
||||
return content
|
||||
}
|
||||
|
||||
//go:embed lang/*
|
||||
var fs embed.FS
|
||||
var bundle *i18n.Bundle
|
||||
|
@ -196,3 +196,12 @@ ErrLicenseSync: "Failed to sync license information, no license information dete
|
||||
ErrXpackNotFound: "This section is a professional edition feature, please import the license first in Panel Settings-License interface"
|
||||
ErrXpackNotActive: "This section is a professional edition feature, please synchronize the license status first in Panel Settings-License interface"
|
||||
ErrXpackOutOfDate: "The current license has expired, please re-import the license in Panel Settings-License interface"
|
||||
|
||||
#task
|
||||
TaskStart: "{{.name}} started [START]"
|
||||
TaskEnd: "{{.name}} ended [COMPLETED]"
|
||||
TaskFailed: "{{.name}} failed: {{.err}}"
|
||||
TaskTimeout: "{{.name}} timed out"
|
||||
TaskSuccess: "{{.name}} succeeded"
|
||||
TaskRetry: "Start {{.name}} retry"
|
||||
SubTaskStart: "Start {{.name}}"
|
||||
|
@ -198,3 +198,12 @@ ErrLicenseSync: "許可證信息同步失敗,資料庫中未檢測到許可證
|
||||
ErrXpackNotFound: "該部分為專業版功能,請先在 面板設置-許可證 界面導入許可證"
|
||||
ErrXpackNotActive: "該部分為專業版功能,請先在 面板設置-許可證 界面同步許可證狀態"
|
||||
ErrXpackOutOfDate: "當前許可證已過期,請重新在 面板設置-許可證 界面導入許可證"
|
||||
|
||||
#task
|
||||
TaskStart: "{{.name}} 開始 [START]"
|
||||
TaskEnd: "{{.name}} 結束 [COMPLETED]"
|
||||
TaskFailed: "{{.name}} 失敗: {{.err}}"
|
||||
TaskTimeout: "{{.name}} 逾時"
|
||||
TaskSuccess: "{{.name}} 成功"
|
||||
TaskRetry: "開始第 {{.name}} 次重試"
|
||||
SubTaskStart: "開始 {{.name}}"
|
||||
|
@ -200,3 +200,11 @@ ErrXpackNotFound: "该部分为专业版功能,请先在 面板设置-许可
|
||||
ErrXpackNotActive: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态"
|
||||
ErrXpackOutOfDate: "当前许可证已过期,请重新在 面板设置-许可证 界面导入许可证"
|
||||
|
||||
#task
|
||||
TaskStart: "{{.name}} 开始 [START]"
|
||||
TaskEnd: "{{.name}} 结束 [COMPLETED]"
|
||||
TaskFailed: "{{.name}} 失败: {{.err}}"
|
||||
TaskTimeout: "{{.name}} 超时"
|
||||
TaskSuccess: "{{.name}} 成功"
|
||||
TaskRetry: "开始第 {{.name}} 次重试"
|
||||
SubTaskStart: "开始 {{.name}}"
|
||||
|
@ -21,7 +21,8 @@ func Init() {
|
||||
constant.LocalAppInstallDir = path.Join(constant.AppInstallDir, "local")
|
||||
constant.RemoteAppResourceDir = path.Join(constant.AppResourceDir, "remote")
|
||||
|
||||
constant.SSLLogDir = path.Join(global.CONF.System.DataDir, "log", "ssl")
|
||||
constant.LogDir = path.Join(global.CONF.System.DataDir, "log")
|
||||
constant.SSLLogDir = path.Join(constant.LogDir, "ssl")
|
||||
|
||||
dirs := []string{constant.DataDir, constant.ResourceDir, constant.AppResourceDir, constant.AppInstallDir,
|
||||
global.CONF.System.Backup, constant.RuntimeDir, constant.LocalAppResourceDir, constant.RemoteAppResourceDir, constant.SSLLogDir}
|
||||
|
Loading…
Reference in New Issue
Block a user