From 96d7e1463193e24371adde872878464d9fcbfca7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Sat, 12 Oct 2024 02:25:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=AE=A1=E5=88=92?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 1 + go.sum | 2 + internal/app/global.go | 2 + internal/biz/task.go | 1 + internal/bootstrap/app.go | 1 + internal/bootstrap/conf.go | 4 +- internal/bootstrap/cron.go | 29 +++++++++ internal/data/task.go | 19 +++++- internal/job/cert_renew.go | 58 ++++++++++++++++++ internal/job/init.go | 19 ++++++ internal/job/monitoring.go | 69 ++++++++++++++++++++++ internal/job/panel_task.go | 51 ++++++++++++++++ internal/{job => queuejob}/process_task.go | 2 +- internal/service/cli.go | 2 +- pkg/cron/logger.go | 43 ++++++++++++++ 15 files changed, 297 insertions(+), 6 deletions(-) create mode 100644 internal/bootstrap/cron.go create mode 100644 internal/job/cert_renew.go create mode 100644 internal/job/init.go create mode 100644 internal/job/monitoring.go create mode 100644 internal/job/panel_task.go rename internal/{job => queuejob}/process_task.go (98%) create mode 100644 pkg/cron/logger.go diff --git a/go.mod b/go.mod index f7758afd..b671e08e 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,7 @@ require ( github.com/libdns/tencentcloud v1.0.0 github.com/mholt/acmez/v2 v2.0.3 github.com/mholt/archiver/v4 v4.0.0-alpha.8 + github.com/robfig/cron/v3 v3.0.1 github.com/sethvargo/go-limiter v1.0.0 github.com/shirou/gopsutil v2.21.11+incompatible github.com/spf13/cast v1.7.0 diff --git a/go.sum b/go.sum index 1118d992..98bd8d4b 100644 --- a/go.sum +++ b/go.sum @@ -263,6 +263,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= diff --git a/internal/app/global.go b/internal/app/global.go index 54d22d1c..3760b751 100644 --- a/internal/app/global.go +++ b/internal/app/global.go @@ -6,6 +6,7 @@ import ( "github.com/go-playground/validator/v10" "github.com/go-rat/sessions" "github.com/knadh/koanf/v2" + "github.com/robfig/cron/v3" "go.uber.org/zap" "gorm.io/gorm" @@ -19,6 +20,7 @@ var ( Validator *validator.Validate Translator *ut.Translator Session *sessions.Manager + Cron *cron.Cron Queue *queue.Queue Logger *zap.Logger ) diff --git a/internal/biz/task.go b/internal/biz/task.go index 35a8d586..4a037996 100644 --- a/internal/biz/task.go +++ b/internal/biz/task.go @@ -28,4 +28,5 @@ type TaskRepo interface { Delete(id uint) error UpdateStatus(id uint, status TaskStatus) error Push(task *Task) error + DispatchWaiting() error } diff --git a/internal/bootstrap/app.go b/internal/bootstrap/app.go index 53ea0428..7dfc194a 100644 --- a/internal/bootstrap/app.go +++ b/internal/bootstrap/app.go @@ -19,6 +19,7 @@ func BootWeb() { boot() initValidator() initSession() + initCron() initQueue() go initHttp() diff --git a/internal/bootstrap/conf.go b/internal/bootstrap/conf.go index 0b5713d0..cca55b2c 100644 --- a/internal/bootstrap/conf.go +++ b/internal/bootstrap/conf.go @@ -7,11 +7,11 @@ import ( "strings" "time" + "github.com/knadh/koanf/parsers/yaml" + "github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/v2" "github.com/TheTNB/panel/internal/app" - "github.com/knadh/koanf/parsers/yaml" - "github.com/knadh/koanf/providers/file" ) func initConf() { diff --git a/internal/bootstrap/cron.go b/internal/bootstrap/cron.go new file mode 100644 index 00000000..3451f138 --- /dev/null +++ b/internal/bootstrap/cron.go @@ -0,0 +1,29 @@ +package bootstrap + +import ( + "fmt" + + "github.com/robfig/cron/v3" + + "github.com/TheTNB/panel/internal/app" + "github.com/TheTNB/panel/internal/job" + pkgcron "github.com/TheTNB/panel/pkg/cron" +) + +func initCron() { + logger := pkgcron.NewLogger(app.Logger, app.Conf.Bool("app.debug")) + c := cron.New( + cron.WithParser(cron.NewParser( + cron.SecondOptional|cron.Minute|cron.Hour|cron.Dom|cron.Month|cron.Dow|cron.Descriptor, + )), + cron.WithLogger(logger), + cron.WithChain(cron.Recover(logger), cron.SkipIfStillRunning(logger)), + ) + app.Cron = c + + if err := job.Boot(app.Cron); err != nil { + panic(fmt.Sprintf("failed to boot cron jobs: %v", err)) + } + + c.Start() +} diff --git a/internal/data/task.go b/internal/data/task.go index f51f9a72..f7deb4b7 100644 --- a/internal/data/task.go +++ b/internal/data/task.go @@ -3,7 +3,7 @@ package data import ( "github.com/TheTNB/panel/internal/app" "github.com/TheTNB/panel/internal/biz" - "github.com/TheTNB/panel/internal/job" + "github.com/TheTNB/panel/internal/queuejob" ) type taskRepo struct{} @@ -43,7 +43,22 @@ func (r *taskRepo) Push(task *biz.Task) error { if err := app.Orm.Create(task).Error; err != nil { return err } - return app.Queue.Push(job.NewProcessTask(r), []any{ + return app.Queue.Push(queuejob.NewProcessTask(r), []any{ task.ID, }) } + +func (r *taskRepo) DispatchWaiting() error { + var tasks []biz.Task + if err := app.Orm.Where("status = ?", biz.TaskStatusWaiting).Find(&tasks).Error; err != nil { + return err + } + + for _, task := range tasks { + if err := r.Push(&task); err != nil { + return err + } + } + + return nil +} diff --git a/internal/job/cert_renew.go b/internal/job/cert_renew.go new file mode 100644 index 00000000..c3e7ec3b --- /dev/null +++ b/internal/job/cert_renew.go @@ -0,0 +1,58 @@ +package job + +import ( + "time" + + "go.uber.org/zap" + + "github.com/TheTNB/panel/internal/app" + "github.com/TheTNB/panel/internal/biz" + "github.com/TheTNB/panel/internal/data" + pkgcert "github.com/TheTNB/panel/pkg/cert" + "github.com/TheTNB/panel/pkg/types" +) + +// CertRenew 证书续签 +type CertRenew struct { + cert biz.CertRepo +} + +func NewCertRenew() *CertRenew { + return &CertRenew{ + cert: data.NewCertRepo(), + } +} + +func (receiver *CertRenew) Run() { + if types.Status != types.StatusNormal { + return + } + + var certs []biz.Cert + if err := app.Orm.Preload("Website").Preload("Account").Preload("DNS").Find(&certs).Error; err != nil { + app.Logger.Error("获取证书失败", zap.Error(err)) + return + } + + for _, cert := range certs { + if !cert.AutoRenew { + continue + } + + decode, err := pkgcert.ParseCert(cert.Cert) + if err != nil { + continue + } + + // 结束时间大于 7 天的证书不续签 + now := time.Now() + if decode.NotAfter.Sub(now).Hours() > 24*7 { + continue + } + + _, err = receiver.cert.Renew(cert.ID) + if err != nil { + app.Logger.Error("续签证书失败", zap.Error(err)) + } + } +} diff --git a/internal/job/init.go b/internal/job/init.go new file mode 100644 index 00000000..b1b812c3 --- /dev/null +++ b/internal/job/init.go @@ -0,0 +1,19 @@ +package job + +import ( + "github.com/robfig/cron/v3" +) + +func Boot(c *cron.Cron) error { + if _, err := c.AddJob("* * * * *", NewMonitoring()); err != nil { + return err + } + if _, err := c.AddJob("0 4 * * *", NewCertRenew()); err != nil { + return err + } + if _, err := c.AddJob("0 2 * * *", NewPanelTask()); err != nil { + return err + } + + return nil +} diff --git a/internal/job/monitoring.go b/internal/job/monitoring.go new file mode 100644 index 00000000..7ce8a33c --- /dev/null +++ b/internal/job/monitoring.go @@ -0,0 +1,69 @@ +package job + +import ( + "time" + + "github.com/spf13/cast" + "go.uber.org/zap" + + "github.com/TheTNB/panel/internal/app" + "github.com/TheTNB/panel/internal/biz" + "github.com/TheTNB/panel/internal/data" + "github.com/TheTNB/panel/pkg/tools" + "github.com/TheTNB/panel/pkg/types" +) + +// Monitoring 系统监控 +type Monitoring struct { + setting biz.SettingRepo +} + +func NewMonitoring() *Monitoring { + return &Monitoring{ + setting: data.NewSettingRepo(), + } +} + +func (receiver *Monitoring) Run() { + if types.Status != types.StatusNormal { + return + } + + // 将等待中的任务分发 + //task := data.NewTaskRepo() + //_ = task.DispatchWaiting() + + monitor, err := receiver.setting.Get(biz.SettingKeyMonitor) + if err != nil || !cast.ToBool(monitor) { + return + } + + info := tools.GetMonitoringInfo() + + // 去除部分数据以减少数据库存储 + info.Disk = nil + info.Cpus = nil + + if types.Status != types.StatusNormal { + return + } + + if err = app.Orm.Create(&biz.Monitor{Info: info}).Error; err != nil { + app.Logger.Error("记录系统监控失败", zap.Error(err)) + return + } + + // 删除过期数据 + dayStr, err := receiver.setting.Get(biz.SettingKeyMonitorDays) + if err != nil { + return + } + day := cast.ToInt(dayStr) + if day <= 0 || types.Status != types.StatusNormal { + return + } + if err = app.Orm.Where("created_at < ?", time.Now().AddDate(0, 0, -day).Format("2006-01-02 15:04:05")).Delete(&biz.Monitor{}).Error; err != nil { + app.Logger.Error("删除过期系统监控失败", zap.Error(err)) + return + } +} diff --git a/internal/job/panel_task.go b/internal/job/panel_task.go new file mode 100644 index 00000000..8b162a3a --- /dev/null +++ b/internal/job/panel_task.go @@ -0,0 +1,51 @@ +package job + +import ( + "path/filepath" + "runtime" + "runtime/debug" + "time" + + "go.uber.org/zap" + + "github.com/TheTNB/panel/internal/app" + "github.com/TheTNB/panel/pkg/io" + "github.com/TheTNB/panel/pkg/shell" + "github.com/TheTNB/panel/pkg/types" +) + +// PanelTask 面板每日任务 +type PanelTask struct { +} + +func NewPanelTask() *PanelTask { + return &PanelTask{} +} + +func (receiver *PanelTask) Run() { + types.Status = types.StatusMaintain + + // 优化数据库 + if err := app.Orm.Exec("VACUUM").Error; err != nil { + types.Status = types.StatusFailed + app.Logger.Error("优化面板数据库失败", zap.Error(err)) + } + + // 备份面板 + if err := io.Compress([]string{"/www/panel"}, filepath.Join(app.Root, "backup", "panel", "panel-"+time.Now().Format(time.DateOnly)+".zip"), io.Zip); err != nil { + types.Status = types.StatusFailed + app.Logger.Error("备份面板失败", zap.Error(err)) + } + + // 清理 7 天前的备份 + if _, err := shell.Execf(`find %s -mtime +7 -name "*.zip" -exec rm -rf {} \;`, filepath.Join(app.Root, "backup", "panel")); err != nil { + types.Status = types.StatusFailed + app.Logger.Error("清理面板备份失败", zap.Error(err)) + } + + // 回收内存 + runtime.GC() + debug.FreeOSMemory() + + types.Status = types.StatusNormal +} diff --git a/internal/job/process_task.go b/internal/queuejob/process_task.go similarity index 98% rename from internal/job/process_task.go rename to internal/queuejob/process_task.go index 380fe1ce..12ad7222 100644 --- a/internal/job/process_task.go +++ b/internal/queuejob/process_task.go @@ -1,4 +1,4 @@ -package job +package queuejob import ( "errors" diff --git a/internal/service/cli.go b/internal/service/cli.go index 272cedc9..91945174 100644 --- a/internal/service/cli.go +++ b/internal/service/cli.go @@ -4,13 +4,13 @@ import ( "context" "errors" "fmt" - "gorm.io/gorm" "path/filepath" "github.com/go-rat/utils/hash" "github.com/goccy/go-yaml" "github.com/gookit/color" "github.com/urfave/cli/v3" + "gorm.io/gorm" "github.com/TheTNB/panel/internal/app" "github.com/TheTNB/panel/internal/biz" diff --git a/pkg/cron/logger.go b/pkg/cron/logger.go new file mode 100644 index 00000000..78b649ba --- /dev/null +++ b/pkg/cron/logger.go @@ -0,0 +1,43 @@ +package cron + +import "go.uber.org/zap" + +type Logger struct { + log *zap.Logger + debug bool +} + +func NewLogger(log *zap.Logger, debug bool) *Logger { + return &Logger{ + debug: debug, + log: log, + } +} + +func (log *Logger) Info(msg string, keysAndValues ...any) { + if !log.debug { + return + } + + log.log.Info(msg, log.toZapFields(keysAndValues...)...) +} + +func (log *Logger) Error(err error, msg string, keysAndValues ...any) { + fields := log.toZapFields(keysAndValues...) + fields = append(fields, zap.Error(err)) + log.log.Error(msg, fields...) +} + +func (log *Logger) toZapFields(keysAndValues ...any) []zap.Field { + fields := make([]zap.Field, 0, len(keysAndValues)/2) + for i := 0; i < len(keysAndValues); i += 2 { + if i+1 < len(keysAndValues) { + key, okKey := keysAndValues[i].(string) + value := keysAndValues[i+1] + if okKey { + fields = append(fields, zap.Any(key, value)) + } + } + } + return fields +}