Files
pig-farm-controller/internal/domain/alarm/alarm_service.go

176 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package alarm
import (
"context"
"errors"
"fmt"
"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"
)
// AlarmService 定义了告警领域服务接口。
type AlarmService interface {
// 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
// SnoozeAlarm 忽略一个活跃告警,或更新其忽略时间。
// 如果告警不存在,将返回错误。
SnoozeAlarm(ctx context.Context, alarmID uint, duration time.Duration) error
// CancelAlarmSnooze 取消对一个告警的忽略状态。
// 如果告警不存在,或本就未被忽略,不执行任何操作并返回 nil。
CancelAlarmSnooze(ctx context.Context, alarmID uint) error
}
// alarmService 是 AlarmService 接口的具体实现。
type alarmService struct {
ctx context.Context
alarmRepo repository.AlarmRepository
uow repository.UnitOfWork
}
// NewAlarmService 创建一个新的 AlarmService 实例。
func NewAlarmService(ctx context.Context, alarmRepo repository.AlarmRepository, uow repository.UnitOfWork) AlarmService {
return &alarmService{
ctx: ctx,
alarmRepo: alarmRepo,
uow: uow,
}
}
// 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
})
}
// 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
}