2025-11-07 23:21:37 +08:00
|
|
|
|
package alarm
|
|
|
|
|
|
|
2025-11-09 21:37:35 +08:00
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"errors"
|
2025-11-09 22:34:05 +08:00
|
|
|
|
"fmt"
|
2025-11-09 21:37:35 +08:00
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
|
|
|
|
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
|
|
|
|
|
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
|
|
|
|
|
|
|
|
|
|
|
"gorm.io/gorm"
|
|
|
|
|
|
)
|
2025-11-07 23:21:37 +08:00
|
|
|
|
|
|
|
|
|
|
// AlarmService 定义了告警领域服务接口。
|
|
|
|
|
|
type AlarmService interface {
|
2025-11-09 21:37:35 +08:00
|
|
|
|
// CreateAlarmIfNotExists 检查是否存在相同的活跃告警,如果不存在,则创建一条新的告警记录。
|
|
|
|
|
|
// "相同"的定义是:SourceType, SourceID, 和 AlarmCode 都相同。
|
|
|
|
|
|
CreateAlarmIfNotExists(ctx context.Context, newAlarm *models.ActiveAlarm) error
|
|
|
|
|
|
|
|
|
|
|
|
// CloseAlarm 关闭一个活跃告警,将其归档到历史记录。
|
|
|
|
|
|
// 如果指定的告警当前不活跃,则不执行任何操作并返回 nil。
|
|
|
|
|
|
CloseAlarm(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint) error
|
2025-11-09 22:34:05 +08:00
|
|
|
|
|
|
|
|
|
|
// SnoozeAlarm 忽略一个活跃告警,或更新其忽略时间。
|
|
|
|
|
|
// 如果告警不存在,将返回错误。
|
|
|
|
|
|
SnoozeAlarm(ctx context.Context, alarmID uint, duration time.Duration) error
|
|
|
|
|
|
|
|
|
|
|
|
// CancelAlarmSnooze 取消对一个告警的忽略状态。
|
|
|
|
|
|
// 如果告警不存在,或本就未被忽略,不执行任何操作并返回 nil。
|
|
|
|
|
|
CancelAlarmSnooze(ctx context.Context, alarmID uint) error
|
2025-11-07 23:21:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// alarmService 是 AlarmService 接口的具体实现。
|
|
|
|
|
|
type alarmService struct {
|
2025-11-09 21:37:35 +08:00
|
|
|
|
ctx context.Context
|
|
|
|
|
|
alarmRepo repository.AlarmRepository
|
|
|
|
|
|
uow repository.UnitOfWork
|
2025-11-07 23:21:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-09 21:37:35 +08:00
|
|
|
|
// NewAlarmService 创建一个新的 AlarmService 实例。
|
|
|
|
|
|
func NewAlarmService(ctx context.Context, alarmRepo repository.AlarmRepository, uow repository.UnitOfWork) AlarmService {
|
2025-11-07 23:21:37 +08:00
|
|
|
|
return &alarmService{
|
2025-11-09 21:37:35 +08:00
|
|
|
|
ctx: ctx,
|
|
|
|
|
|
alarmRepo: alarmRepo,
|
|
|
|
|
|
uow: uow,
|
2025-11-07 23:21:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-09 21:37:35 +08:00
|
|
|
|
|
|
|
|
|
|
// CreateAlarmIfNotExists 实现了创建告警(如果不存在)的逻辑。
|
|
|
|
|
|
func (s *alarmService) CreateAlarmIfNotExists(ctx context.Context, newAlarm *models.ActiveAlarm) error {
|
|
|
|
|
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "CreateAlarmIfNotExists")
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 检查告警是否已处于活跃状态
|
|
|
|
|
|
isActive, err := s.alarmRepo.IsAlarmActiveInUse(serviceCtx, newAlarm.SourceType, newAlarm.SourceID, newAlarm.AlarmCode)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logger.Errorf("检查告警活跃状态时发生数据库错误: %v", err)
|
|
|
|
|
|
return err // 直接返回数据库错误
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if isActive {
|
|
|
|
|
|
// 2. 如果已活跃,则记录日志并忽略
|
|
|
|
|
|
logger.Infof("相同的告警已处于活跃状态,已忽略。来源: %s, ID: %d, 告警代码: %s", newAlarm.SourceType, newAlarm.SourceID, newAlarm.AlarmCode)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 如果不活跃,则创建新告警
|
|
|
|
|
|
logger.Infof("告警尚不活跃,正在创建新告警。来源: %s, ID: %d, 告警代码: %s", newAlarm.SourceType, newAlarm.SourceID, newAlarm.AlarmCode)
|
|
|
|
|
|
return s.alarmRepo.CreateActiveAlarm(serviceCtx, newAlarm)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CloseAlarm 实现了关闭告警并将其归档的逻辑。
|
|
|
|
|
|
func (s *alarmService) CloseAlarm(ctx context.Context, sourceType models.AlarmSourceType, sourceID uint, alarmCode models.AlarmCode, resolveMethod string, resolvedBy *uint) error {
|
|
|
|
|
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "CloseAlarm")
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 在事务外进行快速只读检查,避免不必要的事务开销
|
|
|
|
|
|
isActive, err := s.alarmRepo.IsAlarmActiveInUse(serviceCtx, sourceType, sourceID, alarmCode)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
logger.Errorf("关闭告警失败:预检查告警活跃状态失败: %v", err)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果告警本就不活跃,则无需任何操作
|
|
|
|
|
|
if !isActive {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 确认告警存在后,再进入事务执行“移动”操作
|
|
|
|
|
|
logger.Infof("检测到活跃告警,正在执行关闭和归档操作。来源: %s, ID: %d, 告警代码: %s", sourceType, sourceID, alarmCode)
|
|
|
|
|
|
return s.uow.ExecuteInTransaction(serviceCtx, func(tx *gorm.DB) error {
|
|
|
|
|
|
// 在事务中再次查找,确保数据一致性并获取完整对象
|
|
|
|
|
|
activeAlarm, err := s.alarmRepo.GetActiveAlarmByUniqueFieldsTx(serviceCtx, tx, sourceType, sourceID, alarmCode)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 此时如果没找到,可能在预检查和本事务之间已被其他进程关闭,同样视为正常
|
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
|
logger.Infof("告警在事务开始前已被关闭,无需操作。")
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
logger.Errorf("关闭告警失败:在事务中查找活跃告警失败: %v", err)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建历史告警记录
|
|
|
|
|
|
historicalAlarm := &models.HistoricalAlarm{
|
|
|
|
|
|
SourceType: activeAlarm.SourceType,
|
|
|
|
|
|
SourceID: activeAlarm.SourceID,
|
|
|
|
|
|
AlarmCode: activeAlarm.AlarmCode,
|
|
|
|
|
|
AlarmSummary: activeAlarm.AlarmSummary,
|
|
|
|
|
|
Level: activeAlarm.Level,
|
|
|
|
|
|
AlarmDetails: activeAlarm.AlarmDetails,
|
|
|
|
|
|
TriggerTime: activeAlarm.TriggerTime,
|
|
|
|
|
|
ResolveTime: time.Now(),
|
|
|
|
|
|
ResolveMethod: resolveMethod,
|
|
|
|
|
|
ResolvedBy: resolvedBy,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在事务中插入历史告警
|
|
|
|
|
|
if err := s.alarmRepo.CreateHistoricalAlarmTx(serviceCtx, tx, historicalAlarm); err != nil {
|
|
|
|
|
|
logger.Errorf("关闭告警失败:归档告警 %d 到历史表失败: %v", activeAlarm.ID, err)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 在事务中删除活跃告警
|
|
|
|
|
|
if err := s.alarmRepo.DeleteActiveAlarmTx(serviceCtx, tx, activeAlarm.ID); err != nil {
|
|
|
|
|
|
logger.Errorf("关闭告警失败:从活跃表删除告警 %d 失败: %v", activeAlarm.ID, err)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.Infof("告警 %d 已成功关闭并归档。", activeAlarm.ID)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-11-09 22:34:05 +08:00
|
|
|
|
|
|
|
|
|
|
// SnoozeAlarm 忽略一个活跃告警,或更新其忽略时间。
|
|
|
|
|
|
func (s *alarmService) SnoozeAlarm(ctx context.Context, alarmID uint, duration time.Duration) error {
|
|
|
|
|
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "SnoozeAlarm")
|
|
|
|
|
|
|
|
|
|
|
|
if duration <= 0 {
|
|
|
|
|
|
return errors.New("忽略时长必须为正数")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
ignoredUntil := time.Now().Add(duration)
|
|
|
|
|
|
err := s.alarmRepo.UpdateIgnoreStatus(serviceCtx, alarmID, true, &ignoredUntil)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
|
logger.Warnf("尝试忽略一个不存在的告警: %d", alarmID)
|
|
|
|
|
|
return fmt.Errorf("告警 %d 不存在", alarmID)
|
|
|
|
|
|
}
|
|
|
|
|
|
logger.Errorf("更新告警 %d 的忽略状态失败: %v", alarmID, err)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.Infof("告警 %d 已被成功忽略,持续时间: %v", alarmID, duration)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CancelAlarmSnooze 取消对一个告警的忽略状态。
|
|
|
|
|
|
func (s *alarmService) CancelAlarmSnooze(ctx context.Context, alarmID uint) error {
|
|
|
|
|
|
serviceCtx, logger := logs.Trace(ctx, s.ctx, "CancelAlarmSnooze")
|
|
|
|
|
|
|
|
|
|
|
|
err := s.alarmRepo.UpdateIgnoreStatus(serviceCtx, alarmID, false, nil)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 如果告警本就不存在,这不是一个需要上报的错误
|
|
|
|
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
|
|
|
|
logger.Infof("尝试取消忽略一个不存在的告警: %d,无需操作", alarmID)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
logger.Errorf("取消告警 %d 的忽略状态失败: %v", alarmID, err)
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
logger.Infof("告警 %d 的忽略状态已被成功取消。", alarmID)
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|