feat: 增加任务引擎 (#5763)

This commit is contained in:
zhengkunwang 2024-07-11 16:21:25 +08:00 committed by zhengkunwang223
parent f3fac23a84
commit afcf509ec0
7 changed files with 177 additions and 1 deletions

115
backend/app/task/task.go Normal file
View 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
}

View File

@ -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")
)

View File

@ -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

View File

@ -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}}"

View File

@ -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}}"

View File

@ -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}}"

View File

@ -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}