From 86fa6a434078345980ba721aa25b10351e43d797 Mon Sep 17 00:00:00 2001 From: engigu Date: Sun, 21 Dec 2025 22:19:55 +0800 Subject: [PATCH] feat: add exec log clean --- internal/controllers/task_controller.go | 22 ++++---- internal/models/task.go | 31 ++++++----- internal/services/executor_service.go | 49 +++++++++++++++++ internal/services/task_service.go | 16 +++--- web/src/api/index.ts | 1 + web/src/components/ui/select/Select.vue | 19 +++++++ .../components/ui/select/SelectContent.vue | 51 ++++++++++++++++++ web/src/components/ui/select/SelectGroup.vue | 15 ++++++ web/src/components/ui/select/SelectItem.vue | 44 ++++++++++++++++ .../components/ui/select/SelectItemText.vue | 15 ++++++ web/src/components/ui/select/SelectLabel.vue | 17 ++++++ .../ui/select/SelectScrollDownButton.vue | 26 ++++++++++ .../ui/select/SelectScrollUpButton.vue | 26 ++++++++++ .../components/ui/select/SelectSeparator.vue | 19 +++++++ .../components/ui/select/SelectTrigger.vue | 33 ++++++++++++ web/src/components/ui/select/SelectValue.vue | 15 ++++++ web/src/components/ui/select/index.ts | 11 ++++ web/src/views/tasks/Tasks.vue | 52 +++++++++++++++++-- 18 files changed, 431 insertions(+), 31 deletions(-) create mode 100644 web/src/components/ui/select/Select.vue create mode 100644 web/src/components/ui/select/SelectContent.vue create mode 100644 web/src/components/ui/select/SelectGroup.vue create mode 100644 web/src/components/ui/select/SelectItem.vue create mode 100644 web/src/components/ui/select/SelectItemText.vue create mode 100644 web/src/components/ui/select/SelectLabel.vue create mode 100644 web/src/components/ui/select/SelectScrollDownButton.vue create mode 100644 web/src/components/ui/select/SelectScrollUpButton.vue create mode 100644 web/src/components/ui/select/SelectSeparator.vue create mode 100644 web/src/components/ui/select/SelectTrigger.vue create mode 100644 web/src/components/ui/select/SelectValue.vue create mode 100644 web/src/components/ui/select/index.ts diff --git a/internal/controllers/task_controller.go b/internal/controllers/task_controller.go index 5ceb34f..8fa9e8b 100644 --- a/internal/controllers/task_controller.go +++ b/internal/controllers/task_controller.go @@ -23,9 +23,11 @@ func NewTaskController(taskService *services.TaskService, cronService *services. func (tc *TaskController) CreateTask(c *gin.Context) { var req struct { - Name string `json:"name" binding:"required"` - Command string `json:"command" binding:"required"` - Schedule string `json:"schedule" binding:"required"` + Name string `json:"name" binding:"required"` + Command string `json:"command" binding:"required"` + Schedule string `json:"schedule" binding:"required"` + Timeout int `json:"timeout"` + CleanConfig string `json:"clean_config"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -38,7 +40,7 @@ func (tc *TaskController) CreateTask(c *gin.Context) { return } - task := tc.taskService.CreateTask(req.Name, req.Command, req.Schedule) + task := tc.taskService.CreateTask(req.Name, req.Command, req.Schedule, req.Timeout, req.CleanConfig) tc.cronService.AddTask(task) utils.Success(c, task) @@ -76,10 +78,12 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { } var req struct { - Name string `json:"name"` - Command string `json:"command"` - Schedule string `json:"schedule"` - Enabled bool `json:"enabled"` + Name string `json:"name"` + Command string `json:"command"` + Schedule string `json:"schedule"` + Timeout int `json:"timeout"` + CleanConfig string `json:"clean_config"` + Enabled bool `json:"enabled"` } if err := c.ShouldBindJSON(&req); err != nil { @@ -94,7 +98,7 @@ func (tc *TaskController) UpdateTask(c *gin.Context) { } } - task := tc.taskService.UpdateTask(id, req.Name, req.Command, req.Schedule, req.Enabled) + task := tc.taskService.UpdateTask(id, req.Name, req.Command, req.Schedule, req.Timeout, req.CleanConfig, req.Enabled) if task == nil { utils.NotFound(c, "任务不存在") return diff --git a/internal/models/task.go b/internal/models/task.go index 36cf9f1..f7156ad 100644 --- a/internal/models/task.go +++ b/internal/models/task.go @@ -6,19 +6,26 @@ import ( "gorm.io/gorm" ) +// CleanConfig 清理配置结构 +type CleanConfig struct { + Type string `json:"type"` // "day" 或 "count" + Keep int `json:"keep"` // 保留天数或条数 +} + // Task represents a scheduled task type Task struct { - ID uint `json:"id" gorm:"primaryKey"` - Name string `json:"name" gorm:"size:255;not null"` - Command string `json:"command" gorm:"type:text;not null"` - Schedule string `json:"schedule" gorm:"size:100"` // cron expression - Timeout int `json:"timeout" gorm:"default:30"` // 超时时间(分钟),默认30分钟 - Enabled bool `json:"enabled" gorm:"default:true"` - LastRun *LocalTime `json:"last_run"` - NextRun *LocalTime `json:"next_run"` - CreatedAt LocalTime `json:"created_at"` - UpdatedAt LocalTime `json:"updated_at"` - DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` + ID uint `json:"id" gorm:"primaryKey"` + Name string `json:"name" gorm:"size:255;not null"` + Command string `json:"command" gorm:"type:text;not null"` + Schedule string `json:"schedule" gorm:"size:100"` // cron expression + Timeout int `json:"timeout" gorm:"default:30"` // 超时时间(分钟),默认30分钟 + CleanConfig string `json:"clean_config" gorm:"size:255;default:''"` // 清理配置 JSON + Enabled bool `json:"enabled" gorm:"default:true"` + LastRun *LocalTime `json:"last_run"` + NextRun *LocalTime `json:"next_run"` + CreatedAt LocalTime `json:"created_at"` + UpdatedAt LocalTime `json:"updated_at"` + DeletedAt gorm.DeletedAt `json:"-" gorm:"index"` } func (Task) TableName() string { @@ -39,4 +46,4 @@ type TaskLog struct { func (TaskLog) TableName() string { return constant.TablePrefix + "task_logs" -} \ No newline at end of file +} diff --git a/internal/services/executor_service.go b/internal/services/executor_service.go index b90e105..ea61824 100644 --- a/internal/services/executor_service.go +++ b/internal/services/executor_service.go @@ -3,6 +3,7 @@ package services import ( "bytes" "context" + "encoding/json" "os/exec" "sync" "time" @@ -47,6 +48,7 @@ func NewExecutorService(taskService *TaskService) *ExecutorService { // 注册默认回调 es.RegisterCallback(es.saveTaskLogCallback) es.RegisterCallback(es.updateStatsCallback) + es.RegisterCallback(es.cleanLogsCallback) return es } @@ -112,6 +114,53 @@ func (es *ExecutorService) updateStatsCallback(taskID uint, _ string, result *Ex } } +// CleanConfig 清理配置结构 +type CleanConfig struct { + Type string `json:"type"` // "day" 或 "count" + Keep int `json:"keep"` // 保留天数或条数 +} + +// cleanLogsCallback 清理日志的回调 +func (es *ExecutorService) cleanLogsCallback(taskID uint, _ string, _ *ExecutionResult) { + task := es.taskService.GetTaskByID(int(taskID)) + if task == nil || task.CleanConfig == "" { + return + } + + var config CleanConfig + if err := json.Unmarshal([]byte(task.CleanConfig), &config); err != nil { + logger.Errorf("Failed to parse clean config: %v", err) + return + } + + if config.Keep <= 0 { + return + } + + var deleted int64 + switch config.Type { + case "day": + // 按天清理:删除 N 天前的日志 + cutoff := time.Now().AddDate(0, 0, -config.Keep) + result := database.DB.Where("task_id = ? AND created_at < ?", taskID, cutoff).Delete(&models.TaskLog{}) + deleted = result.RowsAffected + case "count": + // 按条数清理:使用子查询删除超出保留数量的旧日志 + // 先获取第 N 条的 ID 作为边界 + var boundaryLog models.TaskLog + err := database.DB.Where("task_id = ?", taskID).Order("id DESC").Offset(config.Keep - 1).Limit(1).First(&boundaryLog).Error + if err == nil { + // 删除 ID 小于边界的所有日志 + result := database.DB.Where("task_id = ? AND id < ?", taskID, boundaryLog.ID).Delete(&models.TaskLog{}) + deleted = result.RowsAffected + } + } + + if deleted > 0 { + logger.Infof("Cleaned %d logs for task %d", deleted, taskID) + } +} + // ExecuteTask executes a task by ID func (es *ExecutorService) ExecuteTask(taskID int) *ExecutionResult { task := es.taskService.GetTaskByID(taskID) diff --git a/internal/services/task_service.go b/internal/services/task_service.go index 70e155c..1e997f3 100644 --- a/internal/services/task_service.go +++ b/internal/services/task_service.go @@ -11,12 +11,14 @@ func NewTaskService() *TaskService { return &TaskService{} } -func (ts *TaskService) CreateTask(name, command, schedule string) *models.Task { +func (ts *TaskService) CreateTask(name, command, schedule string, timeout int, cleanConfig string) *models.Task { task := &models.Task{ - Name: name, - Command: command, - Schedule: schedule, - Enabled: true, + Name: name, + Command: command, + Schedule: schedule, + Timeout: timeout, + CleanConfig: cleanConfig, + Enabled: true, } database.DB.Create(task) return task @@ -52,7 +54,7 @@ func (ts *TaskService) GetTaskByID(id int) *models.Task { return &task } -func (ts *TaskService) UpdateTask(id int, name, command, schedule string, enabled bool) *models.Task { +func (ts *TaskService) UpdateTask(id int, name, command, schedule string, timeout int, cleanConfig string, enabled bool) *models.Task { var task models.Task if err := database.DB.First(&task, id).Error; err != nil { return nil @@ -60,6 +62,8 @@ func (ts *TaskService) UpdateTask(id int, name, command, schedule string, enable task.Name = name task.Command = command task.Schedule = schedule + task.Timeout = timeout + task.CleanConfig = cleanConfig task.Enabled = enabled database.DB.Save(&task) return &task diff --git a/web/src/api/index.ts b/web/src/api/index.ts index c805b64..0289864 100644 --- a/web/src/api/index.ts +++ b/web/src/api/index.ts @@ -187,6 +187,7 @@ export interface Task { command: string schedule: string timeout: number + clean_config: string enabled: boolean last_run: string next_run: string diff --git a/web/src/components/ui/select/Select.vue b/web/src/components/ui/select/Select.vue new file mode 100644 index 0000000..c94bbe8 --- /dev/null +++ b/web/src/components/ui/select/Select.vue @@ -0,0 +1,19 @@ + + + diff --git a/web/src/components/ui/select/SelectContent.vue b/web/src/components/ui/select/SelectContent.vue new file mode 100644 index 0000000..adf04ec --- /dev/null +++ b/web/src/components/ui/select/SelectContent.vue @@ -0,0 +1,51 @@ + + + diff --git a/web/src/components/ui/select/SelectGroup.vue b/web/src/components/ui/select/SelectGroup.vue new file mode 100644 index 0000000..e981c6c --- /dev/null +++ b/web/src/components/ui/select/SelectGroup.vue @@ -0,0 +1,15 @@ + + + diff --git a/web/src/components/ui/select/SelectItem.vue b/web/src/components/ui/select/SelectItem.vue new file mode 100644 index 0000000..9371764 --- /dev/null +++ b/web/src/components/ui/select/SelectItem.vue @@ -0,0 +1,44 @@ + + + diff --git a/web/src/components/ui/select/SelectItemText.vue b/web/src/components/ui/select/SelectItemText.vue new file mode 100644 index 0000000..b6700b1 --- /dev/null +++ b/web/src/components/ui/select/SelectItemText.vue @@ -0,0 +1,15 @@ + + + diff --git a/web/src/components/ui/select/SelectLabel.vue b/web/src/components/ui/select/SelectLabel.vue new file mode 100644 index 0000000..5b6650c --- /dev/null +++ b/web/src/components/ui/select/SelectLabel.vue @@ -0,0 +1,17 @@ + + + diff --git a/web/src/components/ui/select/SelectScrollDownButton.vue b/web/src/components/ui/select/SelectScrollDownButton.vue new file mode 100644 index 0000000..7dc7670 --- /dev/null +++ b/web/src/components/ui/select/SelectScrollDownButton.vue @@ -0,0 +1,26 @@ + + + diff --git a/web/src/components/ui/select/SelectScrollUpButton.vue b/web/src/components/ui/select/SelectScrollUpButton.vue new file mode 100644 index 0000000..07fe87e --- /dev/null +++ b/web/src/components/ui/select/SelectScrollUpButton.vue @@ -0,0 +1,26 @@ + + + diff --git a/web/src/components/ui/select/SelectSeparator.vue b/web/src/components/ui/select/SelectSeparator.vue new file mode 100644 index 0000000..4b5c885 --- /dev/null +++ b/web/src/components/ui/select/SelectSeparator.vue @@ -0,0 +1,19 @@ + + + diff --git a/web/src/components/ui/select/SelectTrigger.vue b/web/src/components/ui/select/SelectTrigger.vue new file mode 100644 index 0000000..667908b --- /dev/null +++ b/web/src/components/ui/select/SelectTrigger.vue @@ -0,0 +1,33 @@ + + + diff --git a/web/src/components/ui/select/SelectValue.vue b/web/src/components/ui/select/SelectValue.vue new file mode 100644 index 0000000..d5ce58b --- /dev/null +++ b/web/src/components/ui/select/SelectValue.vue @@ -0,0 +1,15 @@ + + + diff --git a/web/src/components/ui/select/index.ts b/web/src/components/ui/select/index.ts new file mode 100644 index 0000000..96eae60 --- /dev/null +++ b/web/src/components/ui/select/index.ts @@ -0,0 +1,11 @@ +export { default as Select } from "./Select.vue" +export { default as SelectContent } from "./SelectContent.vue" +export { default as SelectGroup } from "./SelectGroup.vue" +export { default as SelectItem } from "./SelectItem.vue" +export { default as SelectItemText } from "./SelectItemText.vue" +export { default as SelectLabel } from "./SelectLabel.vue" +export { default as SelectScrollDownButton } from "./SelectScrollDownButton.vue" +export { default as SelectScrollUpButton } from "./SelectScrollUpButton.vue" +export { default as SelectSeparator } from "./SelectSeparator.vue" +export { default as SelectTrigger } from "./SelectTrigger.vue" +export { default as SelectValue } from "./SelectValue.vue" diff --git a/web/src/views/tasks/Tasks.vue b/web/src/views/tasks/Tasks.vue index 320339a..ad32d22 100644 --- a/web/src/views/tasks/Tasks.vue +++ b/web/src/views/tasks/Tasks.vue @@ -1,10 +1,11 @@