From 4d910b15cc472f070d6a200c6f132809c67b4a9e Mon Sep 17 00:00:00 2001 From: engigu Date: Mon, 9 Mar 2026 21:46:11 +0800 Subject: [PATCH] feat: add log settings page --- internal/constant/constant.go | 15 ++-- internal/controllers/settings_controller.go | 44 +++------- internal/services/app_log_service.go | 31 ++++--- internal/services/settings_service.go | 64 +++++++++++--- internal/utils/pagination.go | 9 ++ web/src/api/index.ts | 2 + web/src/views/settings/SiteSettings.vue | 96 ++++++++++++++++----- 7 files changed, 178 insertions(+), 83 deletions(-) diff --git a/internal/constant/constant.go b/internal/constant/constant.go index 1fabd39..6b12cb1 100644 --- a/internal/constant/constant.go +++ b/internal/constant/constant.go @@ -48,8 +48,16 @@ const ( KeySecret = "secret" // System Settings Key 常量 - KeyInitialized = "initialized" - KeyLogRetention = "log_retention" + KeyInitialized = "initialized" + // KeyLogRetention = "log_retention" // Deprecated + + // Log Retention Keys + KeySystemNoticeDays = "system_notice_days" + KeySystemNoticeMaxCount = "system_notice_max_count" + KeyPushLogDays = "push_log_days" + KeyPushLogMaxCount = "push_log_max_count" + KeyLoginLogDays = "login_log_days" + KeyLoginLogMaxCount = "login_log_max_count" // Scheduler Settings Key 常量 KeyWorkerCount = "worker_count" @@ -160,6 +168,3 @@ var DefaultSettings = map[string]map[string]string{ KeyRateInterval: "200", }, } - -// DefaultLogRetention 默认日志清理配置 -var DefaultLogRetention = `{"system_notice":{"days":30,"max_count":500},"push_log":{"days":15,"max_count":5000}}` diff --git a/internal/controllers/settings_controller.go b/internal/controllers/settings_controller.go index 72ddf9f..7b69883 100644 --- a/internal/controllers/settings_controller.go +++ b/internal/controllers/settings_controller.go @@ -108,23 +108,12 @@ func (sc *SettingsController) GetSiteSettings(c *gin.Context) { } // 获取日志清理配置 - logRetentionJson := sc.settingsService.Get(constant.SectionSystem, constant.KeyLogRetention) - if logRetentionJson != "" { - var configs map[string]struct { - Days int `json:"days"` - MaxCount int `json:"max_count"` - } - if err := json.Unmarshal([]byte(logRetentionJson), &configs); err == nil { - if cfg, ok := configs[constant.LogCategorySystemNotice]; ok { - settings["system_notice_days"] = fmt.Sprintf("%d", cfg.Days) - settings["system_notice_max_count"] = fmt.Sprintf("%d", cfg.MaxCount) - } - if cfg, ok := configs[constant.LogCategoryPushLog]; ok { - settings["push_log_days"] = fmt.Sprintf("%d", cfg.Days) - settings["push_log_max_count"] = fmt.Sprintf("%d", cfg.MaxCount) - } - } - } + settings["system_notice_days"] = sc.settingsService.Get(constant.SectionSystem, constant.KeySystemNoticeDays) + settings["system_notice_max_count"] = sc.settingsService.Get(constant.SectionSystem, constant.KeySystemNoticeMaxCount) + settings["push_log_days"] = sc.settingsService.Get(constant.SectionSystem, constant.KeyPushLogDays) + settings["push_log_max_count"] = sc.settingsService.Get(constant.SectionSystem, constant.KeyPushLogMaxCount) + settings["login_log_days"] = sc.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogDays) + settings["login_log_max_count"] = sc.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogMaxCount) utils.Success(c, settings) } @@ -156,6 +145,8 @@ func (sc *SettingsController) UpdateSiteSettings(c *gin.Context) { SystemNoticeMaxCount int `json:"system_notice_max_count"` PushLogDays int `json:"push_log_days"` PushLogMaxCount int `json:"push_log_max_count"` + LoginLogDays int `json:"login_log_days"` + LoginLogMaxCount int `json:"login_log_max_count"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -190,19 +181,12 @@ func (sc *SettingsController) UpdateSiteSettings(c *gin.Context) { } // 保存日志清理配置 - logRetentionConfig := map[string]interface{}{ - constant.LogCategorySystemNotice: map[string]int{ - "days": req.SystemNoticeDays, - "max_count": req.SystemNoticeMaxCount, - }, - constant.LogCategoryPushLog: map[string]int{ - "days": req.PushLogDays, - "max_count": req.PushLogMaxCount, - }, - } - if logRetentionJson, err := json.Marshal(logRetentionConfig); err == nil { - sc.settingsService.Set(constant.SectionSystem, constant.KeyLogRetention, string(logRetentionJson)) - } + sc.settingsService.Set(constant.SectionSystem, constant.KeySystemNoticeDays, fmt.Sprintf("%d", req.SystemNoticeDays)) + sc.settingsService.Set(constant.SectionSystem, constant.KeySystemNoticeMaxCount, fmt.Sprintf("%d", req.SystemNoticeMaxCount)) + sc.settingsService.Set(constant.SectionSystem, constant.KeyPushLogDays, fmt.Sprintf("%d", req.PushLogDays)) + sc.settingsService.Set(constant.SectionSystem, constant.KeyPushLogMaxCount, fmt.Sprintf("%d", req.PushLogMaxCount)) + sc.settingsService.Set(constant.SectionSystem, constant.KeyLoginLogDays, fmt.Sprintf("%d", req.LoginLogDays)) + sc.settingsService.Set(constant.SectionSystem, constant.KeyLoginLogMaxCount, fmt.Sprintf("%d", req.LoginLogMaxCount)) utils.SuccessMsg(c, "保存成功") } diff --git a/internal/services/app_log_service.go b/internal/services/app_log_service.go index 52b63b6..421c9e1 100644 --- a/internal/services/app_log_service.go +++ b/internal/services/app_log_service.go @@ -1,7 +1,6 @@ package services import ( - "encoding/json" "time" "fmt" @@ -85,24 +84,30 @@ func (s *AppLogService) Clear(category string) error { } func (s *AppLogService) GetRetentionConfigs() map[string]LogRetentionConfig { - val := s.settingsService.Get(constant.SectionSystem, "log_retention") - var configs map[string]LogRetentionConfig - if val != "" { - _ = json.Unmarshal([]byte(val), &configs) - } - if configs == nil { - configs = map[string]LogRetentionConfig{ - constant.LogCategorySystemNotice: {Days: 30, MaxCount: 500}, - constant.LogCategoryPushLog: {Days: 15, MaxCount: 5000}, - constant.LogCategoryDefault: {Days: 30, MaxCount: 10000}, - } + configs := map[string]LogRetentionConfig{ + constant.LogCategorySystemNotice: { + Days: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeySystemNoticeDays), 30), + MaxCount: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeySystemNoticeMaxCount), 500), + }, + constant.LogCategoryPushLog: { + Days: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeyPushLogDays), 15), + MaxCount: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeyPushLogMaxCount), 5000), + }, + constant.LogCategoryLoginLog: { + Days: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogDays), 30), + MaxCount: utils.ToInt(s.settingsService.Get(constant.SectionSystem, constant.KeyLoginLogMaxCount), 1000), + }, + constant.LogCategoryDefault: { + Days: 30, + MaxCount: 10000, + }, } return configs } func (s *AppLogService) CleanUp() { configs := s.GetRetentionConfigs() - categories := []string{constant.LogCategorySystemNotice, constant.LogCategoryPushLog} + categories := []string{constant.LogCategorySystemNotice, constant.LogCategoryPushLog, constant.LogCategoryLoginLog} for _, cat := range categories { cfg, ok := configs[cat] diff --git a/internal/services/settings_service.go b/internal/services/settings_service.go index 8714135..3f3362a 100644 --- a/internal/services/settings_service.go +++ b/internal/services/settings_service.go @@ -1,6 +1,9 @@ package services import ( + "encoding/json" + "fmt" + "github.com/engigu/baihu-panel/internal/cache" "github.com/engigu/baihu-panel/internal/constant" "github.com/engigu/baihu-panel/internal/database" @@ -32,21 +35,58 @@ func (s *SettingsService) InitSettings() error { } } } - + // 初始化日志清理配置 - var logRetentionCount int64 - database.DB.Model(&models.Setting{}).Where("section = ? AND `key` = ?", constant.SectionSystem, constant.KeyLogRetention).Count(&logRetentionCount) - if logRetentionCount == 0 { - if err := database.DB.Create(&models.Setting{ - ID: utils.GenerateID(), - Section: constant.SectionSystem, - Key: constant.KeyLogRetention, - Value: models.BigText(constant.DefaultLogRetention), - }).Error; err != nil { - return err + // 检查是否需要从旧的 JSON 迁移 + oldVal := s.Get(constant.SectionSystem, "log_retention") + if oldVal != "" { + var oldConfigs map[string]struct { + Days int `json:"days"` + MaxCount int `json:"max_count"` + } + if err := json.Unmarshal([]byte(oldVal), &oldConfigs); err == nil { + migrationMap := map[string]string{} + if cfg, ok := oldConfigs[constant.LogCategorySystemNotice]; ok { + migrationMap[constant.KeySystemNoticeDays] = fmt.Sprintf("%d", cfg.Days) + migrationMap[constant.KeySystemNoticeMaxCount] = fmt.Sprintf("%d", cfg.MaxCount) + } + if cfg, ok := oldConfigs[constant.LogCategoryPushLog]; ok { + migrationMap[constant.KeyPushLogDays] = fmt.Sprintf("%d", cfg.Days) + migrationMap[constant.KeyPushLogMaxCount] = fmt.Sprintf("%d", cfg.MaxCount) + } + if cfg, ok := oldConfigs[constant.LogCategoryLoginLog]; ok { + migrationMap[constant.KeyLoginLogDays] = fmt.Sprintf("%d", cfg.Days) + migrationMap[constant.KeyLoginLogMaxCount] = fmt.Sprintf("%d", cfg.MaxCount) + } + + if len(migrationMap) > 0 { + for k, v := range migrationMap { + s.Set(constant.SectionSystem, k, v) + } + // 迁移完成后删除旧键 + s.Delete(constant.SectionSystem, "log_retention") + } } } - + + // 默认值初始化 + defaultRetention := map[string]string{ + constant.KeySystemNoticeDays: "30", + constant.KeySystemNoticeMaxCount: "500", + constant.KeyPushLogDays: "15", + constant.KeyPushLogMaxCount: "5000", + constant.KeyLoginLogDays: "30", + constant.KeyLoginLogMaxCount: "1000", + } + + for k, v := range defaultRetention { + var count int64 + database.DB.Model(&models.Setting{}).Where("section = ? AND `key` = ?", constant.SectionSystem, k).Count(&count) + if count == 0 { + s.Set(constant.SectionSystem, k, v) + } + } + // 初始化或获取 JWT Secret 密码 var secCount int64 database.DB.Model(&models.Setting{}).Where("section = ? AND `key` = ?", constant.SectionSecurity, constant.KeySecret).Count(&secCount) diff --git a/internal/utils/pagination.go b/internal/utils/pagination.go index 8a96fd0..d85d39a 100644 --- a/internal/utils/pagination.go +++ b/internal/utils/pagination.go @@ -9,6 +9,15 @@ import ( "github.com/gin-gonic/gin" ) +// ToInt 解析字符串为整数,如果解析失败则返回默认值 +func ToInt(s string, defaultVal int) int { + val, err := strconv.Atoi(s) + if err != nil { + return defaultVal + } + return val +} + // ParseInt 解析字符串为整数 func ParseInt(s string) (int, error) { return strconv.Atoi(s) diff --git a/web/src/api/index.ts b/web/src/api/index.ts index d2f186c..3b05f8b 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -454,6 +454,8 @@ export interface SiteSettings { system_notice_max_count?: string push_log_days?: string push_log_max_count?: string + login_log_days?: string + login_log_max_count?: string } export interface SchedulerSettings { diff --git a/web/src/views/settings/SiteSettings.vue b/web/src/views/settings/SiteSettings.vue index 0fa7fb3..8a75bd1 100644 --- a/web/src/views/settings/SiteSettings.vue +++ b/web/src/views/settings/SiteSettings.vue @@ -8,7 +8,7 @@ import { toast } from 'vue-sonner' import { useSiteSettings } from '@/composables/useSiteSettings' import { Badge } from '@/components/ui/badge' import { Switch } from '@/components/ui/switch' -import { RefreshCw, Copy, AlertTriangle, ExternalLink } from 'lucide-vue-next' +import { RefreshCw, Copy, AlertTriangle, ExternalLink, Info, Clock } from 'lucide-vue-next' import { AlertDialog, AlertDialogAction, @@ -36,7 +36,9 @@ const form = ref({ system_notice_days: '30', system_notice_max_count: '500', push_log_days: '15', - push_log_max_count: '5000' + push_log_max_count: '5000', + login_log_days: '30', + login_log_max_count: '1000' }) const loading = ref(false) const showOpenapiConfirmDialog = ref(false) @@ -70,7 +72,9 @@ async function saveSettings() { system_notice_days: String(form.value.system_notice_days || '30'), system_notice_max_count: String(form.value.system_notice_max_count || '500'), push_log_days: String(form.value.push_log_days || '15'), - push_log_max_count: String(form.value.push_log_max_count || '5000') + push_log_max_count: String(form.value.push_log_max_count || '5000'), + login_log_days: String(form.value.login_log_days || '30'), + login_log_max_count: String(form.value.login_log_max_count || '1000') }) await refreshSettings() toast.success('保存成功') @@ -148,37 +152,83 @@ onMounted(loadSettings)

日志清理策略

自动清理超过指定天数或数量的日志记录,保持系统性能。

- -
+ +
- -
-
- - 天后清理 + +
+
+ + 天清理
-
- 或保留最新 - - +
+ 保留 + +
- -
-
- - 天后清理 + +
+
+ + 天清理
-
- 或保留最新 - - +
+ 保留 + +
+ +
+ +
+
+ + 天清理 +
+
+ 保留 + + +
+
+
+
+ +
+
+
+ +
+
+

双重维度限制

+

+ 系统将根据天数和数量同时进行监测。满足任一条件即执行清理:超过天数的旧数据将被物理删除;若日志总数超过限制条数,则自动剔除最早产生的记录。 +

+
+
+
+
+ +
+
+

执行周期说明

+

+ 清理任务在白虎面板后端服务启动时立即执行一次。在运行期间,系统将自动开启后台巡检计数器,每隔 24 小时进行周期性自动清理。 +

+
+