package api import ( "errors" "net/http" "strconv" "github.com/gin-gonic/gin" "oci-portal/internal/model" "oci-portal/internal/service" ) // logEventHandler 处理回传事件查询与每租户回调地址管理(JWT 组内)。 type logEventHandler struct { svc *service.LogEventService } // getWebhook 查询配置的回调地址;未生成时 exists 为 false 且不含 secret。 // // @Summary 查询配置的回调地址 // @Tags 任务与日志回传 // @Param id path int true "配置 ID" // @Success 200 {object} map[string]any // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/log-webhook [get] func (h *logEventHandler) getWebhook(c *gin.Context) { id, ok := pathID(c) if !ok { return } info, exists, err := h.svc.SecretInfo(c.Request.Context(), id) if err != nil { respondError(c, err) return } if !exists { c.JSON(http.StatusOK, gin.H{"exists": false}) return } c.JSON(http.StatusOK, gin.H{"exists": true, "webhook": info}) } // ensureWebhook 生成(或幂等返回)配置的回传 secret 与回调路径。 // // @Summary 生成 // @Tags 任务与日志回传 // @Param id path int true "配置 ID" // @Success 200 {object} map[string]any // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/log-webhook [post] func (h *logEventHandler) ensureWebhook(c *gin.Context) { id, ok := pathID(c) if !ok { return } info, err := h.svc.EnsureSecret(c.Request.Context(), id) if err != nil { respondError(c, err) return } c.JSON(http.StatusOK, info) } // revokeWebhook 撤销配置的回传 secret,旧回调地址随即失效。 // // @Summary 撤销配置的回传 secret,旧回调地址随即失效 // @Tags 任务与日志回传 // @Param id path int true "配置 ID" // @Success 204 "无内容" // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/log-webhook [delete] func (h *logEventHandler) revokeWebhook(c *gin.Context) { id, ok := pathID(c) if !ok { return } if err := h.svc.RevokeSecret(c.Request.Context(), id); err != nil { respondError(c, err) return } c.Status(http.StatusNoContent) } // list 分页查询回传事件;cfgId 缺省为全部租户。 // // @Summary 回传日志事件列表 // @Tags 任务与日志回传 // @Param configId query int false "按配置过滤" // @Param q query string false "关键字" // @Success 200 {object} map[string]any "items 与 total" // @Security BearerAuth // @Router /api/v1/log-events [get] func (h *logEventHandler) list(c *gin.Context) { cfgID, _ := strconv.ParseUint(c.Query("cfgId"), 10, 64) page, _ := strconv.Atoi(c.Query("page")) pageSize, _ := strconv.Atoi(c.Query("pageSize")) items, total, err := h.svc.List(c.Request.Context(), service.LogEventQuery{ CfgID: uint(cfgID), Page: page, PageSize: pageSize, }) if err != nil { respondError(c, err) return } c.JSON(http.StatusOK, gin.H{"items": items, "total": total}) } // alertRuleRequest 是创建/更新告警规则的请求体。 type alertRuleRequest struct { Name string `json:"name"` Enabled bool `json:"enabled"` OciConfigID uint `json:"ociConfigId"` EventTypes string `json:"eventTypes"` SourceIPs string `json:"sourceIps"` SourceIPMode string `json:"sourceIpMode"` ResourceMatch string `json:"resourceMatch"` Threshold int `json:"threshold"` WindowMinutes int `json:"windowMinutes"` } // toModel 转为规则模型(默认阈值 1)。 func (r alertRuleRequest) toModel() model.AlertRule { if r.Threshold == 0 { r.Threshold = 1 } return model.AlertRule{ Name: r.Name, Enabled: r.Enabled, OciConfigID: r.OciConfigID, EventTypes: r.EventTypes, SourceIPs: r.SourceIPs, SourceIPMode: r.SourceIPMode, ResourceMatch: r.ResourceMatch, Threshold: r.Threshold, WindowMinutes: r.WindowMinutes, } } // listAlertRules 返回全部告警规则。 // // @Summary 告警规则列表 // @Tags 任务与日志回传 // @Success 200 {object} map[string]any // @Security BearerAuth // @Router /api/v1/log-events/alert-rules [get] func (h *logEventHandler) listAlertRules(c *gin.Context) { rules, err := h.svc.ListAlertRules(c.Request.Context()) if err != nil { respondError(c, err) return } c.JSON(http.StatusOK, gin.H{"items": rules}) } // createAlertRule 创建告警规则;字段非法返回 400。 // // @Summary 创建告警规则 // @Tags 任务与日志回传 // @Param body body alertRuleRequest true "请求体" // @Success 201 {object} map[string]any // @Security BearerAuth // @Router /api/v1/log-events/alert-rules [post] func (h *logEventHandler) createAlertRule(c *gin.Context) { var req alertRuleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } rule, err := h.svc.CreateAlertRule(c.Request.Context(), req.toModel()) if err != nil { respondAlertRuleError(c, err) return } c.JSON(http.StatusCreated, rule) } // updateAlertRule 整体覆盖告警规则(含启停)。 // // @Summary 更新告警规则 // @Tags 任务与日志回传 // @Param ruleId path int true "规则 ID" // @Param body body alertRuleRequest true "请求体" // @Success 200 {object} map[string]any // @Security BearerAuth // @Router /api/v1/log-events/alert-rules/{ruleId} [put] func (h *logEventHandler) updateAlertRule(c *gin.Context) { id, err := strconv.ParseUint(c.Param("ruleId"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "规则 ID 非法"}) return } var req alertRuleRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } rule, err := h.svc.UpdateAlertRule(c.Request.Context(), uint(id), req.toModel()) if err != nil { respondAlertRuleError(c, err) return } c.JSON(http.StatusOK, rule) } // deleteAlertRule 删除告警规则及其命中记录。 // // @Summary 删除告警规则 // @Tags 任务与日志回传 // @Param ruleId path int true "规则 ID" // @Success 204 "无内容" // @Security BearerAuth // @Router /api/v1/log-events/alert-rules/{ruleId} [delete] func (h *logEventHandler) deleteAlertRule(c *gin.Context) { id, err := strconv.ParseUint(c.Param("ruleId"), 10, 64) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "规则 ID 非法"}) return } if err := h.svc.DeleteAlertRule(c.Request.Context(), uint(id)); err != nil { respondError(c, err) return } c.Status(http.StatusNoContent) } // respondAlertRuleError 把字段校验错误映射为 400。 func respondAlertRuleError(c *gin.Context, err error) { if errors.Is(err, service.ErrInvalidAlertRule) { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } respondError(c, err) } // getRelay 查询 OCI 侧链路状态与关键事件清单。 // // @Summary 查询 OCI 侧链路状态与关键事件清单 // @Tags 任务与日志回传 // @Param id path int true "配置 ID" // @Success 200 {object} map[string]any // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/log-relay [get] func (h *logEventHandler) getRelay(c *gin.Context) { id, ok := pathID(c) if !ok { return } view, err := h.svc.RelayStatus(c.Request.Context(), id) if err != nil { respondRelayError(c, err) return } c.JSON(http.StatusOK, view) } // setupRelay 一键建立回传链路(P1 引导创建);同步执行,前端以长超时等待。 // // @Summary 一键建立回传链路 // @Tags 任务与日志回传 // @Param id path int true "配置 ID" // @Success 200 {object} map[string]any // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/log-relay [post] func (h *logEventHandler) setupRelay(c *gin.Context) { id, ok := pathID(c) if !ok { return } view, err := h.svc.SetupRelay(c.Request.Context(), id) if err != nil { respondRelayError(c, err) return } c.JSON(http.StatusOK, view) } // teardownRelay 逆序销毁链路并撤销 secret。 // // @Summary 逆序销毁链路并撤销 secret // @Tags 任务与日志回传 // @Param id path int true "配置 ID" // @Success 204 "无内容" // @Security BearerAuth // @Router /api/v1/oci-configs/{id}/log-relay [delete] func (h *logEventHandler) teardownRelay(c *gin.Context) { id, ok := pathID(c) if !ok { return } if err := h.svc.TeardownRelay(c.Request.Context(), id); err != nil { respondRelayError(c, err) return } c.Status(http.StatusNoContent) } // respondRelayError 在通用映射之上补充引导创建专属错误:依赖未配置映射 409。 func respondRelayError(c *gin.Context, err error) { if errors.Is(err, service.ErrRelayNotConfigured) { c.JSON(http.StatusConflict, gin.H{"error": err.Error()}) return } respondError(c, err) }