From cdeaf9f48fd9125b14764670736879ed4a7c9b79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=80=97=E5=AD=90?= Date: Wed, 16 Oct 2024 04:09:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E9=AA=8C=E8=AF=81?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/bootstrap/validator.go | 4 ++ internal/data/setting.go | 2 +- internal/http/request/app.go | 8 ++-- internal/http/request/cert.go | 4 +- internal/http/request/cert_account.go | 2 +- internal/http/request/cert_dns.go | 2 +- internal/http/request/cron.go | 10 ++--- internal/http/request/website.go | 10 ++--- internal/http/rule/exists.go | 42 +++++++++++++++++++++ internal/http/rule/not_exists.go | 42 +++++++++++++++++++++ internal/http/rule/regexp.go | 26 +++++++++++++ internal/http/rule/rule.go | 53 +++++++++++++++++++++++++++ internal/job/cert_renew.go | 4 +- internal/job/monitoring.go | 6 +-- internal/job/panel_task.go | 12 +++--- internal/queuejob/process_task.go | 14 +++---- 16 files changed, 204 insertions(+), 37 deletions(-) create mode 100644 internal/http/rule/exists.go create mode 100644 internal/http/rule/not_exists.go create mode 100644 internal/http/rule/regexp.go create mode 100644 internal/http/rule/rule.go diff --git a/internal/bootstrap/validator.go b/internal/bootstrap/validator.go index c1efa24469..e54e8e5fad 100644 --- a/internal/bootstrap/validator.go +++ b/internal/bootstrap/validator.go @@ -9,6 +9,7 @@ import ( "github.com/go-playground/validator/v10/translations/zh" "github.com/TheTNB/panel/internal/app" + "github.com/TheTNB/panel/internal/http/rule" ) func initValidator() { @@ -23,4 +24,7 @@ func initValidator() { app.Translator = &trans app.Validator = validate + if err := rule.RegisterRules(validate); err != nil { + log.Fatalf("failed to register validator rules: %v", err) + } } diff --git a/internal/data/setting.go b/internal/data/setting.go index 96ce50eaf3..aa550accc2 100644 --- a/internal/data/setting.go +++ b/internal/data/setting.go @@ -4,10 +4,10 @@ import ( "context" "errors" "fmt" - "github.com/go-rat/utils/hash" "path/filepath" "slices" + "github.com/go-rat/utils/hash" "github.com/goccy/go-yaml" "github.com/gookit/color" "github.com/spf13/cast" diff --git a/internal/http/request/app.go b/internal/http/request/app.go index 7574952529..86c69cfc6f 100644 --- a/internal/http/request/app.go +++ b/internal/http/request/app.go @@ -1,15 +1,15 @@ package request type App struct { - Slug string `json:"slug" form:"slug" validate:"required"` + Slug string `json:"slug" form:"slug" validate:"required,not_exists=apps slug"` Channel string `json:"channel" form:"channel" validate:"required"` } type AppSlug struct { - Slug string `json:"slug" form:"slug" validate:"required"` + Slug string `json:"slug" form:"slug" validate:"required,exists=apps slug"` } type AppUpdateShow struct { - Slug string `json:"slug" form:"slug" validate:"required"` - Show bool `json:"show" form:"show" validate:"required"` + Slug string `json:"slug" form:"slug" validate:"required,exists=apps slug"` + Show bool `json:"show" form:"show"` } diff --git a/internal/http/request/cert.go b/internal/http/request/cert.go index 12945ca6be..1168617226 100644 --- a/internal/http/request/cert.go +++ b/internal/http/request/cert.go @@ -10,7 +10,7 @@ type CertCreate struct { } type CertUpdate struct { - ID uint `form:"id" json:"id" validate:"required"` + ID uint `form:"id" json:"id" validate:"required,exists=certs id"` Type string `form:"type" json:"type" validate:"required"` Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"` AutoRenew bool `form:"auto_renew" json:"auto_renew"` @@ -20,6 +20,6 @@ type CertUpdate struct { } type CertDeploy struct { - ID uint `form:"id" json:"id" validate:"required"` + ID uint `form:"id" json:"id" validate:"required,exists=certs id"` WebsiteID uint `form:"website_id" json:"website_id" validate:"required"` } diff --git a/internal/http/request/cert_account.go b/internal/http/request/cert_account.go index 808d3fab49..6dfd5da1f5 100644 --- a/internal/http/request/cert_account.go +++ b/internal/http/request/cert_account.go @@ -9,7 +9,7 @@ type CertAccountCreate struct { } type CertAccountUpdate struct { - ID uint `form:"id" json:"id" validate:"required"` + ID uint `form:"id" json:"id" validate:"required,exists=cert_accounts id"` CA string `form:"ca" json:"ca" validate:"required"` Email string `form:"email" json:"email" validate:"required"` Kid string `form:"kid" json:"kid"` diff --git a/internal/http/request/cert_dns.go b/internal/http/request/cert_dns.go index 7226bb7c51..dac5883499 100644 --- a/internal/http/request/cert_dns.go +++ b/internal/http/request/cert_dns.go @@ -9,7 +9,7 @@ type CertDNSCreate struct { } type CertDNSUpdate struct { - ID uint `form:"id" json:"id" validate:"required"` + ID uint `form:"id" json:"id" validate:"required,exists=cert_dns id"` Type string `form:"type" json:"type" validate:"required"` Name string `form:"name" json:"name" validate:"required"` Data acme.DNSParam `form:"data" json:"data"` diff --git a/internal/http/request/cron.go b/internal/http/request/cron.go index 2da5cdd76b..333af9bcd7 100644 --- a/internal/http/request/cron.go +++ b/internal/http/request/cron.go @@ -1,9 +1,9 @@ package request type CronCreate struct { - Name string `form:"name" json:"name" validate:"required"` + Name string `form:"name" json:"name" validate:"required,not_exists=crons name"` Type string `form:"type" json:"type" validate:"required"` - Time string `form:"time" json:"time" validate:"required"` + Time string `form:"time" json:"time" validate:"required,cron"` Script string `form:"script" json:"script"` BackupType string `form:"backup_type" json:"backup_type" validate:"required"` BackupPath string `form:"backup_path" json:"backup_path"` @@ -12,13 +12,13 @@ type CronCreate struct { } type CronUpdate struct { - ID uint `form:"id" json:"id" validate:"required"` + ID uint `form:"id" json:"id" validate:"required,exists=crons id"` Name string `form:"name" json:"name" validate:"required"` - Time string `form:"time" json:"time" validate:"required"` + Time string `form:"time" json:"time" validate:"required,cron"` Script string `form:"script" json:"script" validate:"required"` } type CronStatus struct { - ID uint `form:"id" json:"id" validate:"required"` + ID uint `form:"id" json:"id" validate:"required,exists=crons id"` Status bool `form:"status" json:"status"` } diff --git a/internal/http/request/website.go b/internal/http/request/website.go index 238cdaa8d3..1b309889cc 100644 --- a/internal/http/request/website.go +++ b/internal/http/request/website.go @@ -8,7 +8,7 @@ type WebsiteDefaultConfig struct { } type WebsiteCreate struct { - Name string `form:"name" json:"name" validate:"required"` + Name string `form:"name" json:"name" validate:"required,not_exists=websites name"` Listens []string `form:"listens" json:"listens" validate:"min=1,dive,required"` Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"` Path string `form:"path" json:"path"` @@ -21,13 +21,13 @@ type WebsiteCreate struct { } type WebsiteDelete struct { - ID uint `form:"id" json:"id" validate:"required"` + ID uint `form:"id" json:"id" validate:"required,exists=websites id"` Path bool `form:"path" json:"path"` DB bool `form:"db" json:"db"` } type WebsiteUpdate struct { - ID uint `form:"id" json:"id" validate:"required"` + ID uint `form:"id" json:"id" validate:"required,exists=websites id"` Listens []types.WebsiteListen `form:"listens" json:"listens" validate:"min=1"` Domains []string `form:"domains" json:"domains" validate:"min=1,dive,required"` HTTPS bool `form:"https" json:"https"` @@ -46,11 +46,11 @@ type WebsiteUpdate struct { } type WebsiteUpdateRemark struct { - ID uint `form:"id" json:"id" validate:"required"` + ID uint `form:"id" json:"id" validate:"required,exists=websites id"` Remark string `form:"remark" json:"remark"` } type WebsiteUpdateStatus struct { - ID uint `json:"id" form:"id" validate:"required"` + ID uint `json:"id" form:"id" validate:"required,exists=websites id"` Status bool `json:"status" form:"status"` } diff --git a/internal/http/rule/exists.go b/internal/http/rule/exists.go new file mode 100644 index 0000000000..f43b4e6689 --- /dev/null +++ b/internal/http/rule/exists.go @@ -0,0 +1,42 @@ +package rule + +import ( + "fmt" + "strings" + + "github.com/go-playground/validator/v10" + "gorm.io/gorm" +) + +type Exists struct { + DB *gorm.DB +} + +func NewExists(db *gorm.DB) *Exists { + return &Exists{DB: db} +} + +// Exists 格式 `exists=categories,id,other_field` +func (r *Exists) Exists(fl validator.FieldLevel) bool { + requestValue := fl.Field().Interface() + params := strings.Fields(fl.Param()) + if len(params) < 2 { + return false + } + + tableName := params[0] + fieldNames := params[1:] + + query := r.DB.Table(tableName).Where(fmt.Sprintf("%s = ?", fieldNames[0]), requestValue) + for _, fieldName := range fieldNames[1:] { + query = query.Or(fmt.Sprintf("%s = ?", fieldName), requestValue) + } + + var count int64 + err := query.Count(&count).Error + if err != nil { + return false + } + + return count != 0 +} diff --git a/internal/http/rule/not_exists.go b/internal/http/rule/not_exists.go new file mode 100644 index 0000000000..f86ebe99e0 --- /dev/null +++ b/internal/http/rule/not_exists.go @@ -0,0 +1,42 @@ +package rule + +import ( + "fmt" + "strings" + + "github.com/go-playground/validator/v10" + "gorm.io/gorm" +) + +type NotExists struct { + DB *gorm.DB +} + +func NewNotExists(db *gorm.DB) *NotExists { + return &NotExists{DB: db} +} + +// NotExists 格式 `not_exists=categories,id,other_field` +func (r *NotExists) NotExists(fl validator.FieldLevel) bool { + requestValue := fl.Field().Interface() + params := strings.Fields(fl.Param()) + if len(params) < 2 { + return false + } + + tableName := params[0] + fieldNames := params[1:] + + query := r.DB.Table(tableName).Where(fmt.Sprintf("%s = ?", fieldNames[0]), requestValue) + for _, fieldName := range fieldNames[1:] { + query = query.Or(fmt.Sprintf("%s = ?", fieldName), requestValue) + } + + var count int64 + err := query.Count(&count).Error + if err != nil { + return false + } + + return count == 0 +} diff --git a/internal/http/rule/regexp.go b/internal/http/rule/regexp.go new file mode 100644 index 0000000000..0b0659b64b --- /dev/null +++ b/internal/http/rule/regexp.go @@ -0,0 +1,26 @@ +package rule + +import ( + "regexp" + + "github.com/go-playground/validator/v10" +) + +type Regexp struct{} + +func NewRegexp() *Regexp { + return &Regexp{} +} + +func (r *Regexp) Regexp(fl validator.FieldLevel) bool { + // 从标签中获取正则,格式类似于 `regexp=^[a-zA-Z0-9_]+$` + pattern := fl.Param() + value := fl.Field().String() + + re, err := regexp.Compile(pattern) + if err != nil { + return false + } + + return re.MatchString(value) +} diff --git a/internal/http/rule/rule.go b/internal/http/rule/rule.go new file mode 100644 index 0000000000..37e181720f --- /dev/null +++ b/internal/http/rule/rule.go @@ -0,0 +1,53 @@ +package rule + +import ( + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + + "github.com/TheTNB/panel/internal/app" +) + +func RegisterRules(v *validator.Validate) error { + if err := v.RegisterValidation("exists", NewExists(app.Orm).Exists); err != nil { + return err + } + if err := v.RegisterValidation("not_exists", NewNotExists(app.Orm).NotExists); err != nil { + return err + } + if err := v.RegisterValidation("regexp", NewRegexp().Regexp); err != nil { + return err + } + + if err := v.RegisterTranslation("exists", *app.Translator, + func(ut ut.Translator) error { + return ut.Add("exists", "{0} 不存在", true) + }, + func(ut ut.Translator, fe validator.FieldError) string { + t, _ := ut.T("exists", fe.Field()) + return t + }); err != nil { + return err + } + if err := v.RegisterTranslation("not_exists", *app.Translator, + func(ut ut.Translator) error { + return ut.Add("not_exists", "{0} 已存在", true) + }, + func(ut ut.Translator, fe validator.FieldError) string { + t, _ := ut.T("not_exists", fe.Field()) + return t + }); err != nil { + return err + } + if err := v.RegisterTranslation("regexp", *app.Translator, + func(ut ut.Translator) error { + return ut.Add("regexp", "{0} 格式不正确", true) + }, + func(ut ut.Translator, fe validator.FieldError) string { + t, _ := ut.T("regexp", fe.Field()) + return t + }); err != nil { + return err + } + + return nil +} diff --git a/internal/job/cert_renew.go b/internal/job/cert_renew.go index 0d2c3c303d..f57f7979e5 100644 --- a/internal/job/cert_renew.go +++ b/internal/job/cert_renew.go @@ -23,7 +23,7 @@ func NewCertRenew() *CertRenew { } } -func (receiver *CertRenew) Run() { +func (r *CertRenew) Run() { if types.Status != types.StatusNormal { return } @@ -50,7 +50,7 @@ func (receiver *CertRenew) Run() { continue } - _, err = receiver.certRepo.Renew(cert.ID) + _, err = r.certRepo.Renew(cert.ID) if err != nil { app.Logger.Error("续签证书失败", zap.Error(err)) } diff --git a/internal/job/monitoring.go b/internal/job/monitoring.go index b178f15c35..09d4830d11 100644 --- a/internal/job/monitoring.go +++ b/internal/job/monitoring.go @@ -24,7 +24,7 @@ func NewMonitoring() *Monitoring { } } -func (receiver *Monitoring) Run() { +func (r *Monitoring) Run() { if types.Status != types.StatusNormal { return } @@ -33,7 +33,7 @@ func (receiver *Monitoring) Run() { //task := data.NewTaskRepo() //_ = task.DispatchWaiting() - monitor, err := receiver.settingRepo.Get(biz.SettingKeyMonitor) + monitor, err := r.settingRepo.Get(biz.SettingKeyMonitor) if err != nil || !cast.ToBool(monitor) { return } @@ -54,7 +54,7 @@ func (receiver *Monitoring) Run() { } // 删除过期数据 - dayStr, err := receiver.settingRepo.Get(biz.SettingKeyMonitorDays) + dayStr, err := r.settingRepo.Get(biz.SettingKeyMonitorDays) if err != nil { return } diff --git a/internal/job/panel_task.go b/internal/job/panel_task.go index 474e209a98..eda528caf8 100644 --- a/internal/job/panel_task.go +++ b/internal/job/panel_task.go @@ -27,7 +27,7 @@ func NewPanelTask() *PanelTask { } } -func (receiver *PanelTask) Run() { +func (r *PanelTask) Run() { types.Status = types.StatusMaintain // 优化数据库 @@ -41,21 +41,21 @@ func (receiver *PanelTask) Run() { } // 备份面板 - if err := receiver.backupRepo.Create(biz.BackupTypePanel, ""); err != nil { + if err := r.backupRepo.Create(biz.BackupTypePanel, ""); err != nil { app.Logger.Error("备份面板失败", zap.Error(err)) } // 清理备份 - path, err := receiver.backupRepo.GetPath("panel") + path, err := r.backupRepo.GetPath("panel") if err == nil { - if err = receiver.backupRepo.ClearExpired(path, "panel_", 10); err != nil { + if err = r.backupRepo.ClearExpired(path, "panel_", 10); err != nil { app.Logger.Error("清理面板备份失败", zap.Error(err)) } } // 更新商店缓存 - if offline, err := receiver.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline { - if err = receiver.appRepo.UpdateCache(); err != nil { + if offline, err := r.settingRepo.GetBool(biz.SettingKeyOfflineMode); err == nil && !offline { + if err = r.appRepo.UpdateCache(); err != nil { app.Logger.Error("更新商店缓存失败", zap.Error(err)) } } diff --git a/internal/queuejob/process_task.go b/internal/queuejob/process_task.go index 12ad722216..05d04b5371 100644 --- a/internal/queuejob/process_task.go +++ b/internal/queuejob/process_task.go @@ -20,19 +20,19 @@ func NewProcessTask(taskRepo biz.TaskRepo) *ProcessTask { } } -func (receiver *ProcessTask) Handle(args ...any) error { +func (r *ProcessTask) Handle(args ...any) error { taskID, ok := args[0].(uint) if !ok { return errors.New("参数错误") } - receiver.taskID = taskID + r.taskID = taskID - task, err := receiver.taskRepo.Get(taskID) + task, err := r.taskRepo.Get(taskID) if err != nil { return err } - if err = receiver.taskRepo.UpdateStatus(taskID, biz.TaskStatusRunning); err != nil { + if err = r.taskRepo.UpdateStatus(taskID, biz.TaskStatusRunning); err != nil { return err } @@ -40,13 +40,13 @@ func (receiver *ProcessTask) Handle(args ...any) error { return err } - if err = receiver.taskRepo.UpdateStatus(taskID, biz.TaskStatusSuccess); err != nil { + if err = r.taskRepo.UpdateStatus(taskID, biz.TaskStatusSuccess); err != nil { return err } return nil } -func (receiver *ProcessTask) ErrHandle(err error) { - _ = receiver.taskRepo.UpdateStatus(receiver.taskID, biz.TaskStatusFailed) +func (r *ProcessTask) ErrHandle(err error) { + _ = r.taskRepo.UpdateStatus(r.taskID, biz.TaskStatusFailed) }