From 0d766020bcdb3a864b97ab5e65c1724147728368 Mon Sep 17 00:00:00 2001 From: mei Date: Sun, 26 Jul 2026 22:34:29 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=89=8B=E5=8A=A8=E8=A7=A6=E5=8F=91?= =?UTF-8?q?=E5=85=A8=E9=83=A8=E6=88=90=E5=8A=9F=E6=97=B6=E8=A2=AB=E8=AF=AF?= =?UTF-8?q?=E6=8A=A5=E4=B8=BA=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit trigger 接口的 fails 是 nil 切片,无失败时序列化成 JSON null, 前端读 failed.length 抛异常并落入 catch 分支,把成功显示成"触发失败"。 改为初始化空切片,前端同时加空值兜底。 顺带修复 logstore.trim 用 First 查询保留位置,日志未超出保留条数时 返回 ErrRecordNotFound,导致每写一条日志都刷一行 gorm 警告。 Co-Authored-By: Claude Fable 5 --- api/timer/trigger.go | 9 ++++++--- handler/logstore/logstore.go | 15 ++++++++++----- web/dist/.gitkeep | 0 web/frontend/src/views/TimersView.vue | 7 +++---- 4 files changed, 19 insertions(+), 12 deletions(-) delete mode 100644 web/dist/.gitkeep diff --git a/api/timer/trigger.go b/api/timer/trigger.go index b7f984f..9f648b6 100644 --- a/api/timer/trigger.go +++ b/api/timer/trigger.go @@ -57,11 +57,14 @@ func HandleTriggerTimer(c *gin.Context) { defer cancel() // 并发执行所有规则,收集失败信息 + // + // fails 必须初始化为空切片:nil 切片会被序列化成 JSON null, + // 前端读 failed.length 时会抛异常,把全部成功误报成触发失败。 var ( - wg sync.WaitGroup - mu sync.Mutex - fails []string + wg sync.WaitGroup + mu sync.Mutex ) + fails := []string{} for i := range rules { rule := rules[i] wg.Add(1) diff --git a/handler/logstore/logstore.go b/handler/logstore/logstore.go index 654b049..66cba78 100644 --- a/handler/logstore/logstore.go +++ b/handler/logstore/logstore.go @@ -42,18 +42,23 @@ func trim(uid uint) { return } - var cutoff model.ExecutionLog - err = gormDB.Where("uid = ?", uid). + // 用 Find 而不是 First:日志没超出保留条数是常态, + // First 找不到记录会返回 ErrRecordNotFound,每写一条日志都刷一行 gorm 警告。 + var cutoff []model.ExecutionLog + if err := gormDB.Where("uid = ?", uid). Order("id DESC"). Offset(config.LogRetainCount). Limit(1). - First(&cutoff).Error - if err != nil { + Find(&cutoff).Error; err != nil { + log.Printf("清理执行日志失败: %v", err) + return + } + if len(cutoff) == 0 { // 没有超出保留条数 return } - if err := gormDB.Where("uid = ? AND id <= ?", uid, cutoff.ID). + if err := gormDB.Where("uid = ? AND id <= ?", uid, cutoff[0].ID). Delete(&model.ExecutionLog{}).Error; err != nil { log.Printf("清理执行日志失败: %v", err) } diff --git a/web/dist/.gitkeep b/web/dist/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/web/frontend/src/views/TimersView.vue b/web/frontend/src/views/TimersView.vue index 6e43ece..682eb42 100644 --- a/web/frontend/src/views/TimersView.vue +++ b/web/frontend/src/views/TimersView.vue @@ -147,12 +147,11 @@ async function trigger(timer: Timer) { triggeringId.value = timer.id try { const result = await timerApi.trigger(timer.id) - if (result.failed.length === 0) { + const failed = result.failed ?? [] + if (failed.length === 0) { ElMessage.success(`已执行 ${result.total} 条规则,全部成功`) } else { - ElMessage.warning( - `已执行 ${result.total} 条规则,${result.failed.length} 条失败,详情见执行日志`, - ) + ElMessage.warning(`已执行 ${result.total} 条规则,${failed.length} 条失败,详情见执行日志`) } } catch (error) { ElMessage.error(error instanceof ApiError ? error.message : '触发失败')