package alert import ( "context" "errors" "strings" "time" "github.com/google/uuid" "gorm.io/gorm" "go-admin/app/bell/event" ) var ErrNotFound = errors.New("预警不存在") type PageQuery struct { PageIndex int `form:"pageIndex"` PageSize int `form:"pageSize"` Status string `form:"status"` Severity string `form:"severity"` Location string `form:"location"` } type Summary struct { Alert RuleName string `json:"ruleName"` EventCount int64 `json:"eventCount"` } type LinkedEvent struct { event.Event LinkedAt time.Time `json:"linkedAt"` } type Detail struct { Alert Summary `json:"alert"` Events []LinkedEvent `json:"events"` Matches []RuleMatch `json:"matches"` } type Service struct{ DB *gorm.DB } func NewService(db *gorm.DB) Service { return Service{DB: db} } func (s Service) List(ctx context.Context, query PageQuery) ([]Summary, int64, error) { page, size := pageValues(query.PageIndex, query.PageSize) base := s.DB.WithContext(ctx).Table("bell_alerts a") if query.Status = strings.TrimSpace(query.Status); query.Status != "" { base = base.Where("a.status = ?", query.Status) } if query.Severity = strings.TrimSpace(query.Severity); query.Severity != "" { base = base.Where("a.severity = ?", query.Severity) } if query.Location = strings.TrimSpace(query.Location); query.Location != "" { base = base.Where("a.location ILIKE ?", "%"+query.Location+"%") } var count int64 if err := base.Count(&count).Error; err != nil { return nil, 0, err } items := make([]Summary, 0) err := base.Select("a.*, r.name AS rule_name, (SELECT count(*) FROM bell_alert_events ae WHERE ae.alert_id = a.id) AS event_count"). Joins("JOIN bell_rules r ON r.id = a.primary_rule_id"). Order("a.created_at DESC, a.id DESC").Offset((page - 1) * size).Limit(size).Scan(&items).Error return items, count, err } func (s Service) Get(ctx context.Context, id string) (Detail, error) { if _, err := uuid.Parse(id); err != nil { return Detail{}, ErrNotFound } detail := Detail{Events: make([]LinkedEvent, 0), Matches: make([]RuleMatch, 0)} db := s.DB.WithContext(ctx) err := db.Table("bell_alerts a"). Select("a.*, r.name AS rule_name, (SELECT count(*) FROM bell_alert_events ae WHERE ae.alert_id = a.id) AS event_count"). Joins("JOIN bell_rules r ON r.id = a.primary_rule_id").Where("a.id = ?", id).Take(&detail.Alert).Error if errors.Is(err, gorm.ErrRecordNotFound) { return Detail{}, ErrNotFound } if err != nil { return Detail{}, err } if err = db.Table("bell_events e").Select("e.*, ae.linked_at"). Joins("JOIN bell_alert_events ae ON ae.event_id = e.id"). Where("ae.alert_id = ?", id).Order("e.occurred_at, e.id").Scan(&detail.Events).Error; err != nil { return Detail{}, err } err = db.Where("alert_id = ?", id).Order("matched_at, event_id, rule_id").Find(&detail.Matches).Error return detail, err } type EventPageQuery struct { PageIndex int `form:"pageIndex"` PageSize int `form:"pageSize"` EventType string `form:"eventType"` Severity string `form:"severity"` Location string `form:"location"` } type EventSummary struct { event.Event AlertCount int64 `json:"alertCount"` } func (s Service) ListEvents(ctx context.Context, query EventPageQuery) ([]EventSummary, int64, error) { page, size := pageValues(query.PageIndex, query.PageSize) db := s.DB.WithContext(ctx).Model(&event.Event{}) if value := strings.TrimSpace(query.EventType); value != "" { db = db.Where("event_type ILIKE ?", "%"+value+"%") } if value := strings.TrimSpace(query.Severity); value != "" { db = db.Where("severity = ?", value) } if value := strings.TrimSpace(query.Location); value != "" { db = db.Where("location ILIKE ?", "%"+value+"%") } var count int64 if err := db.Count(&count).Error; err != nil { return nil, 0, err } items := make([]EventSummary, 0) err := db.Select("bell_events.*, (SELECT count(*) FROM bell_alert_events ae WHERE ae.event_id = bell_events.id) AS alert_count"). Order("occurred_at DESC, id DESC").Offset((page - 1) * size).Limit(size).Scan(&items).Error return items, count, err } func pageValues(page, size int) (int, int) { if page < 1 { page = 1 } if size < 1 || size > 100 { size = 20 } return page, size }