From 487d542c8cc97bfd4de215f941be220e1754043a Mon Sep 17 00:00:00 2001 From: attacker Date: Mon, 15 Jun 2026 14:05:28 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20backend=20alert.go=20split=20(586?= =?UTF-8?q?=E2=86=92133+274+206=20lines,=20alerts/rules/channels)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/api/handlers/alert.go | 459 +----------------- .../internal/api/handlers/alert_channels.go | 206 ++++++++ backend/internal/api/handlers/alert_rules.go | 274 +++++++++++ docs/dev-log.md | 22 + 4 files changed, 505 insertions(+), 456 deletions(-) create mode 100644 backend/internal/api/handlers/alert_channels.go create mode 100644 backend/internal/api/handlers/alert_rules.go diff --git a/backend/internal/api/handlers/alert.go b/backend/internal/api/handlers/alert.go index 5c220e2..c6519ce 100644 --- a/backend/internal/api/handlers/alert.go +++ b/backend/internal/api/handlers/alert.go @@ -11,6 +11,8 @@ import ( "gorm.io/gorm" ) +// 告警事件处理 + AlertHandler 定义(规则见 alert_rules.go,渠道见 alert_channels.go) // Logan.Li + // AlertHandler 告警处理器 type AlertHandler struct { db *gorm.DB @@ -68,268 +70,6 @@ func (h *AlertHandler) ListAlerts(c *gin.Context) { }) } -// ListRules 获取告警规则列表 -// @Summary 获取告警规则列表 -// @Description 获取所有告警规则列表 -// @Tags 告警 -// @Produce json -// @Security Bearer -// @Param page query int false "页码" -// @Param page_size query int false "每页数量" -// @Success 200 {object} models.Response{data=models.ListResponse} -// @Router /alerts/rules [get] -func (h *AlertHandler) ListRules(c *gin.Context) { - var query models.PageQuery - c.ShouldBindQuery(&query) - - var total int64 - h.db.Model(&models.AlertRule{}).Count(&total) - - var rules []models.AlertRule - h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&rules) - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "success", - Data: models.ListResponse{ - List: rules, - Total: total, - }, - }) -} - -// CreateRule 创建告警规则 -// @Summary 创建告警规则 -// @Description 创建新告警规则 -// @Tags 告警 -// @Accept json -// @Produce json -// @Security Bearer -// @Param request body models.CreateAlertRuleRequest true "创建请求" -// @Success 200 {object} models.Response{data=models.AlertRule} -// @Router /alerts/rules [post] -func (h *AlertHandler) CreateRule(c *gin.Context) { - var req models.CreateAlertRuleRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.Response{ - Code: 400, - Message: "请求参数错误: " + err.Error(), - }) - return - } - - rule := models.AlertRule{ - Name: req.Name, - Description: req.Description, - Type: req.Type, - Source: req.Source, - Expression: req.Expression, - Severity: req.Severity, - Duration: req.Duration, - Labels: req.Labels, - Annotations: req.Annotations, - NotifyChannels: toJSON(req.NotifyChannels), - Enabled: true, - } - - if err := h.db.Create(&rule).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{ - Code: 500, - Message: "创建失败", - }) - return - } - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "创建成功", - Data: rule, - }) -} - -// UpdateRule 更新告警规则 -// @Summary 更新告警规则 -// @Description 更新指定告警规则 -// @Tags 告警 -// @Accept json -// @Produce json -// @Security Bearer -// @Param id path int true "规则ID" -// @Param request body models.UpdateAlertRuleRequest true "更新请求" -// @Success 200 {object} models.Response{data=models.AlertRule} -// @Router /alerts/rules/{id} [put] -func (h *AlertHandler) UpdateRule(c *gin.Context) { - id, _ := strconv.Atoi(c.Param("id")) - - var req models.UpdateAlertRuleRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.Response{ - Code: 400, - Message: "请求参数错误: " + err.Error(), - }) - return - } - - var rule models.AlertRule - if err := h.db.First(&rule, id).Error; err != nil { - c.JSON(http.StatusNotFound, models.Response{ - Code: 404, - Message: "规则不存在", - }) - return - } - - updates := map[string]interface{}{} - if req.Name != "" { - updates["name"] = req.Name - } - if req.Description != "" { - updates["description"] = req.Description - } - if req.Expression != "" { - updates["expression"] = req.Expression - } - if req.Severity != "" { - updates["severity"] = req.Severity - } - if req.Duration > 0 { - updates["duration"] = req.Duration - } - if req.Labels != "" { - updates["labels"] = req.Labels - } - if req.Annotations != "" { - updates["annotations"] = req.Annotations - } - if len(req.NotifyChannels) > 0 { - updates["notify_channels"] = toJSON(req.NotifyChannels) - } - if req.Enabled != nil { - updates["enabled"] = *req.Enabled - } - - if err := h.db.Model(&rule).Updates(updates).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{ - Code: 500, - Message: "更新失败", - }) - return - } - - h.db.First(&rule, id) - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "更新成功", - Data: rule, - }) -} - -// DeleteRule 删除告警规则 -// @Summary 删除告警规则 -// @Description 删除指定告警规则 -// @Tags 告警 -// @Produce json -// @Security Bearer -// @Param id path int true "规则ID" -// @Success 200 {object} models.Response -// @Router /alerts/rules/{id} [delete] -func (h *AlertHandler) DeleteRule(c *gin.Context) { - id, _ := strconv.Atoi(c.Param("id")) - - if err := h.db.Delete(&models.AlertRule{}, id).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{ - Code: 500, - Message: "删除失败", - }) - return - } - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "删除成功", - }) -} - -// BatchDeleteRules 批量删除告警规则 // Logan.Li -// @Summary 批量删除告警规则 -// @Description 批量删除指定告警规则 -// @Tags 告警 -// @Accept json -// @Produce json -// @Security Bearer -// @Param request body models.IDsRequest true "删除请求" -// @Success 200 {object} models.Response -// @Router /alerts/rules/batch-delete [delete] -func (h *AlertHandler) BatchDeleteRules(c *gin.Context) { - var req models.IDsRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.Response{ - Code: 400, - Message: "请求参数错误", - }) - return - } - - if len(req.IDs) == 0 { - c.JSON(http.StatusBadRequest, models.Response{ - Code: 400, - Message: "请选择要删除的规则", - }) - return - } - - if err := h.db.Delete(&models.AlertRule{}, req.IDs).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{ - Code: 500, - Message: "删除失败", - }) - return - } - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "删除成功", - }) -} - -// ToggleRule 切换告警规则状态 -// @Summary 切换告警规则状态 -// @Description 启用或禁用告警规则 -// @Tags 告警 -// @Produce json -// @Security Bearer -// @Param id path int true "规则ID" -// @Success 200 {object} models.Response{data=models.AlertRule} -// @Router /alerts/rules/{id}/toggle [post] -func (h *AlertHandler) ToggleRule(c *gin.Context) { - id, _ := strconv.Atoi(c.Param("id")) - - var rule models.AlertRule - if err := h.db.First(&rule, id).Error; err != nil { - c.JSON(http.StatusNotFound, models.Response{ - Code: 404, - Message: "规则不存在", - }) - return - } - - rule.Enabled = !rule.Enabled - if err := h.db.Save(&rule).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{ - Code: 500, - Message: "操作失败", - }) - return - } - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "操作成功", - Data: rule, - }) -} - // AcknowledgeAlert 确认告警 // @Summary 确认告警 // @Description 确认告警事件 @@ -382,201 +122,8 @@ func (h *AlertHandler) AcknowledgeAlert(c *gin.Context) { }) } -// ListNotifyChannels 获取通知渠道列表 -// @Summary 获取通知渠道列表 -// @Description 获取所有通知渠道列表 -// @Tags 告警 -// @Produce json -// @Security Bearer -// @Success 200 {object} models.Response{data=models.ListResponse} -// @Router /alerts/notify-channels [get] -func (h *AlertHandler) ListNotifyChannels(c *gin.Context) { - var channels []models.NotifyChannel - h.db.Order("created_at DESC").Find(&channels) - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "success", - Data: models.ListResponse{ - List: channels, - Total: int64(len(channels)), - }, - }) -} - -// CreateNotifyChannel 创建通知渠道 -// @Summary 创建通知渠道 -// @Description 创建新通知渠道 -// @Tags 告警 -// @Accept json -// @Produce json -// @Security Bearer -// @Param request body models.CreateNotifyChannelRequest true "创建请求" -// @Success 200 {object} models.Response{data=models.NotifyChannel} -// @Router /alerts/notify-channels [post] -func (h *AlertHandler) CreateNotifyChannel(c *gin.Context) { - var req models.CreateNotifyChannelRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.Response{ - Code: 400, - Message: "请求参数错误: " + err.Error(), - }) - return - } - - channel := models.NotifyChannel{ - Name: req.Name, - Type: req.Type, - Config: req.Config, - Enabled: true, - } - - if err := h.db.Create(&channel).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{ - Code: 500, - Message: "创建失败", - }) - return - } - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "创建成功", - Data: channel, - }) -} - -// UpdateNotifyChannel 更新通知渠道 -// @Summary 更新通知渠道 -// @Description 更新指定通知渠道 -// @Tags 告警 -// @Accept json -// @Produce json -// @Security Bearer -// @Param id path int true "渠道ID" -// @Param request body models.UpdateNotifyChannelRequest true "更新请求" -// @Success 200 {object} models.Response{data=models.NotifyChannel} -// @Router /alerts/notify-channels/{id} [put] -func (h *AlertHandler) UpdateNotifyChannel(c *gin.Context) { - id, _ := strconv.Atoi(c.Param("id")) - - var req models.UpdateNotifyChannelRequest - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, models.Response{ - Code: 400, - Message: "请求参数错误: " + err.Error(), - }) - return - } - - var channel models.NotifyChannel - if err := h.db.First(&channel, id).Error; err != nil { - c.JSON(http.StatusNotFound, models.Response{ - Code: 404, - Message: "通知渠道不存在", - }) - return - } - - updates := map[string]interface{}{} - if req.Name != "" { - updates["name"] = req.Name - } - if req.Type != "" { - updates["type"] = req.Type - } - if req.Config != "" { - updates["config"] = req.Config - } - if req.Enabled != nil { - updates["enabled"] = *req.Enabled - } - - if err := h.db.Model(&channel).Updates(updates).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{ - Code: 500, - Message: "更新失败", - }) - return - } - - h.db.First(&channel, id) - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "更新成功", - Data: channel, - }) -} - -// DeleteNotifyChannel 删除通知渠道 -// @Summary 删除通知渠道 -// @Description 删除指定通知渠道 -// @Tags 告警 -// @Produce json -// @Security Bearer -// @Param id path int true "渠道ID" -// @Success 200 {object} models.Response -// @Router /alerts/notify-channels/{id} [delete] -func (h *AlertHandler) DeleteNotifyChannel(c *gin.Context) { - id, _ := strconv.Atoi(c.Param("id")) - - if err := h.db.Delete(&models.NotifyChannel{}, id).Error; err != nil { - c.JSON(http.StatusInternalServerError, models.Response{ - Code: 500, - Message: "删除失败", - }) - return - } - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: "删除成功", - }) -} - -// TestNotifyChannel 测试通知渠道 -// @Summary 测试通知渠道连通性 -// @Description 模拟测试通知渠道(返回测试状态) -// @Tags 告警 -// @Produce json -// @Security Bearer -// @Param id path int true "渠道ID" -// @Success 200 {object} models.Response -// @Router /alerts/notify-channels/{id}/test [post] -func (h *AlertHandler) TestNotifyChannel(c *gin.Context) { - id, _ := strconv.Atoi(c.Param("id")) - - var channel models.NotifyChannel - if err := h.db.First(&channel, id).Error; err != nil { - c.JSON(http.StatusNotFound, models.Response{ - Code: 404, - Message: "通知渠道不存在", - }) - return - } - - // 模拟测试:校验配置非空即视为成功 - status := "success" - message := "测试通知已发送" - if channel.Config == "" { - status = "warning" - message = "渠道未配置参数,请先完善配置" - } - - h.db.Model(&channel).Update("test_status", status) - - c.JSON(http.StatusOK, models.Response{ - Code: 200, - Message: message, - Data: gin.H{ - "status": status, - "channel": channel.Name, - "type": channel.Type, - }, - }) -} - // toJSON serializes a value to JSON string. Returns empty string on error. +// 包级辅助函数,供 alert_rules.go 等同包文件复用 // Logan.Li func toJSON(v interface{}) string { b, err := json.Marshal(v) if err != nil { diff --git a/backend/internal/api/handlers/alert_channels.go b/backend/internal/api/handlers/alert_channels.go new file mode 100644 index 0000000..6cc81b0 --- /dev/null +++ b/backend/internal/api/handlers/alert_channels.go @@ -0,0 +1,206 @@ +package handlers + +import ( + "net/http" + "strconv" + + "ezops/internal/models" + + "github.com/gin-gonic/gin" +) + +// 通知渠道 CRUD(AlertHandler 方法接收者,与 alert.go 同包共享) // Logan.Li + +// ListNotifyChannels 获取通知渠道列表 // Logan.Li +// @Summary 获取通知渠道列表 +// @Description 获取所有通知渠道列表 +// @Tags 告警 +// @Produce json +// @Security Bearer +// @Success 200 {object} models.Response{data=models.ListResponse} +// @Router /alerts/notify-channels [get] +func (h *AlertHandler) ListNotifyChannels(c *gin.Context) { + var channels []models.NotifyChannel + h.db.Order("created_at DESC").Find(&channels) + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "success", + Data: models.ListResponse{ + List: channels, + Total: int64(len(channels)), + }, + }) +} + +// CreateNotifyChannel 创建通知渠道 // Logan.Li +// @Summary 创建通知渠道 +// @Description 创建新通知渠道 +// @Tags 告警 +// @Accept json +// @Produce json +// @Security Bearer +// @Param request body models.CreateNotifyChannelRequest true "创建请求" +// @Success 200 {object} models.Response{data=models.NotifyChannel} +// @Router /alerts/notify-channels [post] +func (h *AlertHandler) CreateNotifyChannel(c *gin.Context) { + var req models.CreateNotifyChannelRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, models.Response{ + Code: 400, + Message: "请求参数错误: " + err.Error(), + }) + return + } + + channel := models.NotifyChannel{ + Name: req.Name, + Type: req.Type, + Config: req.Config, + Enabled: true, + } + + if err := h.db.Create(&channel).Error; err != nil { + c.JSON(http.StatusInternalServerError, models.Response{ + Code: 500, + Message: "创建失败", + }) + return + } + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "创建成功", + Data: channel, + }) +} + +// UpdateNotifyChannel 更新通知渠道 // Logan.Li +// @Summary 更新通知渠道 +// @Description 更新指定通知渠道 +// @Tags 告警 +// @Accept json +// @Produce json +// @Security Bearer +// @Param id path int true "渠道ID" +// @Param request body models.UpdateNotifyChannelRequest true "更新请求" +// @Success 200 {object} models.Response{data=models.NotifyChannel} +// @Router /alerts/notify-channels/{id} [put] +func (h *AlertHandler) UpdateNotifyChannel(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + + var req models.UpdateNotifyChannelRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, models.Response{ + Code: 400, + Message: "请求参数错误: " + err.Error(), + }) + return + } + + var channel models.NotifyChannel + if err := h.db.First(&channel, id).Error; err != nil { + c.JSON(http.StatusNotFound, models.Response{ + Code: 404, + Message: "通知渠道不存在", + }) + return + } + + updates := map[string]interface{}{} + if req.Name != "" { + updates["name"] = req.Name + } + if req.Type != "" { + updates["type"] = req.Type + } + if req.Config != "" { + updates["config"] = req.Config + } + if req.Enabled != nil { + updates["enabled"] = *req.Enabled + } + + if err := h.db.Model(&channel).Updates(updates).Error; err != nil { + c.JSON(http.StatusInternalServerError, models.Response{ + Code: 500, + Message: "更新失败", + }) + return + } + + h.db.First(&channel, id) + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "更新成功", + Data: channel, + }) +} + +// DeleteNotifyChannel 删除通知渠道 // Logan.Li +// @Summary 删除通知渠道 +// @Description 删除指定通知渠道 +// @Tags 告警 +// @Produce json +// @Security Bearer +// @Param id path int true "渠道ID" +// @Success 200 {object} models.Response +// @Router /alerts/notify-channels/{id} [delete] +func (h *AlertHandler) DeleteNotifyChannel(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + + if err := h.db.Delete(&models.NotifyChannel{}, id).Error; err != nil { + c.JSON(http.StatusInternalServerError, models.Response{ + Code: 500, + Message: "删除失败", + }) + return + } + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "删除成功", + }) +} + +// TestNotifyChannel 测试通知渠道 // Logan.Li +// @Summary 测试通知渠道连通性 +// @Description 模拟测试通知渠道(返回测试状态) +// @Tags 告警 +// @Produce json +// @Security Bearer +// @Param id path int true "渠道ID" +// @Success 200 {object} models.Response +// @Router /alerts/notify-channels/{id}/test [post] +func (h *AlertHandler) TestNotifyChannel(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + + var channel models.NotifyChannel + if err := h.db.First(&channel, id).Error; err != nil { + c.JSON(http.StatusNotFound, models.Response{ + Code: 404, + Message: "通知渠道不存在", + }) + return + } + + // 模拟测试:校验配置非空即视为成功 + status := "success" + message := "测试通知已发送" + if channel.Config == "" { + status = "warning" + message = "渠道未配置参数,请先完善配置" + } + + h.db.Model(&channel).Update("test_status", status) + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: message, + Data: gin.H{ + "status": status, + "channel": channel.Name, + "type": channel.Type, + }, + }) +} diff --git a/backend/internal/api/handlers/alert_rules.go b/backend/internal/api/handlers/alert_rules.go new file mode 100644 index 0000000..776d711 --- /dev/null +++ b/backend/internal/api/handlers/alert_rules.go @@ -0,0 +1,274 @@ +package handlers + +import ( + "net/http" + "strconv" + + "ezops/internal/models" + + "github.com/gin-gonic/gin" +) + +// 告警规则 CRUD(AlertHandler 方法接收者,与 alert.go 同包共享) // Logan.Li + +// ListRules 获取告警规则列表 // Logan.Li +// @Summary 获取告警规则列表 +// @Description 获取所有告警规则列表 +// @Tags 告警 +// @Produce json +// @Security Bearer +// @Param page query int false "页码" +// @Param page_size query int false "每页数量" +// @Success 200 {object} models.Response{data=models.ListResponse} +// @Router /alerts/rules [get] +func (h *AlertHandler) ListRules(c *gin.Context) { + var query models.PageQuery + c.ShouldBindQuery(&query) + + var total int64 + h.db.Model(&models.AlertRule{}).Count(&total) + + var rules []models.AlertRule + h.db.Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&rules) + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "success", + Data: models.ListResponse{ + List: rules, + Total: total, + }, + }) +} + +// CreateRule 创建告警规则 // Logan.Li +// @Summary 创建告警规则 +// @Description 创建新告警规则 +// @Tags 告警 +// @Accept json +// @Produce json +// @Security Bearer +// @Param request body models.CreateAlertRuleRequest true "创建请求" +// @Success 200 {object} models.Response{data=models.AlertRule} +// @Router /alerts/rules [post] +func (h *AlertHandler) CreateRule(c *gin.Context) { + var req models.CreateAlertRuleRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, models.Response{ + Code: 400, + Message: "请求参数错误: " + err.Error(), + }) + return + } + + rule := models.AlertRule{ + Name: req.Name, + Description: req.Description, + Type: req.Type, + Source: req.Source, + Expression: req.Expression, + Severity: req.Severity, + Duration: req.Duration, + Labels: req.Labels, + Annotations: req.Annotations, + NotifyChannels: toJSON(req.NotifyChannels), + Enabled: true, + } + + if err := h.db.Create(&rule).Error; err != nil { + c.JSON(http.StatusInternalServerError, models.Response{ + Code: 500, + Message: "创建失败", + }) + return + } + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "创建成功", + Data: rule, + }) +} + +// UpdateRule 更新告警规则 // Logan.Li +// @Summary 更新告警规则 +// @Description 更新指定告警规则 +// @Tags 告警 +// @Accept json +// @Produce json +// @Security Bearer +// @Param id path int true "规则ID" +// @Param request body models.UpdateAlertRuleRequest true "更新请求" +// @Success 200 {object} models.Response{data=models.AlertRule} +// @Router /alerts/rules/{id} [put] +func (h *AlertHandler) UpdateRule(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + + var req models.UpdateAlertRuleRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, models.Response{ + Code: 400, + Message: "请求参数错误: " + err.Error(), + }) + return + } + + var rule models.AlertRule + if err := h.db.First(&rule, id).Error; err != nil { + c.JSON(http.StatusNotFound, models.Response{ + Code: 404, + Message: "规则不存在", + }) + return + } + + updates := map[string]interface{}{} + if req.Name != "" { + updates["name"] = req.Name + } + if req.Description != "" { + updates["description"] = req.Description + } + if req.Expression != "" { + updates["expression"] = req.Expression + } + if req.Severity != "" { + updates["severity"] = req.Severity + } + if req.Duration > 0 { + updates["duration"] = req.Duration + } + if req.Labels != "" { + updates["labels"] = req.Labels + } + if req.Annotations != "" { + updates["annotations"] = req.Annotations + } + if len(req.NotifyChannels) > 0 { + updates["notify_channels"] = toJSON(req.NotifyChannels) + } + if req.Enabled != nil { + updates["enabled"] = *req.Enabled + } + + if err := h.db.Model(&rule).Updates(updates).Error; err != nil { + c.JSON(http.StatusInternalServerError, models.Response{ + Code: 500, + Message: "更新失败", + }) + return + } + + h.db.First(&rule, id) + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "更新成功", + Data: rule, + }) +} + +// DeleteRule 删除告警规则 // Logan.Li +// @Summary 删除告警规则 +// @Description 删除指定告警规则 +// @Tags 告警 +// @Produce json +// @Security Bearer +// @Param id path int true "规则ID" +// @Success 200 {object} models.Response +// @Router /alerts/rules/{id} [delete] +func (h *AlertHandler) DeleteRule(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + + if err := h.db.Delete(&models.AlertRule{}, id).Error; err != nil { + c.JSON(http.StatusInternalServerError, models.Response{ + Code: 500, + Message: "删除失败", + }) + return + } + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "删除成功", + }) +} + +// BatchDeleteRules 批量删除告警规则 // Logan.Li +// @Summary 批量删除告警规则 +// @Description 批量删除指定告警规则 +// @Tags 告警 +// @Accept json +// @Produce json +// @Security Bearer +// @Param request body models.IDsRequest true "删除请求" +// @Success 200 {object} models.Response +// @Router /alerts/rules/batch-delete [delete] +func (h *AlertHandler) BatchDeleteRules(c *gin.Context) { + var req models.IDsRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, models.Response{ + Code: 400, + Message: "请求参数错误", + }) + return + } + + if len(req.IDs) == 0 { + c.JSON(http.StatusBadRequest, models.Response{ + Code: 400, + Message: "请选择要删除的规则", + }) + return + } + + if err := h.db.Delete(&models.AlertRule{}, req.IDs).Error; err != nil { + c.JSON(http.StatusInternalServerError, models.Response{ + Code: 500, + Message: "删除失败", + }) + return + } + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "删除成功", + }) +} + +// ToggleRule 切换告警规则状态 // Logan.Li +// @Summary 切换告警规则状态 +// @Description 启用或禁用告警规则 +// @Tags 告警 +// @Produce json +// @Security Bearer +// @Param id path int true "规则ID" +// @Success 200 {object} models.Response{data=models.AlertRule} +// @Router /alerts/rules/{id}/toggle [post] +func (h *AlertHandler) ToggleRule(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + + var rule models.AlertRule + if err := h.db.First(&rule, id).Error; err != nil { + c.JSON(http.StatusNotFound, models.Response{ + Code: 404, + Message: "规则不存在", + }) + return + } + + rule.Enabled = !rule.Enabled + if err := h.db.Save(&rule).Error; err != nil { + c.JSON(http.StatusInternalServerError, models.Response{ + Code: 500, + Message: "操作失败", + }) + return + } + + c.JSON(http.StatusOK, models.Response{ + Code: 200, + Message: "操作成功", + Data: rule, + }) +} diff --git a/docs/dev-log.md b/docs/dev-log.md index da0d66f..b809d76 100644 --- a/docs/dev-log.md +++ b/docs/dev-log.md @@ -954,3 +954,25 @@ cd ~/devops/aiops - sidebar.tsx(689行) 略低于700但属于shadcn基础组件,可考虑提取 context/variants - 后端 alert.go(586行) 超400行限制,可考虑按 alerts/rules/channels 拆分 - 后端 pipeline.go(541行)、billing.go(476行)、server.go(446行) 均略超400行限制 + +## 2026-06-15 自动推进 + +- **任务**: 后端 alert.go 拆分(586行超400限制 → 3文件,按 alerts/rules/channels 分域) +- **修改**: + - `backend/internal/api/handlers/alert.go` — 586→133行:仅保留 AlertHandler struct + NewAlertHandler + 告警事件处理(ListAlerts/AcknowledgeAlert)+ 包级 toJSON 辅助函数(供同包 rules 文件复用) + - `backend/internal/api/handlers/alert_rules.go` — **新建**(274行):告警规则 CRUD(ListRules/CreateRule/UpdateRule/DeleteRule/BatchDeleteRules/ToggleRule),使用 alert.go 的 toJSON 序列化 NotifyChannels + - `backend/internal/api/handlers/alert_channels.go` — **新建**(206行):通知渠道 CRUD(ListNotifyChannels/CreateNotifyChannel/UpdateNotifyChannel/DeleteNotifyChannel/TestNotifyChannel) +- **结果**: + - `go build -o aiops-server ./cmd/` 编译通过 ✅ + - `go vet ./...` 零错误 ✅ + - `./run.sh restart` 后健康检查 /health 正常 ✅ + - curl 端到端验证(admin token): + - GET /alerts/rules → code 200, total 6 ✅ + - GET /alerts/notify-channels → code 200, total 3 ✅ + - GET /alerts(事件)→ code 200, total 20 ✅ + - POST /alerts/rules/1/toggle → code 200 "操作成功"(toggle 后再 toggle 回来)✅ + - 纯后端文件重组,无业务逻辑变更,前端零改动 + - 净行数:586 → 133+274+206 = 613(含 swagger 注释 + Logan.Li 署名注释) +- **下一步建议**: + - 后端 pipeline.go(541行)、billing.go(476行)、server.go(446行)、sql_audit.go(435行)、approval.go(435行) 仍超400行限制,可按同模式拆分 + - sidebar.tsx(689行) 可考虑提取 context/variants