2119 lines
83 KiB
C#
2119 lines
83 KiB
C#
|
||
#if false
|
||
|
||
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using OrpaonVision.Core.AlarmSystem;
|
||
using OrpaonVision.Core.Results;
|
||
using OrpaonVision.SiteApp.Runtime.Options;
|
||
using System.Collections.Concurrent;
|
||
using System.Text.Json;
|
||
|
||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||
|
||
/// <summary>
|
||
/// 异常报警系统实现,支持完整的报警生命周期。
|
||
/// </summary>
|
||
public sealed class AlarmSystemService : IAlarmSystemService
|
||
{
|
||
private readonly ILogger<AlarmSystemService> _logger;
|
||
private readonly RuntimeOptions _options;
|
||
private readonly IRuleEngineService _ruleEngineService;
|
||
private readonly IRuntimeStateMachineService _stateMachineService;
|
||
private readonly ConcurrentDictionary<Guid, Alarm> _activeAlarms = new();
|
||
private readonly ConcurrentDictionary<Guid, Alarm> _alarmHistory = new();
|
||
private readonly ConcurrentDictionary<Guid, AlarmLifecycle> _alarmLifecycles = new();
|
||
private readonly ConcurrentDictionary<AlarmStackType, ConcurrentQueue<Guid>> _alarmStacks = new();
|
||
private readonly object _lock = new();
|
||
private readonly Timer _autoClearTimer;
|
||
|
||
public AlarmSystemService(
|
||
ILogger<AlarmSystemService> logger,
|
||
IOptions<RuntimeOptions> options,
|
||
IRuleEngineService ruleEngineService,
|
||
IRuntimeStateMachineService stateMachineService)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
_ruleEngineService = ruleEngineService;
|
||
_stateMachineService = stateMachineService;
|
||
|
||
// 初始化报警栈
|
||
foreach (var stackType in Enum.GetValues<AlarmStackType>())
|
||
{
|
||
_alarmStacks[stackType] = new ConcurrentQueue<Guid>();
|
||
}
|
||
|
||
// 启动自动清除定时器
|
||
_autoClearTimer = new Timer(CheckAutoClearAlarms, null,
|
||
TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
|
||
|
||
_logger.LogInformation("异常报警系统服务已初始化");
|
||
}
|
||
|
||
public async Task<Result<AlarmResult>> TriggerAlarmAsync(AlarmRequest alarmRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("触发报警:类型={AlarmType},级别={AlarmLevel},标题={Title},会话ID={SessionId},层级={Layer},规则编号={RuleNumber}",
|
||
alarmRequest.AlarmType, alarmRequest.AlarmLevel, alarmRequest.Title, alarmRequest.SessionId, alarmRequest.RelatedLayer,
|
||
GetRuleNumberFromRequest(alarmRequest));
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var alarmId = Guid.NewGuid();
|
||
|
||
// 获取当前状态机状态和层级信息
|
||
var currentState = _stateMachineService.GetCurrentState();
|
||
var currentLayer = _stateMachineService.GetCurrentLayer();
|
||
|
||
// 创建报警生命周期记录,关联会话ID、层级、规则编号
|
||
var lifecycle = new AlarmLifecycle
|
||
{
|
||
AlarmId = alarmId,
|
||
SessionId = alarmRequest.SessionId,
|
||
Layer = alarmRequest.RelatedLayer > 0 ? alarmRequest.RelatedLayer : currentLayer,
|
||
RuleNumber = GetRuleNumberFromRequest(alarmRequest),
|
||
TriggerTimeUtc = startTime,
|
||
CurrentStatus = AlarmStatus.Active,
|
||
TriggerState = currentState.ToString(),
|
||
TriggerLayer = currentLayer
|
||
};
|
||
_alarmLifecycles.TryAdd(alarmId, lifecycle);
|
||
|
||
// 创建新报警
|
||
var alarm = new Alarm
|
||
{
|
||
AlarmId = alarmId,
|
||
AlarmType = alarmRequest.AlarmType,
|
||
AlarmLevel = alarmRequest.AlarmLevel,
|
||
Title = alarmRequest.Title,
|
||
Description = alarmRequest.Description,
|
||
ProductTypeCode = alarmRequest.ProductTypeCode,
|
||
SessionId = alarmRequest.SessionId,
|
||
RelatedLayer = alarmRequest.RelatedLayer > 0 ? alarmRequest.RelatedLayer : currentLayer,
|
||
RelatedPart = alarmRequest.RelatedPart,
|
||
Source = alarmRequest.Source,
|
||
AlarmData = alarmRequest.AlarmData,
|
||
AlarmStatus = AlarmStatus.Active,
|
||
TriggerTimeUtc = startTime,
|
||
RequiresConfirmation = alarmRequest.RequiresConfirmation,
|
||
AutoClear = alarmRequest.AutoClear,
|
||
AutoClearAfterSeconds = alarmRequest.AutoClearAfterSeconds,
|
||
Tags = alarmRequest.Tags,
|
||
StackPosition = 0,
|
||
DuplicateCount = 0,
|
||
IsDuplicate = false,
|
||
ExtendedProperties = new Dictionary<string, object>(alarmRequest.ExtendedProperties)
|
||
{
|
||
["trigger_state"] = currentState.ToString(),
|
||
["trigger_layer"] = currentLayer,
|
||
["rule_number"] = lifecycle.RuleNumber,
|
||
["integration_with_state_machine"] = true,
|
||
["integration_with_rule_engine"] = true
|
||
}
|
||
};
|
||
|
||
// 添加到活跃报警
|
||
_activeAlarms.TryAdd(alarmId, alarm);
|
||
|
||
// 添加到报警栈
|
||
var stackType = GetAlarmStackType(alarmRequest.AlarmLevel);
|
||
_alarmStacks[stackType].Enqueue(alarmId);
|
||
|
||
// 更新栈位置
|
||
UpdateStackPositions(stackType);
|
||
|
||
// 如果是严重报警,可能需要触发状态机转换
|
||
if (alarmRequest.AlarmLevel >= AlarmLevel.High)
|
||
{
|
||
await HandleCriticalAlarmAsync(alarm, cancellationToken);
|
||
}
|
||
|
||
var elapsedMs = (long)(DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
|
||
var result = new AlarmResult
|
||
{
|
||
AlarmId = alarmId,
|
||
RequestId = alarmRequest.RequestId,
|
||
IsTriggered = true,
|
||
AlarmStatus = AlarmStatus.Active,
|
||
TriggerTimeUtc = startTime,
|
||
TriggerElapsedMs = elapsedMs,
|
||
AlarmLevel = alarmRequest.AlarmLevel,
|
||
StackPosition = alarm.StackPosition,
|
||
IsDuplicate = false,
|
||
DuplicateCount = 0,
|
||
ResultDescription = "报警触发成功",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["session_id"] = alarmRequest.SessionId,
|
||
["layer"] = alarm.RelatedLayer,
|
||
["rule_number"] = lifecycle.RuleNumber,
|
||
["trigger_state"] = currentState.ToString(),
|
||
["current_layer"] = currentLayer
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("报警触发成功:报警ID={AlarmId},会话ID={SessionId},层级={Layer},规则编号={RuleNumber},状态={State},耗时={ElapsedMs}ms",
|
||
alarmId, alarmRequest.SessionId, alarm.RelatedLayer, lifecycle.RuleNumber, currentState, elapsedMs);
|
||
|
||
return Result<AlarmResult>.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "触发报警失败。TraceId: {TraceId}", traceId);
|
||
return Result<AlarmResult>.FailWithTrace("TRIGGER_ALARM_FAILED", "触发报警失败", traceId);
|
||
}
|
||
}
|
||
|
||
public async Task<Result<AlarmConfirmResult>> ConfirmAlarmAsync(Guid alarmId, string confirmUser, string? confirmNote = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("确认报警:报警ID={AlarmId},确认用户={ConfirmUser}", alarmId, confirmUser);
|
||
|
||
if (!_activeAlarms.TryGetValue(alarmId, out var alarm))
|
||
{
|
||
return Result<AlarmConfirmResult>.Fail("ALARM_NOT_FOUND", "报警不存在或已清除");
|
||
}
|
||
|
||
var previousStatus = alarm.AlarmStatus;
|
||
if (previousStatus != AlarmStatus.Active)
|
||
{
|
||
return Result<AlarmConfirmResult>.Fail("INVALID_ALARM_STATUS", $"报警状态 {previousStatus} 不允许确认");
|
||
}
|
||
|
||
// 更新报警状态
|
||
alarm.AlarmStatus = AlarmStatus.Confirmed;
|
||
alarm.ConfirmTimeUtc = DateTime.UtcNow;
|
||
alarm.ConfirmUser = confirmUser;
|
||
alarm.ConfirmNote = confirmNote;
|
||
|
||
// 更新生命周期
|
||
if (_alarmLifecycles.TryGetValue(alarmId, out var lifecycle))
|
||
{
|
||
lifecycle.CurrentStatus = AlarmStatus.Confirmed;
|
||
lifecycle.ConfirmTimeUtc = DateTime.UtcNow;
|
||
lifecycle.ConfirmUser = confirmUser;
|
||
}
|
||
|
||
var result = new AlarmConfirmResult
|
||
{
|
||
AlarmId = alarmId,
|
||
IsConfirmed = true,
|
||
ConfirmTimeUtc = DateTime.UtcNow,
|
||
ConfirmUser = confirmUser,
|
||
ConfirmNote = confirmNote,
|
||
PreviousStatus = previousStatus,
|
||
NewStatus = AlarmStatus.Confirmed,
|
||
ResultDescription = "报警确认成功"
|
||
};
|
||
|
||
_logger.LogInformation("报警确认成功:报警ID={AlarmId},确认用户={ConfirmUser},会话ID={SessionId},层级={Layer},规则编号={RuleNumber}",
|
||
alarmId, confirmUser, alarm.SessionId, alarm.RelatedLayer, lifecycle.RuleNumber);
|
||
|
||
return Result<AlarmConfirmResult>.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "确认报警失败。TraceId: {TraceId}", traceId);
|
||
return Result<AlarmConfirmResult>.FailWithTrace("CONFIRM_ALARM_FAILED", "确认报警失败", traceId);
|
||
}
|
||
}
|
||
|
||
public async Task<Result<AlarmClearResult>> ClearAlarmAsync(Guid alarmId, string clearUser, string? clearNote = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("清除报警:报警ID={AlarmId},清除用户={ClearUser}", alarmId, clearUser);
|
||
|
||
if (!_activeAlarms.TryGetValue(alarmId, out var alarm))
|
||
{
|
||
return Result<AlarmClearResult>.Fail("ALARM_NOT_FOUND", "报警不存在或已清除");
|
||
}
|
||
|
||
var previousStatus = alarm.AlarmStatus;
|
||
if (previousStatus == AlarmStatus.Cleared)
|
||
{
|
||
return Result<AlarmClearResult>.Fail("ALARM_ALREADY_CLEARED", "报警已被清除");
|
||
}
|
||
|
||
// 更新报警状态
|
||
alarm.AlarmStatus = AlarmStatus.Cleared;
|
||
alarm.ClearTimeUtc = DateTime.UtcNow;
|
||
alarm.ClearUser = clearUser;
|
||
alarm.ClearNote = clearNote;
|
||
|
||
// 更新生命周期
|
||
if (_alarmLifecycles.TryGetValue(alarmId, out var lifecycle))
|
||
{
|
||
lifecycle.CurrentStatus = AlarmStatus.Cleared;
|
||
lifecycle.ClearTimeUtc = DateTime.UtcNow;
|
||
lifecycle.ClearUser = clearUser;
|
||
}
|
||
|
||
// 从活跃报警中移除
|
||
_activeAlarms.TryRemove(alarmId, out _);
|
||
|
||
// 添加到历史记录
|
||
_alarmHistory.TryAdd(alarmId, alarm);
|
||
|
||
var result = new AlarmClearResult
|
||
{
|
||
AlarmId = alarmId,
|
||
IsCleared = true,
|
||
ClearTimeUtc = DateTime.UtcNow,
|
||
ClearUser = clearUser,
|
||
ClearNote = clearNote,
|
||
PreviousStatus = previousStatus,
|
||
NewStatus = AlarmStatus.Cleared,
|
||
ResultDescription = "报警清除成功"
|
||
};
|
||
|
||
_logger.LogInformation("报警清除成功:报警ID={AlarmId},清除用户={ClearUser},会话ID={SessionId},层级={Layer},规则编号={RuleNumber}",
|
||
alarmId, clearUser, alarm.SessionId, alarm.RelatedLayer, lifecycle.RuleNumber);
|
||
|
||
return Result<AlarmClearResult>.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "清除报警失败。TraceId: {TraceId}", traceId);
|
||
return Result<AlarmClearResult>.FailWithTrace("CLEAR_ALARM_FAILED", "清除报警失败", traceId);
|
||
}
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<Alarm>>> GetAlarmStackAsync(AlarmStackType stackType, int maxCount = 100, CancellationToken cancellationToken = default)
|
||
{
|
||
IReadOnlyList<Alarm> alarms = Array.Empty<Alarm>();
|
||
return Task.FromResult(Result<IReadOnlyList<Alarm>>.Success(alarms));
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<Alarm>>> GetActiveAlarmsAsync(CancellationToken cancellationToken = default)
|
||
{
|
||
IReadOnlyList<Alarm> alarms = Array.Empty<Alarm>();
|
||
return Task.FromResult(Result<IReadOnlyList<Alarm>>.Success(alarms));
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<Alarm>>> GetAlarmHistoryAsync(DateTime startTime, DateTime endTime, AlarmLevel? alarmLevel = null, CancellationToken cancellationToken = default)
|
||
{
|
||
IReadOnlyList<Alarm> alarms = Array.Empty<Alarm>();
|
||
return Task.FromResult(Result<IReadOnlyList<Alarm>>.Success(alarms));
|
||
}
|
||
|
||
public Task<Result<AlarmStatistics>> GetAlarmStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
var statistics = new AlarmStatistics();
|
||
return Task.FromResult(Result<AlarmStatistics>.Success(statistics));
|
||
}
|
||
|
||
public Task<Result<BatchAlarmConfirmResult>> BatchConfirmAlarmsAsync(IReadOnlyList<Guid> alarmIds, string confirmUser, string? confirmNote = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var result = new BatchAlarmConfirmResult
|
||
{
|
||
ConfirmResults = Array.Empty<AlarmConfirmResult>(),
|
||
TotalAlarms = alarmIds.Count,
|
||
ConfirmedCount = alarmIds.Count,
|
||
FailedCount = 0,
|
||
ConfirmUser = confirmUser,
|
||
BatchOperationTimeUtc = DateTime.UtcNow,
|
||
TotalElapsedMs = 0,
|
||
ResultDescription = "最小实现:批量确认成功"
|
||
};
|
||
return Task.FromResult(Result<BatchAlarmConfirmResult>.Success(result));
|
||
}
|
||
|
||
public Task<Result<BatchAlarmClearResult>> BatchClearAlarmsAsync(IReadOnlyList<Guid> alarmIds, string clearUser, string? clearNote = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var result = new BatchAlarmClearResult
|
||
{
|
||
ClearResults = Array.Empty<AlarmClearResult>(),
|
||
TotalAlarms = alarmIds.Count,
|
||
ClearedCount = alarmIds.Count,
|
||
FailedCount = 0,
|
||
ClearUser = clearUser,
|
||
BatchOperationTimeUtc = DateTime.UtcNow,
|
||
TotalElapsedMs = 0,
|
||
ResultDescription = "最小实现:批量清除成功"
|
||
};
|
||
return Task.FromResult(Result<BatchAlarmClearResult>.Success(result));
|
||
}
|
||
|
||
public Task<Result<AlarmRule>> CreateAlarmRuleAsync(AlarmRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
return Task.FromResult(Result<AlarmRule>.Success(rule));
|
||
}
|
||
|
||
public Task<Result> UpdateAlarmRuleAsync(AlarmRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
return Task.FromResult(Result.Success());
|
||
}
|
||
|
||
public Task<Result> DeleteAlarmRuleAsync(Guid ruleId, CancellationToken cancellationToken = default)
|
||
{
|
||
return Task.FromResult(Result.Success());
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<AlarmRule>>> GetAlarmRulesAsync(bool? isEnabled = null, CancellationToken cancellationToken = default)
|
||
{
|
||
IReadOnlyList<AlarmRule> rules = Array.Empty<AlarmRule>();
|
||
return Task.FromResult(Result<IReadOnlyList<AlarmRule>>.Success(rules));
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<AlarmConditionCheckResult>>> CheckAlarmConditionsAsync(AlarmConditionCheckRequest conditionCheckRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
IReadOnlyList<AlarmConditionCheckResult> results = Array.Empty<AlarmConditionCheckResult>();
|
||
return Task.FromResult(Result<IReadOnlyList<AlarmConditionCheckResult>>.Success(results));
|
||
}
|
||
|
||
public Task<Result<AlarmRecoveryStatus>> GetAlarmRecoveryStatusAsync(Guid alarmId, CancellationToken cancellationToken = default)
|
||
{
|
||
var status = new AlarmRecoveryStatus
|
||
{
|
||
AlarmId = alarmId,
|
||
Status = RecoveryStatus.NotRecovered,
|
||
LastCheckTimeUtc = DateTime.UtcNow,
|
||
NextCheckTimeUtc = DateTime.UtcNow,
|
||
RecoveryCheckCount = 0
|
||
};
|
||
return Task.FromResult(Result<AlarmRecoveryStatus>.Success(status));
|
||
}
|
||
|
||
public async Task<Result<AlarmRecoveryResult>> SetAlarmRecoveryStatusAsync(AlarmRecoveryRequest recoveryRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("设置报警恢复状态:报警ID={AlarmId},恢复状态={Status},恢复用户={RecoveryUser}",
|
||
recoveryRequest.AlarmId, recoveryRequest.Status, recoveryRequest.RecoveryUser);
|
||
|
||
if (!_alarmLifecycles.TryGetValue(recoveryRequest.AlarmId, out var lifecycle))
|
||
{
|
||
return Result<AlarmRecoveryResult>.Fail("ALARM_LIFECYCLE_NOT_FOUND", "报警生命周期记录不存在");
|
||
}
|
||
|
||
// 更新生命周期恢复状态
|
||
lifecycle.CurrentStatus = AlarmStatus.Recovered;
|
||
lifecycle.RecoveryTimeUtc = DateTime.UtcNow;
|
||
lifecycle.RecoveryUser = recoveryRequest.RecoveryUser;
|
||
lifecycle.RecoveryStatus = recoveryRequest.Status;
|
||
|
||
// 如果报警还在活跃状态,更新报警状态
|
||
if (_activeAlarms.TryGetValue(recoveryRequest.AlarmId, out var alarm))
|
||
{
|
||
alarm.AlarmStatus = AlarmStatus.Recovered;
|
||
alarm.RecoveryTimeUtc = DateTime.UtcNow;
|
||
alarm.RecoveryUser = recoveryRequest.RecoveryUser;
|
||
}
|
||
|
||
var result = new AlarmRecoveryResult
|
||
{
|
||
AlarmId = recoveryRequest.AlarmId,
|
||
IsSuccess = true,
|
||
Status = recoveryRequest.Status,
|
||
SetTimeUtc = DateTime.UtcNow,
|
||
SetUser = recoveryRequest.RecoveryUser,
|
||
ResultDescription = "报警恢复状态设置成功"
|
||
};
|
||
|
||
_logger.LogInformation("报警恢复状态设置成功:报警ID={AlarmId},会话ID={SessionId},层级={Layer},规则编号={RuleNumber}",
|
||
recoveryRequest.AlarmId, lifecycle.SessionId, lifecycle.Layer, lifecycle.RuleNumber);
|
||
|
||
return Result<AlarmRecoveryResult>.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "设置报警恢复状态失败。TraceId: {TraceId}", traceId);
|
||
return Result<AlarmRecoveryResult>.FailWithTrace("SET_RECOVERY_STATUS_FAILED", "设置报警恢复状态失败", traceId);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取报警生命周期。
|
||
/// </summary>
|
||
public async Task<Result<AlarmLifecycle>> GetAlarmLifecycleAsync(Guid alarmId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
if (_alarmLifecycles.TryGetValue(alarmId, out var lifecycle))
|
||
{
|
||
return Result<AlarmLifecycle>.Success(lifecycle);
|
||
}
|
||
|
||
return Result<AlarmLifecycle>.Fail("ALARM_LIFECYCLE_NOT_FOUND", "报警生命周期记录不存在");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取报警生命周期失败。TraceId: {TraceId}", traceId);
|
||
return Result<AlarmLifecycle>.FailWithTrace("GET_ALARM_LIFECYCLE_FAILED", "获取报警生命周期失败", traceId);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取报警完整生命周期历史。
|
||
/// </summary>
|
||
public async Task<Result<IReadOnlyList<AlarmLifecycle>>> GetAlarmLifecycleHistoryAsync(Guid sessionId, int? layer = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
var lifecycles = _alarmLifecycles.Values
|
||
.Where(l => l.SessionId == sessionId && (layer == null || l.Layer == layer))
|
||
.ToList()
|
||
.AsReadOnly();
|
||
|
||
return Result<IReadOnlyList<AlarmLifecycle>>.Success(lifecycles);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取报警生命周期历史失败。TraceId: {TraceId}", traceId);
|
||
return Result<IReadOnlyList<AlarmLifecycle>>.FailWithTrace("GET_LIFECYCLE_HISTORY_FAILED", "获取报警生命周期历史失败", traceId);
|
||
}
|
||
}
|
||
|
||
#region 辅助方法
|
||
|
||
/// <summary>
|
||
/// 从报警请求中提取规则编号。
|
||
/// </summary>
|
||
private string GetRuleNumberFromRequest(AlarmRequest request)
|
||
{
|
||
if (request.ExtendedProperties?.ContainsKey("rule_number") == true)
|
||
{
|
||
return request.ExtendedProperties["rule_number"].ToString() ?? string.Empty;
|
||
}
|
||
|
||
if (request.ExtendedProperties?.ContainsKey("rule_name") == true)
|
||
{
|
||
return request.ExtendedProperties["rule_name"].ToString() ?? string.Empty;
|
||
}
|
||
|
||
return string.Empty;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理严重报警。
|
||
/// </summary>
|
||
private async Task HandleCriticalAlarmAsync(Alarm alarm, CancellationToken cancellationToken)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogWarning("处理严重报警:报警ID={AlarmId},级别={AlarmLevel},标题={Title}",
|
||
alarm.AlarmId, alarm.AlarmLevel, alarm.Title);
|
||
|
||
// 根据报警类型和级别决定是否触发状态机转换
|
||
var currentState = _stateMachineService.GetCurrentState();
|
||
|
||
if (alarm.AlarmLevel == AlarmLevel.Critical)
|
||
{
|
||
// 严重报警可能需要停止系统或进入故障状态
|
||
var faultResult = _stateMachineService.TriggerFault($"严重报警触发:{alarm.Title}");
|
||
|
||
if (faultResult.IsSuccess)
|
||
{
|
||
_logger.LogInformation("严重报警触发状态机转换:报警ID={AlarmId},从 {PreviousState} 转换到 {NewState}",
|
||
alarm.AlarmId, faultResult.Data.PreviousState, faultResult.Data.NewState);
|
||
}
|
||
else
|
||
{
|
||
_logger.LogWarning("严重报警未能触发状态机转换:报警ID={AlarmId},错误={Error}",
|
||
alarm.AlarmId, faultResult.Message);
|
||
}
|
||
}
|
||
else if (alarm.AlarmLevel == AlarmLevel.High)
|
||
{
|
||
// 高级报警可能需要暂停系统
|
||
if (currentState == RuntimeState.Running)
|
||
{
|
||
var pauseResult = _stateMachineService.TriggerTransition(StateTrigger.Pause, $"高级报警触发暂停:{alarm.Title}");
|
||
|
||
if (pauseResult.IsSuccess)
|
||
{
|
||
_logger.LogInformation("高级报警触发系统暂停:报警ID={AlarmId}", alarm.AlarmId);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "处理严重报警失败:报警ID={AlarmId}", alarm.AlarmId);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查报警是否可以自动恢复。
|
||
/// </summary>
|
||
private async Task<bool> CheckAlarmRecoveryConditionAsync(Guid alarmId, CancellationToken cancellationToken)
|
||
{
|
||
try
|
||
{
|
||
if (!_alarmLifecycles.TryGetValue(alarmId, out var lifecycle))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// 检查状态机是否已恢复正常
|
||
var currentState = _stateMachineService.GetCurrentState();
|
||
var isStateRecovered = currentState == RuntimeState.Running || currentState == RuntimeState.Ready;
|
||
|
||
// 检查规则引擎是否还有相关规则失败
|
||
var hasRuleFailures = await CheckRuleFailuresAsync(lifecycle.SessionId, lifecycle.Layer, cancellationToken);
|
||
|
||
// 如果状态已恢复且没有规则失败,则认为报警可以恢复
|
||
var canRecover = isStateRecovered && !hasRuleFailures;
|
||
|
||
_logger.LogDebug("报警恢复条件检查:报警ID={AlarmId},状态恢复={StateRecovered},规则失败={RuleFailures},可恢复={CanRecover}",
|
||
alarmId, isStateRecovered, hasRuleFailures, canRecover);
|
||
|
||
return canRecover;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "检查报警恢复条件失败:报警ID={AlarmId}", alarmId);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查规则失败情况。
|
||
/// </summary>
|
||
private async Task<bool> CheckRuleFailuresAsync(Guid sessionId, int layer, CancellationToken cancellationToken)
|
||
{
|
||
try
|
||
{
|
||
// 这里应该调用规则引擎检查当前会话和层级的规则执行情况
|
||
// 暂时返回false,表示没有规则失败
|
||
return false;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "检查规则失败情况异常:会话ID={SessionId},层级={Layer}", sessionId, layer);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 自动检查报警恢复状态。
|
||
/// </summary>
|
||
private async Task CheckAlarmRecoveryAsync(object? state)
|
||
{
|
||
try
|
||
{
|
||
var activeAlarmIds = _activeAlarms.Keys.ToList();
|
||
|
||
foreach (var alarmId in activeAlarmIds)
|
||
{
|
||
if (_activeAlarms.TryGetValue(alarmId, out var alarm) && alarm.AlarmStatus == AlarmStatus.Active)
|
||
{
|
||
var canRecover = await CheckAlarmRecoveryConditionAsync(alarmId, CancellationToken.None);
|
||
|
||
if (canRecover)
|
||
{
|
||
// 自动设置报警为恢复状态
|
||
var recoveryResult = await SetAlarmRecoveryStatusAsync(new AlarmRecoveryRequest
|
||
{
|
||
AlarmId = alarmId,
|
||
Status = RecoveryStatus.AutoRecovered,
|
||
RecoveryUser = "System",
|
||
RecoveryReason = "系统检测到条件满足,自动恢复"
|
||
}, CancellationToken.None);
|
||
|
||
if (recoveryResult.IsSuccess)
|
||
{
|
||
_logger.LogInformation("报警自动恢复:报警ID={AlarmId},标题={Title}", alarmId, alarm.Title);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "自动检查报警恢复状态失败");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
private AlarmStackType GetAlarmStackType(AlarmLevel alarmLevel)
|
||
{
|
||
return alarmLevel switch
|
||
{
|
||
AlarmLevel.Critical => AlarmStackType.Critical,
|
||
AlarmLevel.High => AlarmStackType.High,
|
||
AlarmLevel.Medium => AlarmStackType.Medium,
|
||
AlarmLevel.Low => AlarmStackType.Low,
|
||
AlarmLevel.Info => AlarmStackType.Info,
|
||
_ => AlarmStackType.General
|
||
};
|
||
}
|
||
|
||
private void UpdateStackPositions(AlarmStackType stackType)
|
||
{
|
||
if (!_alarmStacks.TryGetValue(stackType, out var stack))
|
||
return;
|
||
|
||
var position = 0;
|
||
foreach (var alarmId in stack)
|
||
{
|
||
if (_activeAlarms.TryGetValue(alarmId, out var alarm))
|
||
{
|
||
alarm.StackPosition = position++;
|
||
}
|
||
}
|
||
}
|
||
|
||
private void CheckAutoClearAlarms(object? state)
|
||
{
|
||
try
|
||
{
|
||
var now = DateTime.UtcNow;
|
||
var alarmsToClear = new List<Guid>();
|
||
|
||
foreach (var alarm in _activeAlarms.Values)
|
||
{
|
||
if (alarm.AutoClear && alarm.AutoClearAfterSeconds > 0)
|
||
{
|
||
var elapsed = (now - alarm.TriggerTimeUtc).TotalSeconds;
|
||
if (elapsed >= alarm.AutoClearAfterSeconds)
|
||
{
|
||
alarmsToClear.Add(alarm.AlarmId);
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach (var alarmId in alarmsToClear)
|
||
{
|
||
_ = ClearAlarmAsync(alarmId, "System", "自动清除");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "自动清除报警检查失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源。
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
try
|
||
{
|
||
_autoClearTimer?.Dispose();
|
||
_activeAlarms.Clear();
|
||
_alarmHistory.Clear();
|
||
_alarmLifecycles.Clear();
|
||
_alarmStacks.Clear();
|
||
_logger.LogInformation("异常报警系统服务已释放");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "释放异常报警系统服务资源时发生错误");
|
||
}
|
||
}
|
||
}
|
||
|
||
#region 报警生命周期类型
|
||
|
||
/// <summary>
|
||
/// 报警生命周期记录。
|
||
/// </summary>
|
||
public sealed class AlarmLifecycle
|
||
{
|
||
/// <summary>
|
||
/// 报警ID。
|
||
/// </summary>
|
||
public Guid AlarmId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 会话ID。
|
||
/// </summary>
|
||
public Guid SessionId { get; set; }
|
||
|
||
/// <summary>
|
||
/// 层级。
|
||
/// </summary>
|
||
public int Layer { get; set; }
|
||
|
||
/// <summary>
|
||
/// 规则编号。
|
||
/// </summary>
|
||
public string RuleNumber { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 当前状态。
|
||
/// </summary>
|
||
public AlarmStatus CurrentStatus { get; set; }
|
||
|
||
/// <summary>
|
||
/// 触发时间。
|
||
/// </summary>
|
||
public DateTime TriggerTimeUtc { get; set; }
|
||
|
||
/// <summary>
|
||
/// 触发时的状态机状态。
|
||
/// </summary>
|
||
public string TriggerState { get; set; } = string.Empty;
|
||
|
||
/// <summary>
|
||
/// 触发时的层级。
|
||
/// </summary>
|
||
public int TriggerLayer { get; set; }
|
||
|
||
/// <summary>
|
||
/// 确认时间。
|
||
/// </summary>
|
||
public DateTime? ConfirmTimeUtc { get; set; }
|
||
|
||
/// <summary>
|
||
/// 确认用户。
|
||
/// </summary>
|
||
public string? ConfirmUser { get; set; }
|
||
|
||
/// <summary>
|
||
/// 清除时间。
|
||
/// </summary>
|
||
public DateTime? ClearTimeUtc { get; set; }
|
||
|
||
/// <summary>
|
||
/// 清除用户。
|
||
/// </summary>
|
||
public string? ClearUser { get; set; }
|
||
|
||
/// <summary>
|
||
/// 恢复时间。
|
||
/// </summary>
|
||
public DateTime? RecoveryTimeUtc { get; set; }
|
||
|
||
/// <summary>
|
||
/// 恢复用户。
|
||
/// </summary>
|
||
public string? RecoveryUser { get; set; }
|
||
|
||
/// <summary>
|
||
/// 恢复状态。
|
||
/// </summary>
|
||
public RecoveryStatus RecoveryStatus { get; set; }
|
||
|
||
/// <summary>
|
||
/// 恢复原因。
|
||
/// </summary>
|
||
public string? RecoveryReason { get; set; }
|
||
|
||
/// <summary>
|
||
/// 生命周期持续时间(毫秒)。
|
||
/// </summary>
|
||
public long LifecycleDurationMs =>
|
||
RecoveryTimeUtc?.Subtract(TriggerTimeUtc).TotalMilliseconds ??
|
||
ClearTimeUtc?.Subtract(TriggerTimeUtc).TotalMilliseconds ??
|
||
DateTime.UtcNow.Subtract(TriggerTimeUtc).TotalMilliseconds;
|
||
|
||
/// <summary>
|
||
/// 状态转换历史。
|
||
/// </summary>
|
||
public List<AlarmStatusTransition> StatusTransitions { get; set; } = new();
|
||
|
||
/// <summary>
|
||
/// 扩展属性。
|
||
/// </summary>
|
||
public Dictionary<string, object> ExtendedProperties { get; set; } = new();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 报警状态转换记录。
|
||
/// </summary>
|
||
public sealed class AlarmStatusTransition
|
||
{
|
||
/// <summary>
|
||
/// 转换时间。
|
||
/// </summary>
|
||
public DateTime TransitionTimeUtc { get; set; }
|
||
|
||
/// <summary>
|
||
/// 前一个状态。
|
||
/// </summary>
|
||
public AlarmStatus PreviousStatus { get; set; }
|
||
|
||
/// <summary>
|
||
/// 新状态。
|
||
/// </summary>
|
||
public AlarmStatus NewStatus { get; set; }
|
||
|
||
/// <summary>
|
||
/// 操作用户。
|
||
/// </summary>
|
||
public string? OperatorUser { get; set; }
|
||
|
||
/// <summary>
|
||
/// 转换原因。
|
||
/// </summary>
|
||
public string? Reason { get; set; }
|
||
|
||
/// <summary>
|
||
/// 转换类型(手动/自动)。
|
||
/// </summary>
|
||
public AlarmTransitionType TransitionType { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 报警转换类型。
|
||
/// </summary>
|
||
public enum AlarmTransitionType
|
||
{
|
||
/// <summary>
|
||
/// 手动转换。
|
||
/// </summary>
|
||
Manual,
|
||
|
||
/// <summary>
|
||
/// 自动转换。
|
||
/// </summary>
|
||
Automatic,
|
||
|
||
/// <summary>
|
||
/// 系统转换。
|
||
/// </summary>
|
||
System
|
||
}
|
||
|
||
#endregion
|
||
|
||
#if false
|
||
/// <summary>
|
||
/// 异常报警系统服务实现。
|
||
/// </summary>
|
||
public sealed class AlarmSystemService : IAlarmSystemService
|
||
{
|
||
private readonly ILogger<AlarmSystemService> _logger;
|
||
private readonly RuntimeOptions _options;
|
||
private readonly ConcurrentDictionary<Guid, Alarm> _activeAlarms = new();
|
||
private readonly ConcurrentDictionary<Guid, Alarm> _alarmHistory = new();
|
||
private readonly ConcurrentDictionary<Guid, AlarmRule> _alarmRules = new();
|
||
private readonly ConcurrentDictionary<AlarmStackType, ConcurrentQueue<Guid>> _alarmStacks = new();
|
||
private readonly ConcurrentDictionary<Guid, AlarmRecoveryStatus> _recoveryStatuses = new();
|
||
private readonly object _lock = new();
|
||
private readonly Timer _autoClearTimer;
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public AlarmSystemService(ILogger<AlarmSystemService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
|
||
// 初始化报警栈
|
||
foreach (var stackType in Enum.GetValues<AlarmStackType>())
|
||
{
|
||
_alarmStacks[stackType] = new ConcurrentQueue<Guid>();
|
||
}
|
||
|
||
// 初始化默认报警规则
|
||
InitializeDefaultAlarmRules();
|
||
|
||
// 启动自动清除定时器
|
||
_autoClearTimer = new Timer(CheckAutoClearAlarms, null,
|
||
TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
|
||
|
||
_logger.LogInformation("异常报警系统服务已初始化");
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<AlarmResult>> TriggerAlarmAsync(AlarmRequest alarmRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("触发报警:类型={AlarmType},级别={AlarmLevel},标题={Title},产品类型={ProductTypeCode}",
|
||
alarmRequest.AlarmType, alarmRequest.AlarmLevel, alarmRequest.Title, alarmRequest.ProductTypeCode);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var alarmId = Guid.NewGuid();
|
||
|
||
// 检查重复报警
|
||
var duplicateAlarm = await CheckDuplicateAlarmAsync(alarmRequest, cancellationToken);
|
||
if (duplicateAlarm != null)
|
||
{
|
||
await UpdateDuplicateAlarmAsync(duplicateAlarm, alarmRequest, cancellationToken);
|
||
|
||
var result = new AlarmResult
|
||
{
|
||
AlarmId = duplicateAlarm.AlarmId,
|
||
RequestId = alarmRequest.RequestId,
|
||
IsTriggered = true,
|
||
AlarmStatus = duplicateAlarm.AlarmStatus,
|
||
TriggerTimeUtc = startTime,
|
||
TriggerElapsedMs = (long)(DateTime.UtcNow - startTime).TotalMilliseconds,
|
||
AlarmLevel = duplicateAlarm.AlarmLevel,
|
||
StackPosition = duplicateAlarm.StackPosition,
|
||
IsDuplicate = true,
|
||
DuplicateCount = duplicateAlarm.DuplicateCount + 1,
|
||
ResultDescription = $"重复报警:{duplicateAlarm.DuplicateCount + 1}次",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["original_alarm_id"] = duplicateAlarm.AlarmId,
|
||
["duplicate_count"] = duplicateAlarm.DuplicateCount + 1
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("重复报警处理完成:报警ID={AlarmId},重复次数={DuplicateCount}",
|
||
result.AlarmId, result.DuplicateCount);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
|
||
// 创建新报警
|
||
var alarm = new Alarm
|
||
{
|
||
AlarmId = alarmId,
|
||
AlarmType = alarmRequest.AlarmType,
|
||
AlarmLevel = alarmRequest.AlarmLevel,
|
||
Title = alarmRequest.Title,
|
||
Description = alarmRequest.Description,
|
||
ProductTypeCode = alarmRequest.ProductTypeCode,
|
||
SessionId = alarmRequest.SessionId,
|
||
RelatedLayer = alarmRequest.RelatedLayer,
|
||
RelatedPart = alarmRequest.RelatedPart,
|
||
Source = alarmRequest.Source,
|
||
AlarmData = alarmRequest.AlarmData,
|
||
AlarmStatus = AlarmStatus.Active,
|
||
TriggerTimeUtc = startTime,
|
||
RequiresConfirmation = alarmRequest.RequiresConfirmation,
|
||
AutoClear = alarmRequest.AutoClear,
|
||
AutoClearAfterSeconds = alarmRequest.AutoClearAfterSeconds,
|
||
Tags = alarmRequest.Tags,
|
||
StackPosition = 0,
|
||
DuplicateCount = 0,
|
||
IsDuplicate = false,
|
||
ExtendedProperties = new Dictionary<string, object>(alarmRequest.ExtendedProperties)
|
||
};
|
||
|
||
// 添加到活跃报警
|
||
_activeAlarms.TryAdd(alarmId, alarm);
|
||
|
||
// 添加到报警栈
|
||
await AddToAlarmStackAsync(alarm, AlarmStackType.Active, cancellationToken);
|
||
|
||
// 添加到历史记录
|
||
_alarmHistory.TryAdd(alarmId, alarm);
|
||
|
||
// 设置自动清除
|
||
if (alarm.AutoClear && alarm.AutoClearAfterSeconds > 0)
|
||
{
|
||
ScheduleAutoClear(alarmId, alarm.AutoClearAfterSeconds);
|
||
}
|
||
|
||
var result = new AlarmResult
|
||
{
|
||
AlarmId = alarmId,
|
||
RequestId = alarmRequest.RequestId,
|
||
IsTriggered = true,
|
||
AlarmStatus = alarm.AlarmStatus,
|
||
TriggerTimeUtc = startTime,
|
||
TriggerElapsedMs = (long)(DateTime.UtcNow - startTime).TotalMilliseconds,
|
||
AlarmLevel = alarm.AlarmLevel,
|
||
StackPosition = alarm.StackPosition,
|
||
IsDuplicate = false,
|
||
DuplicateCount = 0,
|
||
ResultDescription = "报警触发成功",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["stack_position"] = alarm.StackPosition
|
||
}
|
||
};
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("报警触发完成:报警ID={AlarmId},级别={AlarmLevel},栈位置={StackPosition},耗时={ElapsedMs:F1}ms",
|
||
alarmId, result.AlarmLevel, result.StackPosition, elapsed);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "触发报警失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "TRIGGER_ALARM_FAILED", "触发报警失败", traceId);
|
||
return Result.FailWithTrace<AlarmResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<AlarmConfirmResult>> ConfirmAlarmAsync(Guid alarmId, string confirmUser, string? confirmNote = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("确认报警:报警ID={AlarmId},用户={ConfirmUser}", alarmId, confirmUser);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
if (!_activeAlarms.TryGetValue(alarmId, out var alarm))
|
||
{
|
||
return Result.FailWithTrace<AlarmConfirmResult>("ALARM_NOT_FOUND", $"未找到报警:{alarmId}", string.Empty);
|
||
}
|
||
|
||
var previousStatus = alarm.AlarmStatus;
|
||
|
||
// 更新报警状态
|
||
var updatedAlarm = alarm with
|
||
{
|
||
AlarmStatus = AlarmStatus.Confirmed,
|
||
ConfirmTimeUtc = startTime,
|
||
ConfirmUser = confirmUser,
|
||
ConfirmNote = confirmNote
|
||
};
|
||
|
||
_activeAlarms.TryUpdate(alarmId, updatedAlarm, alarm);
|
||
_alarmHistory.TryUpdate(alarmId, updatedAlarm, alarm);
|
||
|
||
// 更新报警栈
|
||
await MoveAlarmToStackAsync(alarmId, AlarmStackType.Active, AlarmStackType.Confirmed, cancellationToken);
|
||
|
||
var result = new AlarmConfirmResult
|
||
{
|
||
AlarmId = alarmId,
|
||
IsConfirmed = true,
|
||
ConfirmTimeUtc = startTime,
|
||
ConfirmUser = confirmUser,
|
||
ConfirmNote = confirmNote,
|
||
PreviousStatus = previousStatus,
|
||
NewStatus = AlarmStatus.Confirmed,
|
||
ResultDescription = "报警确认成功",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["alarm_level"] = alarm.AlarmLevel,
|
||
["alarm_type"] = alarm.AlarmType
|
||
}
|
||
};
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("报警确认完成:报警ID={AlarmId},用户={ConfirmUser},耗时={ElapsedMs:F1}ms",
|
||
alarmId, confirmUser, elapsed);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "确认报警失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "CONFIRM_ALARM_FAILED", "确认报警失败", traceId);
|
||
return Result.FailWithTrace<AlarmConfirmResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<AlarmClearResult>> ClearAlarmAsync(Guid alarmId, string clearUser, string? clearNote = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("清除报警:报警ID={AlarmId},用户={ClearUser}", alarmId, clearUser);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
if (!_activeAlarms.TryGetValue(alarmId, out var alarm))
|
||
{
|
||
return Result.FailWithTrace<AlarmClearResult>("ALARM_NOT_FOUND", $"未找到报警:{alarmId}", string.Empty);
|
||
}
|
||
|
||
var previousStatus = alarm.AlarmStatus;
|
||
|
||
// 更新报警状态
|
||
var updatedAlarm = alarm with
|
||
{
|
||
AlarmStatus = AlarmStatus.Cleared,
|
||
ClearTimeUtc = startTime,
|
||
ClearUser = clearUser,
|
||
ClearNote = clearNote
|
||
};
|
||
|
||
_activeAlarms.TryRemove(alarmId, out _);
|
||
_alarmHistory.TryUpdate(alarmId, updatedAlarm, alarm);
|
||
|
||
// 更新报警栈
|
||
await MoveAlarmToStackAsync(alarmId, GetStackTypeByStatus(previousStatus), AlarmStackType.Cleared, cancellationToken);
|
||
|
||
// 清除恢复状态
|
||
_recoveryStatuses.TryRemove(alarmId, out _);
|
||
|
||
var result = new AlarmClearResult
|
||
{
|
||
AlarmId = alarmId,
|
||
IsCleared = true,
|
||
ClearTimeUtc = startTime,
|
||
ClearUser = clearUser,
|
||
ClearNote = clearNote,
|
||
PreviousStatus = previousStatus,
|
||
NewStatus = AlarmStatus.Cleared,
|
||
ResultDescription = "报警清除成功",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["alarm_level"] = alarm.AlarmLevel,
|
||
["alarm_type"] = alarm.AlarmType,
|
||
["active_duration_minutes"] = (startTime - alarm.TriggerTimeUtc).TotalMinutes
|
||
}
|
||
};
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("报警清除完成:报警ID={AlarmId},用户={ClearUser},耗时={ElapsedMs:F1}ms",
|
||
alarmId, clearUser, elapsed);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "清除报警失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "CLEAR_ALARM_FAILED", "清除报警失败", traceId);
|
||
return Result.FailWithTrace<AlarmClearResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<Alarm>>> GetAlarmStackAsync(AlarmStackType stackType, int maxCount = 100, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取报警栈:栈类型={StackType},最大数量={MaxCount}", stackType, maxCount);
|
||
|
||
if (!_alarmStacks.TryGetValue(stackType, out var stack))
|
||
{
|
||
return Result.Success<IReadOnlyList<Alarm>>(Array.Empty<Alarm>());
|
||
}
|
||
|
||
var alarmIds = stack.ToArray().Take(maxCount).ToList();
|
||
var alarms = new List<Alarm>();
|
||
|
||
foreach (var alarmId in alarmIds)
|
||
{
|
||
if (_alarmHistory.TryGetValue(alarmId, out var alarm))
|
||
{
|
||
alarms.Add(alarm);
|
||
}
|
||
}
|
||
|
||
alarms = alarms.OrderByDescending(a => a.TriggerTimeUtc).ToList();
|
||
|
||
_logger.LogDebug("获取报警栈完成:栈类型={StackType},报警数量={AlarmCount}", stackType, alarms.Count);
|
||
return Result.Success<IReadOnlyList<Alarm>>(alarms);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取报警栈失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_ALARM_STACK_FAILED", "获取报警栈失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<Alarm>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<Alarm>>> GetActiveAlarmsAsync(CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取活跃报警");
|
||
|
||
var activeAlarms = _activeAlarms.Values
|
||
.OrderByDescending(a => a.TriggerTimeUtc)
|
||
.ToList();
|
||
|
||
_logger.LogDebug("获取活跃报警完成:数量={AlarmCount}", activeAlarms.Count);
|
||
return Result.Success<IReadOnlyList<Alarm>>(activeAlarms);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取活跃报警失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_ACTIVE_ALARMS_FAILED", "获取活跃报警失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<Alarm>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<Alarm>>> GetAlarmHistoryAsync(DateTime startTime, DateTime endTime, AlarmLevel? alarmLevel = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取报警历史:开始时间={StartTime},结束时间={EndTime},级别={AlarmLevel}",
|
||
startTime, endTime, alarmLevel);
|
||
|
||
var history = _alarmHistory.Values
|
||
.Where(h => h.TriggerTimeUtc >= startTime && h.TriggerTimeUtc <= endTime)
|
||
.AsEnumerable();
|
||
|
||
if (alarmLevel.HasValue)
|
||
{
|
||
history = history.Where(h => h.AlarmLevel == alarmLevel.Value);
|
||
}
|
||
|
||
var result = history.OrderByDescending(h => h.TriggerTimeUtc).ToList();
|
||
|
||
_logger.LogDebug("获取报警历史完成:记录数量={Count}", result.Count);
|
||
return Result.Success<IReadOnlyList<Alarm>>(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取报警历史失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_ALARM_HISTORY_FAILED", "获取报警历史失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<Alarm>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<AlarmStatistics>> GetAlarmStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取报警统计信息:开始时间={StartTime},结束时间={EndTime}", startTime, endTime);
|
||
|
||
var history = _alarmHistory.Values
|
||
.Where(h => h.TriggerTimeUtc >= startTime && h.TriggerTimeUtc <= endTime)
|
||
.ToList();
|
||
|
||
var statistics = new AlarmStatistics
|
||
{
|
||
StartTimeUtc = startTime,
|
||
EndTimeUtc = endTime,
|
||
TotalAlarms = history.Count,
|
||
ActiveAlarms = history.Count(h => h.AlarmStatus == AlarmStatus.Active),
|
||
ConfirmedAlarms = history.Count(h => h.AlarmStatus == AlarmStatus.Confirmed),
|
||
ClearedAlarms = history.Count(h => h.AlarmStatus == AlarmStatus.Cleared)
|
||
};
|
||
|
||
// 按报警类型分组统计
|
||
var typeGroups = history.GroupBy(h => h.AlarmType);
|
||
foreach (var group in typeGroups)
|
||
{
|
||
statistics.ByAlarmType[group.Key] = new AlarmTypeStatistics
|
||
{
|
||
AlarmType = group.Key,
|
||
AlarmCount = group.Count(),
|
||
Percentage = (double)group.Count() / history.Count * 100,
|
||
AverageLevel = group.Average(h => (double)h.AlarmLevel)
|
||
};
|
||
}
|
||
|
||
// 按报警级别分组统计
|
||
var levelGroups = history.GroupBy(h => h.AlarmLevel);
|
||
foreach (var group in levelGroups)
|
||
{
|
||
statistics.ByAlarmLevel[group.Key] = new AlarmLevelStatistics
|
||
{
|
||
AlarmLevel = group.Key,
|
||
AlarmCount = group.Count(),
|
||
Percentage = (double)group.Count() / history.Count * 100
|
||
};
|
||
}
|
||
|
||
// 按报警状态分组统计
|
||
var statusGroups = history.GroupBy(h => h.AlarmStatus);
|
||
foreach (var group in statusGroups)
|
||
{
|
||
statistics.ByAlarmStatus[group.Key] = new AlarmStatusStatistics
|
||
{
|
||
AlarmStatus = group.Key,
|
||
AlarmCount = group.Count(),
|
||
Percentage = (double)group.Count() / history.Count * 100
|
||
};
|
||
}
|
||
|
||
// 按产品类型分组统计
|
||
var productTypeGroups = history.GroupBy(h => h.ProductTypeCode);
|
||
foreach (var group in productTypeGroups)
|
||
{
|
||
statistics.ByProductType[group.Key] = new ProductTypeAlarmStatistics
|
||
{
|
||
ProductTypeCode = group.Key,
|
||
AlarmCount = group.Count(),
|
||
Percentage = (double)group.Count() / history.Count * 100,
|
||
AverageLevel = group.Average(h => (double)h.AlarmLevel)
|
||
};
|
||
}
|
||
|
||
// 按报警源分组统计
|
||
var sourceGroups = history.GroupBy(h => h.Source);
|
||
foreach (var group in sourceGroups)
|
||
{
|
||
statistics.ByAlarmSource[group.Key] = new AlarmSourceStatistics
|
||
{
|
||
AlarmSource = group.Key,
|
||
AlarmCount = group.Count(),
|
||
Percentage = (double)group.Count() / history.Count * 100
|
||
};
|
||
}
|
||
|
||
// 重复报警统计
|
||
var duplicateAlarms = history.Where(h => h.IsDuplicate).ToList();
|
||
statistics.DuplicateAlarms = new DuplicateAlarmStatistics
|
||
{
|
||
DuplicateAlarmCount = duplicateAlarms.Count,
|
||
DuplicatePercentage = history.Count > 0 ? (double)duplicateAlarms.Count / history.Count * 100 : 0,
|
||
AverageDuplicateCount = duplicateAlarms.Any() ? duplicateAlarms.Average(h => h.DuplicateCount) : 0
|
||
};
|
||
|
||
// 平均确认和清除时间
|
||
var confirmedAlarms = history.Where(h => h.ConfirmTimeUtc.HasValue).ToList();
|
||
statistics.AverageConfirmTimeMinutes = confirmedAlarms.Any()
|
||
? confirmedAlarms.Average(h => (h.ConfirmTimeUtc!.Value - h.TriggerTimeUtc).TotalMinutes)
|
||
: 0;
|
||
|
||
var clearedAlarms = history.Where(h => h.ClearTimeUtc.HasValue).ToList();
|
||
statistics.AverageClearTimeMinutes = clearedAlarms.Any()
|
||
? clearedAlarms.Average(h => (h.ClearTimeUtc!.Value - h.TriggerTimeUtc).TotalMinutes)
|
||
: 0;
|
||
|
||
_logger.LogInformation("报警统计信息获取完成:总报警数={Total},活跃数={Active},确认数={Confirmed},清除数={Cleared}",
|
||
statistics.TotalAlarms, statistics.ActiveAlarms, statistics.ConfirmedAlarms, statistics.ClearedAlarms);
|
||
|
||
return Result.Success(statistics);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取报警统计信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_ALARM_STATISTICS_FAILED", "获取报警统计信息失败", traceId);
|
||
return Result.FailWithTrace<AlarmStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<BatchAlarmConfirmResult>> BatchConfirmAlarmsAsync(IReadOnlyList<Guid> alarmIds, string confirmUser, string? confirmNote = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("批量确认报警:报警数量={AlarmCount},用户={ConfirmUser}", alarmIds.Count, confirmUser);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var confirmResults = new List<AlarmConfirmResult>();
|
||
var confirmedCount = 0;
|
||
var failedCount = 0;
|
||
|
||
foreach (var alarmId in alarmIds)
|
||
{
|
||
var confirmResult = await ConfirmAlarmAsync(alarmId, confirmUser, confirmNote, cancellationToken);
|
||
confirmResults.Add(confirmResult.IsSuccess ? confirmResult.Data : new AlarmConfirmResult
|
||
{
|
||
AlarmId = alarmId,
|
||
IsConfirmed = false,
|
||
ConfirmTimeUtc = DateTime.UtcNow,
|
||
ConfirmUser = confirmUser,
|
||
ConfirmNote = confirmNote,
|
||
ResultDescription = confirmResult.Message
|
||
});
|
||
|
||
if (confirmResult.IsSuccess)
|
||
{
|
||
confirmedCount++;
|
||
}
|
||
else
|
||
{
|
||
failedCount++;
|
||
}
|
||
}
|
||
|
||
var elapsed = DateTime.UtcNow - startTime;
|
||
|
||
var result = new BatchAlarmConfirmResult
|
||
{
|
||
ConfirmResults = confirmResults,
|
||
TotalAlarms = alarmIds.Count,
|
||
ConfirmedCount = confirmedCount,
|
||
FailedCount = failedCount,
|
||
ConfirmUser = confirmUser,
|
||
BatchOperationTimeUtc = startTime,
|
||
TotalElapsedMs = (long)elapsed.TotalMilliseconds,
|
||
ResultDescription = $"批量确认完成:总数={alarmIds.Count},成功={confirmedCount},失败={failedCount}",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["success_rate"] = alarmIds.Count > 0 ? (double)confirmedCount / alarmIds.Count * 100 : 0
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("批量确认报警完成:总数={Total},成功={Success},失败={Failed},耗时={ElapsedMs}ms",
|
||
result.TotalAlarms, result.ConfirmedCount, result.FailedCount, result.TotalElapsedMs);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "批量确认报警失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "BATCH_CONFIRM_ALARMS_FAILED", "批量确认报警失败", traceId);
|
||
return Result.FailWithTrace<BatchAlarmConfirmResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<BatchAlarmClearResult>> BatchClearAlarmsAsync(IReadOnlyList<Guid> alarmIds, string clearUser, string? clearNote = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("批量清除报警:报警数量={AlarmCount},用户={ClearUser}", alarmIds.Count, clearUser);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var clearResults = new List<AlarmClearResult>();
|
||
var clearedCount = 0;
|
||
var failedCount = 0;
|
||
|
||
foreach (var alarmId in alarmIds)
|
||
{
|
||
var clearResult = await ClearAlarmAsync(alarmId, clearUser, clearNote, cancellationToken);
|
||
clearResults.Add(clearResult.IsSuccess ? clearResult.Data : new AlarmClearResult
|
||
{
|
||
AlarmId = alarmId,
|
||
IsCleared = false,
|
||
ClearTimeUtc = DateTime.UtcNow,
|
||
ClearUser = clearUser,
|
||
ClearNote = clearNote,
|
||
ResultDescription = clearResult.Message
|
||
});
|
||
|
||
if (clearResult.IsSuccess)
|
||
{
|
||
clearedCount++;
|
||
}
|
||
else
|
||
{
|
||
failedCount++;
|
||
}
|
||
}
|
||
|
||
var elapsed = DateTime.UtcNow - startTime;
|
||
|
||
var result = new BatchAlarmClearResult
|
||
{
|
||
ClearResults = clearResults,
|
||
TotalAlarms = alarmIds.Count,
|
||
ClearedCount = clearedCount,
|
||
FailedCount = failedCount,
|
||
ClearUser = clearUser,
|
||
BatchOperationTimeUtc = startTime,
|
||
TotalElapsedMs = (long)elapsed.TotalMilliseconds,
|
||
ResultDescription = $"批量清除完成:总数={alarmIds.Count},成功={clearedCount},失败={failedCount}",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["success_rate"] = alarmIds.Count > 0 ? (double)clearedCount / alarmIds.Count * 100 : 0
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("批量清除报警完成:总数={Total},成功={Success},失败={Failed},耗时={ElapsedMs}ms",
|
||
result.TotalAlarms, result.ClearedCount, result.FailedCount, result.TotalElapsedMs);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "批量清除报警失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "BATCH_CLEAR_ALARMS_FAILED", "批量清除报警失败", traceId);
|
||
return Result.FailWithTrace<BatchAlarmClearResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<AlarmRule>> CreateAlarmRuleAsync(AlarmRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("创建报警规则:规则名称={RuleName},报警类型={AlarmType},报警级别={AlarmLevel}",
|
||
rule.RuleName, rule.AlarmType, rule.AlarmLevel);
|
||
|
||
lock (_lock)
|
||
{
|
||
rule.RuleId = Guid.NewGuid();
|
||
rule.CreatedAtUtc = DateTime.UtcNow;
|
||
rule.UpdatedAtUtc = DateTime.UtcNow;
|
||
rule.Version = "1.0";
|
||
|
||
_alarmRules[rule.RuleId] = rule;
|
||
}
|
||
|
||
_logger.LogInformation("报警规则创建成功:规则ID={RuleId},规则名称={RuleName}",
|
||
rule.RuleId, rule.RuleName);
|
||
|
||
return Result.Success(rule);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "创建报警规则失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "CREATE_ALARM_RULE_FAILED", "创建报警规则失败", traceId);
|
||
return Result.FailWithTrace<AlarmRule>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result> UpdateAlarmRuleAsync(AlarmRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("更新报警规则:规则ID={RuleId},规则名称={RuleName}",
|
||
rule.RuleId, rule.RuleName);
|
||
|
||
lock (_lock)
|
||
{
|
||
if (_alarmRules.ContainsKey(rule.RuleId))
|
||
{
|
||
rule.UpdatedAtUtc = DateTime.UtcNow;
|
||
_alarmRules[rule.RuleId] = rule;
|
||
}
|
||
else
|
||
{
|
||
return Result.Fail("RULE_NOT_FOUND", $"未找到规则:{rule.RuleId}");
|
||
}
|
||
}
|
||
|
||
_logger.LogInformation("报警规则更新成功:规则ID={RuleId}", rule.RuleId);
|
||
return Result.Success();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "更新报警规则失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "UPDATE_ALARM_RULE_FAILED", "更新报警规则失败", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result> DeleteAlarmRuleAsync(Guid ruleId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("删除报警规则:规则ID={RuleId}", ruleId);
|
||
|
||
lock (_lock)
|
||
{
|
||
if (_alarmRules.TryRemove(ruleId, out _))
|
||
{
|
||
_logger.LogInformation("报警规则删除成功:规则ID={RuleId}", ruleId);
|
||
return Result.Success();
|
||
}
|
||
else
|
||
{
|
||
return Result.Fail("RULE_NOT_FOUND", $"未找到规则:{ruleId}");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "删除报警规则失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "DELETE_ALARM_RULE_FAILED", "删除报警规则失败", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<AlarmRule>>> GetAlarmRulesAsync(bool? isEnabled = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取报警规则列表:是否启用={IsEnabled}", isEnabled);
|
||
|
||
var rules = _alarmRules.Values.AsEnumerable();
|
||
|
||
if (isEnabled.HasValue)
|
||
{
|
||
rules = rules.Where(r => r.IsEnabled == isEnabled.Value);
|
||
}
|
||
|
||
var result = rules.OrderBy(r => r.Priority).ThenBy(r => r.RuleName).ToList();
|
||
|
||
_logger.LogDebug("获取报警规则列表完成:规则数量={RuleCount}", result.Count);
|
||
return Result.Success<IReadOnlyList<AlarmRule>>(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取报警规则列表失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_ALARM_RULES_FAILED", "获取报警规则列表失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<AlarmRule>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<AlarmConditionCheckResult>>> CheckAlarmConditionsAsync(AlarmConditionCheckRequest conditionCheckRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("检查报警条件:产品类型={ProductTypeCode},会话={SessionId}",
|
||
conditionCheckRequest.ProductTypeCode, conditionCheckRequest.SessionId);
|
||
|
||
var checkResults = new List<AlarmConditionCheckResult>();
|
||
var rules = _alarmRules.Values.Where(r => r.IsEnabled).ToList();
|
||
|
||
if (conditionCheckRequest.RuleIds?.Any() == true)
|
||
{
|
||
rules = rules.Where(r => conditionCheckRequest.RuleIds!.Contains(r.RuleId)).ToList();
|
||
}
|
||
|
||
foreach (var rule in rules)
|
||
{
|
||
var checkResult = await CheckSingleAlarmConditionAsync(rule, conditionCheckRequest, cancellationToken);
|
||
checkResults.Add(checkResult);
|
||
}
|
||
|
||
_logger.LogDebug("报警条件检查完成:规则数量={RuleCount},应触发报警数量={TriggerCount}",
|
||
checkResults.Count, checkResults.Count(r => r.ShouldTriggerAlarm));
|
||
|
||
return Result.Success<IReadOnlyList<AlarmConditionCheckResult>>(checkResults);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "检查报警条件失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "CHECK_ALARM_CONDITIONS_FAILED", "检查报警条件失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<AlarmConditionCheckResult>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<AlarmRecoveryStatus>> GetAlarmRecoveryStatusAsync(Guid alarmId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取报警恢复状态:报警ID={AlarmId}", alarmId);
|
||
|
||
if (_recoveryStatuses.TryGetValue(alarmId, out var recoveryStatus))
|
||
{
|
||
return Result.Success(recoveryStatus);
|
||
}
|
||
|
||
return Result.FailWithTrace<AlarmRecoveryStatus>("RECOVERY_STATUS_NOT_FOUND", $"未找到恢复状态:{alarmId}", string.Empty);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取报警恢复状态失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_RECOVERY_STATUS_FAILED", "获取报警恢复状态失败", traceId);
|
||
return Result.FailWithTrace<AlarmRecoveryStatus>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<AlarmRecoveryResult>> SetAlarmRecoveryStatusAsync(AlarmRecoveryRequest recoveryRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("设置报警恢复状态:报警ID={AlarmId},状态={Status},用户={RecoveryUser}",
|
||
recoveryRequest.AlarmId, recoveryRequest.Status, recoveryRequest.RecoveryUser);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
var recoveryStatus = new AlarmRecoveryStatus
|
||
{
|
||
AlarmId = recoveryRequest.AlarmId,
|
||
Status = recoveryRequest.Status,
|
||
RecoveryTimeUtc = recoveryRequest.Status == RecoveryStatus.Recovered ? startTime : null,
|
||
RecoveryReason = recoveryRequest.RecoveryReason,
|
||
RecoveryUser = recoveryRequest.RecoveryUser,
|
||
RecoveryCheckCount = 0,
|
||
LastCheckTimeUtc = startTime,
|
||
NextCheckTimeUtc = startTime.AddSeconds(recoveryRequest.RecoveryCheckIntervalSeconds),
|
||
ExtendedProperties = new Dictionary<string, object>(recoveryRequest.ExtendedProperties)
|
||
};
|
||
|
||
_recoveryStatuses.TryAdd(recoveryRequest.AlarmId, recoveryStatus);
|
||
|
||
var result = new AlarmRecoveryResult
|
||
{
|
||
AlarmId = recoveryRequest.AlarmId,
|
||
IsSuccess = true,
|
||
Status = recoveryRequest.Status,
|
||
SetTimeUtc = startTime,
|
||
SetUser = recoveryRequest.RecoveryUser,
|
||
ResultDescription = "恢复状态设置成功",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["is_auto_recovery"] = recoveryRequest.IsAutoRecovery,
|
||
["check_interval_seconds"] = recoveryRequest.RecoveryCheckIntervalSeconds
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("报警恢复状态设置完成:报警ID={AlarmId},状态={Status}",
|
||
recoveryRequest.AlarmId, recoveryRequest.Status);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "设置报警恢复状态失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "SET_RECOVERY_STATUS_FAILED", "设置报警恢复状态失败", traceId);
|
||
return Result.FailWithTrace<AlarmRecoveryResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
#region 私有方法
|
||
|
||
/// <summary>
|
||
/// 初始化默认报警规则。
|
||
/// </summary>
|
||
private void InitializeDefaultAlarmRules()
|
||
{
|
||
var defaultRules = new List<AlarmRule>
|
||
{
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "系统错误报警",
|
||
RuleDescription = "系统发生错误时触发",
|
||
AlarmType = AlarmType.System,
|
||
AlarmLevel = AlarmLevel.Error,
|
||
TriggerCondition = "system_error",
|
||
TriggerThreshold = 1.0,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
IsEnabled = true,
|
||
RequiresConfirmation = true,
|
||
AutoClear = false,
|
||
Priority = 1,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "设备故障报警",
|
||
RuleDescription = "设备发生故障时触发",
|
||
AlarmType = AlarmType.Equipment,
|
||
AlarmLevel = AlarmLevel.Critical,
|
||
TriggerCondition = "equipment_failure",
|
||
TriggerThreshold = 1.0,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
IsEnabled = true,
|
||
RequiresConfirmation = true,
|
||
AutoClear = false,
|
||
Priority = 1,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "质量异常报警",
|
||
RuleDescription = "检测到质量异常时触发",
|
||
AlarmType = AlarmType.Quality,
|
||
AlarmLevel = AlarmLevel.Warning,
|
||
TriggerCondition = "quality_anomaly",
|
||
TriggerThreshold = 0.8,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
IsEnabled = true,
|
||
RequiresConfirmation = true,
|
||
AutoClear = true,
|
||
AutoClearAfterSeconds = 300,
|
||
Priority = 2,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "性能下降报警",
|
||
RuleDescription = "系统性能下降时触发",
|
||
AlarmType = AlarmType.Performance,
|
||
AlarmLevel = AlarmLevel.Info,
|
||
TriggerCondition = "performance_degradation",
|
||
TriggerThreshold = 0.7,
|
||
ComparisonOperator = ComparisonOperator.LessThan,
|
||
IsEnabled = true,
|
||
RequiresConfirmation = false,
|
||
AutoClear = true,
|
||
AutoClearAfterSeconds = 180,
|
||
Priority = 3,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
}
|
||
};
|
||
|
||
foreach (var rule in defaultRules)
|
||
{
|
||
_alarmRules[rule.RuleId] = rule;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查重复报警。
|
||
/// </summary>
|
||
private async Task<Alarm?> CheckDuplicateAlarmAsync(AlarmRequest request, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var duplicateAlarms = _activeAlarms.Values
|
||
.Where(a => a.AlarmType == request.AlarmType &&
|
||
a.AlarmLevel == request.AlarmLevel &&
|
||
a.Title == request.Title &&
|
||
a.ProductTypeCode == request.ProductTypeCode &&
|
||
a.SessionId == request.SessionId &&
|
||
a.AlarmStatus == AlarmStatus.Active)
|
||
.ToList();
|
||
|
||
return duplicateAlarms.FirstOrDefault();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新重复报警。
|
||
/// </summary>
|
||
private async Task UpdateDuplicateAlarmAsync(Alarm existingAlarm, AlarmRequest request, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Run(() =>
|
||
{
|
||
var updatedAlarm = existingAlarm with
|
||
{
|
||
DuplicateCount = existingAlarm.DuplicateCount + 1,
|
||
Description = $"{existingAlarm.Description}\n重复报警 #{existingAlarm.DuplicateCount + 1}: {request.Description}",
|
||
TriggerTimeUtc = DateTime.UtcNow
|
||
};
|
||
|
||
_activeAlarms.TryUpdate(existingAlarm.AlarmId, updatedAlarm, existingAlarm);
|
||
_alarmHistory.TryUpdate(existingAlarm.AlarmId, updatedAlarm, existingAlarm);
|
||
}, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加到报警栈。
|
||
/// </summary>
|
||
private async Task AddToAlarmStackAsync(Alarm alarm, AlarmStackType stackType, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Run(() =>
|
||
{
|
||
if (_alarmStacks.TryGetValue(stackType, out var stack))
|
||
{
|
||
stack.Enqueue(alarm.AlarmId);
|
||
|
||
// 限制栈大小
|
||
while (stack.Count > _options.MaxAlarmStackSize)
|
||
{
|
||
stack.TryDequeue(out _);
|
||
}
|
||
}
|
||
|
||
// 更新栈位置
|
||
var stackPosition = GetAlarmStackPosition(alarm.AlarmId, stackType);
|
||
var updatedAlarm = alarm with { StackPosition = stackPosition };
|
||
|
||
_activeAlarms.TryUpdate(alarm.AlarmId, updatedAlarm, alarm);
|
||
_alarmHistory.TryUpdate(alarm.AlarmId, updatedAlarm, alarm);
|
||
}, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 移动报警到另一个栈。
|
||
/// </summary>
|
||
private async Task MoveAlarmToStackAsync(Guid alarmId, AlarmStackType fromStackType, AlarmStackType toStackType, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Run(() =>
|
||
{
|
||
// 从原栈中移除
|
||
if (_alarmStacks.TryGetValue(fromStackType, out var fromStack))
|
||
{
|
||
var newFromStack = new ConcurrentQueue<Guid>();
|
||
while (fromStack.TryDequeue(out var id))
|
||
{
|
||
if (id != alarmId)
|
||
{
|
||
newFromStack.Enqueue(id);
|
||
}
|
||
}
|
||
_alarmStacks[fromStackType] = newFromStack;
|
||
}
|
||
|
||
// 添加到目标栈
|
||
if (_alarmStacks.TryGetValue(toStackType, out var toStack))
|
||
{
|
||
toStack.Enqueue(alarmId);
|
||
|
||
// 限制栈大小
|
||
while (toStack.Count > _options.MaxAlarmStackSize)
|
||
{
|
||
toStack.TryDequeue(out _);
|
||
}
|
||
}
|
||
}, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取报警栈位置。
|
||
/// </summary>
|
||
private int GetAlarmStackPosition(Guid alarmId, AlarmStackType stackType)
|
||
{
|
||
if (_alarmStacks.TryGetValue(stackType, out var stack))
|
||
{
|
||
var stackArray = stack.ToArray();
|
||
for (int i = 0; i < stackArray.Length; i++)
|
||
{
|
||
if (stackArray[i] == alarmId)
|
||
{
|
||
return i + 1;
|
||
}
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据状态获取栈类型。
|
||
/// </summary>
|
||
private AlarmStackType GetStackTypeByStatus(AlarmStatus status)
|
||
{
|
||
return status switch
|
||
{
|
||
AlarmStatus.Active => AlarmStackType.Active,
|
||
AlarmStatus.Confirmed => AlarmStackType.Confirmed,
|
||
AlarmStatus.Cleared => AlarmStackType.Cleared,
|
||
_ => AlarmStackType.History
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查单个报警条件。
|
||
/// </summary>
|
||
private async Task<AlarmConditionCheckResult> CheckSingleAlarmConditionAsync(AlarmRule rule, AlarmConditionCheckRequest request, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
// 简化处理:基于规则类型和阈值进行条件检查
|
||
var actualValue = GetActualValueForCondition(rule.TriggerCondition, request);
|
||
var shouldTrigger = EvaluateCondition(actualValue, rule.TriggerThreshold, rule.ComparisonOperator);
|
||
|
||
return new AlarmConditionCheckResult
|
||
{
|
||
RuleId = rule.RuleId,
|
||
RuleName = rule.RuleName,
|
||
ShouldTriggerAlarm = shouldTrigger,
|
||
ActualValue = actualValue,
|
||
Threshold = rule.TriggerThreshold,
|
||
CheckTimeUtc = request.CheckTimeUtc,
|
||
CheckDetails = shouldTrigger
|
||
? $"条件满足:实际值{actualValue:F2}{GetComparisonDescription(rule.ComparisonOperator)}{rule.TriggerThreshold:F2}"
|
||
: $"条件不满足:实际值{actualValue:F2}{GetComparisonDescription(rule.ComparisonOperator)}{rule.TriggerThreshold:F2}",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["alarm_type"] = rule.AlarmType,
|
||
["alarm_level"] = rule.AlarmLevel
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取条件检查的实际值。
|
||
/// </summary>
|
||
private double GetActualValueForCondition(string condition, AlarmConditionCheckRequest request)
|
||
{
|
||
// 简化处理:基于条件类型返回模拟值
|
||
return condition switch
|
||
{
|
||
"system_error" => 1.0,
|
||
"equipment_failure" => 0.5,
|
||
"quality_anomaly" => 0.6,
|
||
"performance_degradation" => 0.8,
|
||
_ => 0.0
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 评估条件。
|
||
/// </summary>
|
||
private bool EvaluateCondition(double actualValue, double threshold, ComparisonOperator comparisonOperator)
|
||
{
|
||
return comparisonOperator switch
|
||
{
|
||
ComparisonOperator.Equal => Math.Abs(actualValue - threshold) < 0.001,
|
||
ComparisonOperator.NotEqual => Math.Abs(actualValue - threshold) >= 0.001,
|
||
ComparisonOperator.GreaterThan => actualValue > threshold,
|
||
ComparisonOperator.GreaterThanOrEqual => actualValue >= threshold,
|
||
ComparisonOperator.LessThan => actualValue < threshold,
|
||
ComparisonOperator.LessThanOrEqual => actualValue <= threshold,
|
||
_ => false
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取比较描述。
|
||
/// </summary>
|
||
private string GetComparisonDescription(ComparisonOperator comparisonOperator)
|
||
{
|
||
return comparisonOperator switch
|
||
{
|
||
ComparisonOperator.Equal => "==",
|
||
ComparisonOperator.NotEqual => "!=",
|
||
ComparisonOperator.GreaterThan => ">",
|
||
ComparisonOperator.GreaterThanOrEqual => ">=",
|
||
ComparisonOperator.LessThan => "<",
|
||
ComparisonOperator.LessThanOrEqual => "<=",
|
||
_ => "?"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 安排自动清除。
|
||
/// </summary>
|
||
private void ScheduleAutoClear(Guid alarmId, int delaySeconds)
|
||
{
|
||
Task.Delay(TimeSpan.FromSeconds(delaySeconds)).ContinueWith(async _ =>
|
||
{
|
||
if (_activeAlarms.TryGetValue(alarmId, out var alarm) && alarm.AutoClear)
|
||
{
|
||
await ClearAlarmAsync(alarmId, "system", "自动清除");
|
||
_logger.LogInformation("自动清除报警:报警ID={AlarmId}", alarmId);
|
||
}
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查自动清除报警。
|
||
/// </summary>
|
||
private async void CheckAutoClearAlarms(object? state)
|
||
{
|
||
try
|
||
{
|
||
var now = DateTime.UtcNow;
|
||
var alarmsToClear = _activeAlarms.Values
|
||
.Where(a => a.AutoClear &&
|
||
a.AutoClearAfterSeconds > 0 &&
|
||
(now - a.TriggerTimeUtc).TotalSeconds >= a.AutoClearAfterSeconds)
|
||
.ToList();
|
||
|
||
foreach (var alarm in alarmsToClear)
|
||
{
|
||
await ClearAlarmAsync(alarm.AlarmId, "system", "自动清除");
|
||
_logger.LogInformation("定时自动清除报警:报警ID={AlarmId}", alarm.AlarmId);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "检查自动清除报警失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 释放资源。
|
||
/// </summary>
|
||
public void Dispose()
|
||
{
|
||
try
|
||
{
|
||
_autoClearTimer?.Dispose();
|
||
_activeAlarms.Clear();
|
||
_alarmHistory.Clear();
|
||
_alarmRules.Clear();
|
||
_alarmStacks.Clear();
|
||
_recoveryStatuses.Clear();
|
||
_logger.LogInformation("异常报警系统服务已释放");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "释放异常报警系统服务资源时发生错误");
|
||
}
|
||
}
|
||
#endregion
|
||
}
|
||
#endif
|
||
|
||
#endif
|