You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
5.2 KiB
200 lines
5.2 KiB
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("triggered_at DESC, 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
|
|
}
|
|
|