From b6cae7bbb53a920060a257efd95e34be8efb62f1 Mon Sep 17 00:00:00 2001 From: Chang lue Tsen Date: Mon, 1 Sep 2025 06:11:35 -0400 Subject: [PATCH] feat(api): add log settings management with auto-clear and clear days configuration --- apis/admin/log.api | 12 ++++ .../database/02107_log_setting.down.sql | 0 .../migrate/database/02107_log_setting.up.sql | 4 ++ internal/config/config.go | 6 ++ .../handler/admin/log/getLogSettingHandler.go | 18 ++++++ .../admin/log/updateLogSettingHandler.go | 26 ++++++++ internal/handler/routes.go | 6 ++ .../admin/log/filterServerTrafficLogLogic.go | 18 +++++- .../logic/admin/log/getLogSettingLogic.go | 37 +++++++++++ .../logic/admin/log/updateLogSettingLogic.go | 63 +++++++++++++++++++ internal/model/system/model.go | 10 +++ internal/types/types.go | 5 ++ .../subscription/checkSubscriptionLogic.go | 2 +- queue/logic/traffic/trafficStatLogic.go | 9 +-- 14 files changed, 210 insertions(+), 6 deletions(-) create mode 100644 initialize/migrate/database/02107_log_setting.down.sql create mode 100644 initialize/migrate/database/02107_log_setting.up.sql create mode 100644 internal/handler/admin/log/getLogSettingHandler.go create mode 100644 internal/handler/admin/log/updateLogSettingHandler.go create mode 100644 internal/logic/admin/log/getLogSettingLogic.go create mode 100644 internal/logic/admin/log/updateLogSettingLogic.go diff --git a/apis/admin/log.api b/apis/admin/log.api index 1cd597e..2ecefa9 100644 --- a/apis/admin/log.api +++ b/apis/admin/log.api @@ -185,6 +185,10 @@ type ( Total int64 `json:"total"` List []TrafficLogDetails `json:"list"` } + LogSetting { + AutoClear *bool `json:"auto_clear"` + ClearDays int64 `json:"clear_days"` + } ) @server ( @@ -244,5 +248,13 @@ service ppanel { @doc "Filter traffic log details" @handler FilterTrafficLogDetails get /traffic/details (FilterTrafficLogDetailsRequest) returns (FilterTrafficLogDetailsResponse) + + @doc "Get log setting" + @handler GetLogSetting + get /setting returns (LogSetting) + + @doc "Update log setting" + @handler UpdateLogSetting + post /setting (LogSetting) } diff --git a/initialize/migrate/database/02107_log_setting.down.sql b/initialize/migrate/database/02107_log_setting.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/initialize/migrate/database/02107_log_setting.up.sql b/initialize/migrate/database/02107_log_setting.up.sql new file mode 100644 index 0000000..c1bc8e2 --- /dev/null +++ b/initialize/migrate/database/02107_log_setting.up.sql @@ -0,0 +1,4 @@ +INSERT IGNORE INTO `system` (`category`, `key`, `value`, `type`, `desc`, `created_at`, `updated_at`) +VALUES + ('log', 'AutoClear', 'true', 'bool', 'Auto Clear Log', '2025-04-22 14:25:16.637', '2025-04-22 14:25:16.637'), + ('log', 'ClearDays', '7', 'int', 'Clear Days', '2025-04-22 14:25:16.637','2025-04-22 14:25:16.637'); \ No newline at end of file diff --git a/internal/config/config.go b/internal/config/config.go index 6758559..9065bcb 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,6 +25,7 @@ type Config struct { Subscribe SubscribeConfig `yaml:"Subscribe"` Invite InviteConfig `yaml:"Invite"` Telegram Telegram `yaml:"Telegram"` + Log Log `yaml:"Log"` Administrator struct { Email string `yaml:"Email" default:"admin@ppanel.dev"` Password string `yaml:"Password" default:"password"` @@ -146,3 +147,8 @@ type VerifyCode struct { Limit int64 `yaml:"Limit" default:"15"` Interval int64 `yaml:"Interval" default:"60"` } + +type Log struct { + AutoClear bool `yaml:"AutoClear" default:"true"` + ClearDays int64 `yaml:"ClearDays" default:"7"` +} diff --git a/internal/handler/admin/log/getLogSettingHandler.go b/internal/handler/admin/log/getLogSettingHandler.go new file mode 100644 index 0000000..50217cb --- /dev/null +++ b/internal/handler/admin/log/getLogSettingHandler.go @@ -0,0 +1,18 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/result" +) + +// Get log setting +func GetLogSettingHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + + l := log.NewGetLogSettingLogic(c.Request.Context(), svcCtx) + resp, err := l.GetLogSetting() + result.HttpResult(c, resp, err) + } +} diff --git a/internal/handler/admin/log/updateLogSettingHandler.go b/internal/handler/admin/log/updateLogSettingHandler.go new file mode 100644 index 0000000..91aa2c8 --- /dev/null +++ b/internal/handler/admin/log/updateLogSettingHandler.go @@ -0,0 +1,26 @@ +package log + +import ( + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/admin/log" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/result" +) + +// Update log setting +func UpdateLogSettingHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) { + return func(c *gin.Context) { + var req types.LogSetting + _ = c.ShouldBind(&req) + validateErr := svcCtx.Validate(&req) + if validateErr != nil { + result.ParamErrorResult(c, validateErr) + return + } + + l := log.NewUpdateLogSettingLogic(c.Request.Context(), svcCtx) + err := l.UpdateLogSetting(&req) + result.HttpResult(c, nil, err) + } +} diff --git a/internal/handler/routes.go b/internal/handler/routes.go index dfff2ee..8af162d 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -216,6 +216,12 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { // Filter server traffic log adminLogGroupRouter.GET("/server/traffic/list", adminLog.FilterServerTrafficLogHandler(serverCtx)) + // Get log setting + adminLogGroupRouter.GET("/setting", adminLog.GetLogSettingHandler(serverCtx)) + + // Update log setting + adminLogGroupRouter.POST("/setting", adminLog.UpdateLogSettingHandler(serverCtx)) + // Filter subscribe log adminLogGroupRouter.GET("/subscribe/list", adminLog.FilterSubscribeLogHandler(serverCtx)) diff --git a/internal/logic/admin/log/filterServerTrafficLogLogic.go b/internal/logic/admin/log/filterServerTrafficLogLogic.go index 92f73a1..644d817 100644 --- a/internal/logic/admin/log/filterServerTrafficLogLogic.go +++ b/internal/logic/admin/log/filterServerTrafficLogLogic.go @@ -95,13 +95,29 @@ func (l *FilterServerTrafficLogLogic) FilterServerTrafficLog(req *types.FilterSe l.Errorw("[FilterServerTrafficLog] Unmarshal Error", logger.Field("error", err.Error()), logger.Field("content", item.Content)) continue } + + hasDetails := true + if l.svcCtx.Config.Log.AutoClear { + last := now.AddDate(0, 0, int(-l.svcCtx.Config.Log.ClearDays)) + dataTime, err := time.Parse(time.DateOnly, item.Date) + if err != nil { + l.Errorw("[FilterServerTrafficLog] Parse Date Error", logger.Field("error", err.Error()), logger.Field("date", item.Date)) + } else { + if dataTime.Before(last) { + hasDetails = false + } else { + hasDetails = true + } + } + } + list = append(list, types.ServerTrafficLog{ ServerId: item.ObjectID, Upload: content.Upload, Download: content.Download, Total: content.Total, Date: item.Date, - Details: false, + Details: hasDetails, }) } diff --git a/internal/logic/admin/log/getLogSettingLogic.go b/internal/logic/admin/log/getLogSettingLogic.go new file mode 100644 index 0000000..568d7e0 --- /dev/null +++ b/internal/logic/admin/log/getLogSettingLogic.go @@ -0,0 +1,37 @@ +package log + +import ( + "context" + + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" +) + +type GetLogSettingLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// Get log setting +func NewGetLogSettingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetLogSettingLogic { + return &GetLogSettingLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetLogSettingLogic) GetLogSetting() (resp *types.LogSetting, err error) { + configs, err := l.svcCtx.SystemModel.GetLogConfig(l.ctx) + if err != nil { + l.Errorw("[GetLogSetting] Database query error", logger.Field("error", err.Error())) + return nil, err + } + resp = &types.LogSetting{} + // reflect to response + tool.SystemConfigSliceReflectToStruct(configs, resp) + return +} diff --git a/internal/logic/admin/log/updateLogSettingLogic.go b/internal/logic/admin/log/updateLogSettingLogic.go new file mode 100644 index 0000000..16cbc9f --- /dev/null +++ b/internal/logic/admin/log/updateLogSettingLogic.go @@ -0,0 +1,63 @@ +package log + +import ( + "context" + "reflect" + + "github.com/perfect-panel/server/internal/config" + "github.com/perfect-panel/server/internal/model/system" + "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/internal/types" + "github.com/perfect-panel/server/pkg/logger" + "github.com/perfect-panel/server/pkg/tool" + "github.com/perfect-panel/server/pkg/xerr" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +type UpdateLogSettingLogic struct { + logger.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +// NewUpdateLogSettingLogic Update log setting +func NewUpdateLogSettingLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateLogSettingLogic { + return &UpdateLogSettingLogic{ + Logger: logger.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *UpdateLogSettingLogic) UpdateLogSetting(req *types.LogSetting) error { + v := reflect.ValueOf(*req) + // Get the reflection type of the structure + t := v.Type() + err := l.svcCtx.SystemModel.Transaction(l.ctx, func(db *gorm.DB) error { + var err error + for i := 0; i < v.NumField(); i++ { + // Get the field name + fieldName := t.Field(i).Name + // Get the field value to string + fieldValue := tool.ConvertValueToString(v.Field(i)) + // Update the server config + err = db.Model(&system.System{}).Where("`category` = 'server' and `key` = ?", fieldName).Update("value", fieldValue).Error + if err != nil { + break + } + } + return err + }) + if err != nil { + l.Errorw("[UpdateLogSetting] update log setting error", logger.Field("error", err.Error())) + return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), " update log setting error: %v", err) + } + + l.svcCtx.Config.Log = config.Log{ + AutoClear: *req.AutoClear, + ClearDays: req.ClearDays, + } + + return nil +} diff --git a/internal/model/system/model.go b/internal/model/system/model.go index 60cef38..a7c28e1 100644 --- a/internal/model/system/model.go +++ b/internal/model/system/model.go @@ -19,6 +19,7 @@ type customSystemLogicModel interface { GetTosConfig(ctx context.Context) ([]*System, error) GetCurrencyConfig(ctx context.Context) ([]*System, error) GetVerifyCodeConfig(ctx context.Context) ([]*System, error) + GetLogConfig(ctx context.Context) ([]*System, error) UpdateNodeMultiplierConfig(ctx context.Context, config string) error FindNodeMultiplierConfig(ctx context.Context) (*System, error) } @@ -152,3 +153,12 @@ func (m *customSystemModel) GetVerifyCodeConfig(ctx context.Context) ([]*System, }) return configs, err } + +// GetLogConfig returns the log config. +func (m *customSystemModel) GetLogConfig(ctx context.Context) ([]*System, error) { + var configs []*System + err := m.QueryNoCacheCtx(ctx, &configs, func(conn *gorm.DB, v interface{}) error { + return conn.Where("`category` = ?", "log").Find(v).Error + }) + return configs, err +} diff --git a/internal/types/types.go b/internal/types/types.go index 9c3e6f1..5721a19 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -1143,6 +1143,11 @@ type LogResponse struct { List interface{} `json:"list"` } +type LogSetting struct { + AutoClear *bool `json:"auto_clear"` + ClearDays int64 `json:"clear_days"` +} + type LoginLog struct { UserId int64 `json:"user_id"` Method string `json:"method"` diff --git a/queue/logic/subscription/checkSubscriptionLogic.go b/queue/logic/subscription/checkSubscriptionLogic.go index 1bb0340..c331de8 100644 --- a/queue/logic/subscription/checkSubscriptionLogic.go +++ b/queue/logic/subscription/checkSubscriptionLogic.go @@ -199,7 +199,7 @@ func (l *CheckSubscriptionLogic) sendTrafficNotify(ctx context.Context, subs []i } func (l *CheckSubscriptionLogic) clearServerCache(ctx context.Context, userSubs ...*user.Subscribe) { - var subs map[int64]bool + subs := make(map[int64]bool) for _, sub := range userSubs { if _, ok := subs[sub.SubscribeId]; !ok { subs[sub.SubscribeId] = true diff --git a/queue/logic/traffic/trafficStatLogic.go b/queue/logic/traffic/trafficStatLogic.go index e2f1cfc..81a422b 100644 --- a/queue/logic/traffic/trafficStatLogic.go +++ b/queue/logic/traffic/trafficStatLogic.go @@ -166,10 +166,11 @@ func (l *StatLogic) ProcessTask(ctx context.Context, _ *asynq.Task) error { } // Delete old traffic logs - err = tx.WithContext(ctx).Model(&traffic.TrafficLog{}).Where("created_at <= ?", end).Delete(&traffic.TrafficLog{}).Error - if err != nil { - logger.Errorf("[Traffic Stat Queue] Delete server traffic log failed: %v", err.Error()) + if l.svc.Config.Log.AutoClear { + err = tx.WithContext(ctx).Model(&traffic.TrafficLog{}).Where("created_at <= ?", end.AddDate(0, 0, int(-l.svc.Config.Log.ClearDays))).Delete(&traffic.TrafficLog{}).Error + if err != nil { + logger.Errorf("[Traffic Stat Queue] Delete server traffic log failed: %v", err.Error()) + } } - return nil }