From afcf509ec05a4d8b960f3d8dc1b7d8f0d84ae3e4 Mon Sep 17 00:00:00 2001 From: zhengkunwang <31820853+zhengkunwang223@users.noreply.github.com> Date: Thu, 11 Jul 2024 16:21:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E5=BC=95=E6=93=8E=20(#5763)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/task/task.go | 115 +++++++++++++++++++++++++++++++++ backend/constant/dir.go | 1 + backend/i18n/i18n.go | 33 ++++++++++ backend/i18n/lang/en.yaml | 9 +++ backend/i18n/lang/zh-Hant.yaml | 9 +++ backend/i18n/lang/zh.yaml | 8 +++ backend/init/app/app.go | 3 +- 7 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 backend/app/task/task.go diff --git a/backend/app/task/task.go b/backend/app/task/task.go new file mode 100644 index 000000000..121d14904 --- /dev/null +++ b/backend/app/task/task.go @@ -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 +} diff --git a/backend/constant/dir.go b/backend/constant/dir.go index 1b474a63e..addcf40be 100644 --- a/backend/constant/dir.go +++ b/backend/constant/dir.go @@ -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") ) diff --git a/backend/i18n/i18n.go b/backend/i18n/i18n.go index b1cc34468..2cc7d4802 100644 --- a/backend/i18n/i18n.go +++ b/backend/i18n/i18n.go @@ -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 diff --git a/backend/i18n/lang/en.yaml b/backend/i18n/lang/en.yaml index a3582c69f..69b99e474 100644 --- a/backend/i18n/lang/en.yaml +++ b/backend/i18n/lang/en.yaml @@ -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}}" diff --git a/backend/i18n/lang/zh-Hant.yaml b/backend/i18n/lang/zh-Hant.yaml index 56a8e2227..817f38e23 100644 --- a/backend/i18n/lang/zh-Hant.yaml +++ b/backend/i18n/lang/zh-Hant.yaml @@ -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}}" diff --git a/backend/i18n/lang/zh.yaml b/backend/i18n/lang/zh.yaml index eca156380..ff3bb1346 100644 --- a/backend/i18n/lang/zh.yaml +++ b/backend/i18n/lang/zh.yaml @@ -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}}" diff --git a/backend/init/app/app.go b/backend/init/app/app.go index 422657961..51cc8c46d 100644 --- a/backend/init/app/app.go +++ b/backend/init/app/app.go @@ -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}