mirror of
https://github.com/acepanel/panel.git
synced 2026-02-04 05:31:44 +08:00
feat: 添加计划任务
This commit is contained in:
1
go.mod
1
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
|
||||
|
||||
2
go.sum
2
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=
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -28,4 +28,5 @@ type TaskRepo interface {
|
||||
Delete(id uint) error
|
||||
UpdateStatus(id uint, status TaskStatus) error
|
||||
Push(task *Task) error
|
||||
DispatchWaiting() error
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ func BootWeb() {
|
||||
boot()
|
||||
initValidator()
|
||||
initSession()
|
||||
initCron()
|
||||
initQueue()
|
||||
go initHttp()
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
29
internal/bootstrap/cron.go
Normal file
29
internal/bootstrap/cron.go
Normal file
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
58
internal/job/cert_renew.go
Normal file
58
internal/job/cert_renew.go
Normal file
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
19
internal/job/init.go
Normal file
19
internal/job/init.go
Normal file
@@ -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
|
||||
}
|
||||
69
internal/job/monitoring.go
Normal file
69
internal/job/monitoring.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
51
internal/job/panel_task.go
Normal file
51
internal/job/panel_task.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package job
|
||||
package queuejob
|
||||
|
||||
import (
|
||||
"errors"
|
||||
@@ -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"
|
||||
|
||||
43
pkg/cron/logger.go
Normal file
43
pkg/cron/logger.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user