package service import ( "errors" "time" "gorm.io/gorm" "laic-backend/cache" "laic-backend/common" "laic-backend/logger" "laic-backend/model" "laic-backend/tool" "laic-backend/vo" ) type AlarmService struct{} var DefaultAlarmService = &AlarmService{} // GetPage 告警分页列表(按所属机巢过滤) func (s *AlarmService) GetPage(userID int64, isAdmin bool, req *vo.AlarmPageReq) (*common.PageResponse[model.Alarm], *common.BusiError) { db := common.DB.Model(&model.Alarm{}).Scopes(withDockFilter(userID, isAdmin)) if req.Status != "" { db = db.Where("status = ?", req.Status) } if req.Level != "" { db = db.Where("level = ?", req.Level) } if req.DeviceType != "" { db = db.Where("device_type = ?", req.DeviceType) } if req.DockID != "" { db = db.Where("dock_id = ?", req.DockID) } if req.Keyword != "" { kw := "%" + req.Keyword + "%" db = db.Where("code LIKE ? OR message_cn LIKE ?", kw, kw) } var total int64 if err := db.Count(&total).Error; err != nil { logger.ERROR("统计告警失败", err) return nil, common.ErrInternal } var list []model.Alarm if err := db.Scopes(req.Paginate).Order("id DESC").Find(&list).Error; err != nil { logger.ERROR("查询告警列表失败", err) return nil, common.ErrInternal } return common.Page(req.Pagination, total, list), nil } // Acknowledge 确认告警(active → acknowledged) func (s *AlarmService) Acknowledge(userID int64, isAdmin bool, id int64) *common.BusiError { var alarm model.Alarm if err := common.DB.Scopes(withDockFilter(userID, isAdmin)).First(&alarm, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return common.ErrAlarmNotFound } return common.ErrInternal } if alarm.Status == "active" { if err := common.DB.Model(&model.Alarm{}).Where("id = ?", id).Update("status", "acknowledged").Error; err != nil { logger.ERROR("确认告警失败", err) return common.ErrInternal } broadcast("alarm.acknowledged", map[string]any{ "id": id, "dockId": alarm.DockID, "code": alarm.Code, }) } return nil } // Resolve 关闭告警(active/acknowledged → resolved) func (s *AlarmService) Resolve(userID int64, isAdmin bool, id int64) *common.BusiError { var alarm model.Alarm if err := common.DB.Scopes(withDockFilter(userID, isAdmin)).First(&alarm, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return common.ErrAlarmNotFound } return common.ErrInternal } if alarm.Status != "resolved" { now := time.Now() if err := common.DB.Model(&model.Alarm{}).Where("id = ?", id). Updates(map[string]any{"status": "resolved", "resolved_at": now}).Error; err != nil { logger.ERROR("关闭告警失败", err) return common.ErrInternal } broadcast("alarm.resolved", map[string]any{ "id": id, "dockId": alarm.DockID, "code": alarm.Code, "resolvedAt": now, }) } return nil } // SyncByState 根据设备最新 alarmCodes 做 diff,生成/关闭告警 // deviceType: dock / drone func (s *AlarmService) SyncByState(dockID, deviceType string, codes []string) { key := s.alarmKey(dockID, deviceType) last, err := common.SetMembers(key) if err != nil { last = []string{} } lastSet := toSet(last) newSet := toSet(codes) // 新增告警 → active for code := range newSet { if !lastSet[code] { s.createAlarm(dockID, deviceType, code) } } // 移除告警 → resolved for code := range lastSet { if !newSet[code] { s.resolveAlarm(dockID, code) } } // 更新当前告警码集合 _ = common.Delete(key) for _, code := range codes { _ = common.SetAdd(key, code) } } func (s *AlarmService) createAlarm(dockID, deviceType, code string) { messageCn, level := code, "critical" var ac model.AlarmCode if err := common.DB.Where("code = ?", code).First(&ac).Error; err == nil { messageCn, level = ac.MessageCn, ac.Level } id, err := tool.NextID() if err != nil { logger.ERROR("生成告警 ID 失败", err) return } now := time.Now() alarm := model.Alarm{ ID: id, DockID: dockID, Code: code, DeviceType: deviceType, MessageCn: messageCn, Level: level, Status: "active", TriggeredAt: &now, CreatedAt: now, } if err := common.DB.Create(&alarm).Error; err != nil { logger.ERROR("写入告警失败", err) return } broadcast("alarm.created", map[string]any{ "dockId": dockID, "alarm": alarm, }) } func (s *AlarmService) resolveAlarm(dockID, code string) { now := time.Now() res := common.DB.Model(&model.Alarm{}).Where("dock_id = ? AND code = ? AND status = 'active'", dockID, code). Updates(map[string]any{"status": "resolved", "resolved_at": now}) if res.Error != nil { logger.ERROR("关闭告警失败", res.Error) return } if res.RowsAffected > 0 { broadcast("alarm.resolved", map[string]any{ "dockId": dockID, "code": code, "resolvedAt": now, }) } } func (s *AlarmService) alarmKey(dockID, deviceType string) string { if deviceType == "drone" { return cache.DroneAlarmKeyOf(dockID) } return cache.DockAlarmKeyOf(dockID) } func toSet(items []string) map[string]bool { set := make(map[string]bool, len(items)) for _, it := range items { set[it] = true } return set }