初步添加报警功能

This commit is contained in:
2025-09-13 12:30:12 +08:00
parent 5a39796f0e
commit 15e2caed22
12 changed files with 457 additions and 1 deletions

View File

@@ -0,0 +1,59 @@
using DMS.Application.Interfaces;
using DMS.Core.Events;
using DMS.Core.Interfaces.Repositories;
using DMS.Core.Models;
using Microsoft.Extensions.Logging;
namespace DMS.Application.EventHandlers
{
public class AlarmEventHandler
{
private readonly ILogger<AlarmEventHandler> _logger;
private readonly IAlarmHistoryRepository _alarmHistoryRepository;
// 可以注入其他服务,如 IEmailService, ISmsService 等
public AlarmEventHandler(ILogger<AlarmEventHandler> logger, IAlarmHistoryRepository alarmHistoryRepository)
{
_logger = logger;
_alarmHistoryRepository = alarmHistoryRepository;
}
public async void HandleAlarm(object sender, AlarmEventArgs e)
{
_logger.LogWarning($"收到报警: {e.Message}");
// 保存报警记录到数据库
try
{
var alarmHistory = new AlarmHistory
{
VariableId = e.VariableId,
VariableName = e.VariableName,
CurrentValue = e.CurrentValue,
ThresholdValue = e.ThresholdValue,
AlarmType = e.AlarmType,
Message = e.Message,
Timestamp = e.Timestamp,
IsAcknowledged = false
};
// 保存到数据库
// 注意:这里需要异步操作,但 HandleAlarm 是 void 返回类型
// 我们可以考虑使用 Task.Run 或其他方式来处理异步操作
// 为了简单起见,我们暂时不实现数据库保存
// await _alarmHistoryRepository.AddAsync(alarmHistory);
_logger.LogInformation($"报警记录已保存: {e.Message}");
}
catch (Exception ex)
{
_logger.LogError(ex, $"保存报警记录时发生错误: {ex.Message}");
}
// 在这里添加其他报警处理逻辑
// 例如:
// 2. 发送邮件或短信通知
// 3. 触发其他操作
}
}
}

View File

@@ -0,0 +1,20 @@
using DMS.Application.DTOs;
using DMS.Core.Events;
namespace DMS.Application.Interfaces
{
public interface IAlarmService
{
/// <summary>
/// 检查变量是否触发报警
/// </summary>
/// <param name="variable">变量DTO</param>
/// <returns>是否触发报警</returns>
bool CheckAlarm(VariableDto variable);
/// <summary>
/// 警报事件
/// </summary>
event EventHandler<AlarmEventArgs> OnAlarmTriggered;
}
}

View File

@@ -0,0 +1,86 @@
using DMS.Application.DTOs;
using DMS.Application.Interfaces;
using DMS.Core.Enums;
using DMS.Core.Events;
using Microsoft.Extensions.Logging;
namespace DMS.Application.Services
{
public class AlarmService : IAlarmService
{
private readonly ILogger<AlarmService> _logger;
public AlarmService(ILogger<AlarmService> logger)
{
_logger = logger;
}
public event EventHandler<AlarmEventArgs> OnAlarmTriggered;
public bool CheckAlarm(VariableDto variable)
{
if (!variable.IsAlarmEnabled)
{
return false;
}
// 尝试将 DataValue 转换为 double
if (!double.TryParse(variable.DataValue, out double currentValue))
{
// 如果是布尔值,我们也应该处理
if (bool.TryParse(variable.DataValue, out bool boolValue))
{
// 布尔值变化报警需要更复杂的逻辑,通常在 VariableItemViewModel 中处理
// 因为需要检测从 false 到 true 或从 true 到 false 的变化
// 这里我们暂时不处理
return false;
}
_logger.LogWarning($"无法将变量 {variable.Name} 的值 '{variable.DataValue}' 转换为数字进行报警检查。");
return false;
}
bool isTriggered = false;
string message = "";
string alarmType = "";
double thresholdValue = 0;
// 检查上限报警
if (variable.AlarmMaxValue > 0 && currentValue > variable.AlarmMaxValue)
{
isTriggered = true;
message = $"变量 {variable.Name} 的值 {currentValue} 超过了上限 {variable.AlarmMaxValue}。";
alarmType = "High";
thresholdValue = variable.AlarmMaxValue;
}
// 检查下限报警
else if (variable.AlarmMinValue > 0 && currentValue < variable.AlarmMinValue)
{
isTriggered = true;
message = $"变量 {variable.Name} 的值 {currentValue} 低于了下限 {variable.AlarmMinValue}。";
alarmType = "Low";
thresholdValue = variable.AlarmMinValue;
}
// 检查死区报警
// 注意:这里的实现假设我们有一个方法可以获取变量的上一次值
// 在实际应用中,这可能需要在 VariableItemViewModel 或其他地方维护
// 为了简化,我们假设有一个 PreviousValue 属性(但这在 DTO 中不存在)
// 我们将在 VariableItemViewModel 中处理这个逻辑,并通过事件触发
// 如果需要在 AlarmService 中处理死区报警,我们需要一种方式来获取上一次的值
// 这可能需要修改 VariableDto 或通过其他方式传递上一次的值
// 为了保持设计的清晰性,我们暂时不在这里实现死区报警
// 死区报警可以在 VariableItemViewModel 中实现,当检测到值变化超过死区时触发一个事件
if (isTriggered)
{
_logger.LogInformation(message);
OnAlarmTriggered?.Invoke(this, new AlarmEventArgs(
variable.Id, variable.Name, currentValue, thresholdValue, message, alarmType));
}
return isTriggered;
}
}
}

View File

@@ -0,0 +1,39 @@
using DMS.Application.Interfaces;
using DMS.Application.Models;
using Microsoft.Extensions.Logging;
namespace DMS.Application.Services.Processors
{
public class AlarmProcessor : IVariableProcessor
{
private readonly IAlarmService _alarmService;
private readonly ILogger<AlarmProcessor> _logger;
public AlarmProcessor(IAlarmService alarmService, ILogger<AlarmProcessor> logger)
{
_alarmService = alarmService;
_logger = logger;
}
public async Task ProcessAsync(VariableContext context)
{
try
{
// 检查是否触发报警
bool isAlarmTriggered = _alarmService.CheckAlarm(context.Data);
if (isAlarmTriggered)
{
_logger.LogInformation($"变量 {context.Data.Name} 触发了报警。");
// 报警逻辑已经通过事件处理,这里可以添加其他处理逻辑(如记录到数据库)
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"处理变量 {context.Data.Name} 的报警时发生错误: {ex.Message}");
}
// 不设置 context.IsHandled = true让其他处理器继续处理
}
}
}

View File

@@ -0,0 +1,30 @@
namespace DMS.Core.Enums
{
public enum AlarmRule
{
/// <summary>
/// 超过上限报警
/// </summary>
AboveMax,
/// <summary>
/// 低于下限报警
/// </summary>
BelowMin,
/// <summary>
/// 超出范围报警(同时检查上限和下限)
/// </summary>
OutOfRange,
/// <summary>
/// 死区报警
/// </summary>
Deadband,
/// <summary>
/// 布尔值变化报警
/// </summary>
BooleanChange
}
}

View File

@@ -0,0 +1,27 @@
using System;
namespace DMS.Core.Events
{
public class AlarmEventArgs : EventArgs
{
public int VariableId { get; }
public string VariableName { get; }
public double CurrentValue { get; }
public double ThresholdValue { get; }
public string Message { get; }
public DateTime Timestamp { get; }
public string AlarmType { get; } // 可以是 "High", "Low", "Change" 等
public AlarmEventArgs(int variableId, string variableName, double currentValue,
double thresholdValue, string message, string alarmType)
{
VariableId = variableId;
VariableName = variableName;
CurrentValue = currentValue;
ThresholdValue = thresholdValue;
Message = message;
Timestamp = DateTime.Now;
AlarmType = alarmType;
}
}
}

View File

@@ -0,0 +1,16 @@
using DMS.Core.Models;
namespace DMS.Core.Interfaces.Repositories
{
/// <summary>
/// 报警历史记录仓库接口
/// </summary>
public interface IAlarmHistoryRepository : IBaseRepository<AlarmHistory>
{
// 可以添加特定于报警历史记录的查询方法
// 例如:
// Task<IEnumerable<AlarmHistory>> GetUnacknowledgedAlarmsAsync();
// Task<IEnumerable<AlarmHistory>> GetAlarmsByVariableIdAsync(int variableId);
// Task AcknowledgeAlarmAsync(int alarmId, string acknowledgedBy);
}
}

View File

@@ -0,0 +1,71 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace DMS.Core.Models
{
/// <summary>
/// 报警历史记录实体
/// </summary>
public class AlarmHistory
{
/// <summary>
/// 主键ID
/// </summary>
[Key]
public int Id { get; set; }
/// <summary>
/// 变量ID
/// </summary>
public int VariableId { get; set; }
/// <summary>
/// 变量名称
/// </summary>
[MaxLength(100)]
public string VariableName { get; set; }
/// <summary>
/// 当前值
/// </summary>
public double CurrentValue { get; set; }
/// <summary>
/// 阈值
/// </summary>
public double ThresholdValue { get; set; }
/// <summary>
/// 报警类型 (High, Low, Deadband, BooleanChange)
/// </summary>
[MaxLength(50)]
public string AlarmType { get; set; }
/// <summary>
/// 报警消息
/// </summary>
public string Message { get; set; }
/// <summary>
/// 报警触发时间
/// </summary>
public DateTime Timestamp { get; set; }
/// <summary>
/// 是否已确认
/// </summary>
public bool IsAcknowledged { get; set; }
/// <summary>
/// 确认时间
/// </summary>
public DateTime? AcknowledgedAt { get; set; }
/// <summary>
/// 确认人
/// </summary>
[MaxLength(100)]
public string AcknowledgedBy { get; set; }
}
}

View File

@@ -0,0 +1,27 @@
using AutoMapper;
using DMS.Core.Interfaces.Repositories;
using DMS.Core.Models;
using DMS.Infrastructure.Data;
using Microsoft.Extensions.Logging;
namespace DMS.Infrastructure.Repositories
{
/// <summary>
/// 报警历史记录仓库实现
/// </summary>
public class AlarmHistoryRepository : BaseRepository<AlarmHistory>, IAlarmHistoryRepository
{
public AlarmHistoryRepository(SqlSugarDbContext dbContext,ILogger<AlarmHistoryRepository> logger) : base(dbContext,logger)
{
}
// 可以添加特定于报警历史记录的查询方法的实现
// 例如:
// public async Task<IEnumerable<AlarmHistory>> GetUnacknowledgedAlarmsAsync()
// {
// return await _db.Queryable<AlarmHistory>()
// .Where(a => !a.IsAcknowledged)
// .ToListAsync();
// }
}
}

View File

@@ -199,7 +199,7 @@ public abstract class BaseRepository<TEntity>
/// </summary> /// </summary>
/// <param name="number">要获取的实体数量。</param> /// <param name="number">要获取的实体数量。</param>
/// <returns>包含指定数量实体对象的列表。</returns> /// <returns>包含指定数量实体对象的列表。</returns>
protected async Task<List<TEntity>> TakeAsync(int number) public virtual async Task<List<TEntity>> TakeAsync(int number)
{ {
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
stopwatch.Start(); stopwatch.Start();
@@ -210,6 +210,23 @@ public abstract class BaseRepository<TEntity>
return entity; return entity;
} }
/// <summary>
/// 异步根据主键 ID 删除单个实体。
/// </summary>
/// <param name="id">要删除的实体的主键 ID。</param>
/// <returns>返回受影响的行数。</returns>
public virtual async Task<int> DeleteByIdAsync(int id)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
var result = await Db.Deleteable<TEntity>()
.In(id)
.ExecuteCommandAsync();
stopwatch.Stop();
_logger.LogInformation($"DeleteById {typeof(TEntity).Name}, ID: {id}, 耗时:{stopwatch.ElapsedMilliseconds}ms");
return result;
}
public async Task<bool> AddBatchAsync(List<TEntity> entities) public async Task<bool> AddBatchAsync(List<TEntity> entities)
{ {
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();

View File

@@ -71,12 +71,19 @@ public partial class App : System.Windows.Application
dataProcessingService.AddProcessor(Host.Services.GetRequiredService<HistoryProcessor>()); dataProcessingService.AddProcessor(Host.Services.GetRequiredService<HistoryProcessor>());
dataProcessingService.AddProcessor(Host.Services.GetRequiredService<MqttPublishProcessor>()); dataProcessingService.AddProcessor(Host.Services.GetRequiredService<MqttPublishProcessor>());
dataProcessingService.AddProcessor(Host.Services.GetRequiredService<UpdateDbVariableProcessor>()); dataProcessingService.AddProcessor(Host.Services.GetRequiredService<UpdateDbVariableProcessor>());
// 添加报警处理器
dataProcessingService.AddProcessor(Host.Services.GetRequiredService<DMS.Application.Services.Processors.AlarmProcessor>());
} }
catch (Exception exception) catch (Exception exception)
{ {
var notificationService = Host.Services.GetRequiredService<INotificationService>(); var notificationService = Host.Services.GetRequiredService<INotificationService>();
notificationService.ShowError($"加载数据时发生错误,如果是连接字符串不正确,可以在设置界面更改:{exception.Message}", exception); notificationService.ShowError($"加载数据时发生错误,如果是连接字符串不正确,可以在设置界面更改:{exception.Message}", exception);
} }
// 订阅报警事件
var alarmService = Host.Services.GetRequiredService<DMS.Application.Interfaces.IAlarmService>();
var alarmEventHandler = Host.Services.GetRequiredService<DMS.Application.EventHandlers.AlarmEventHandler>();
alarmService.OnAlarmTriggered += alarmEventHandler.HandleAlarm;
var splashWindow = Host.Services.GetRequiredService<SplashWindow>(); var splashWindow = Host.Services.GetRequiredService<SplashWindow>();
splashWindow.Show(); splashWindow.Show();
@@ -184,6 +191,10 @@ public partial class App : System.Windows.Application
services.AddSingleton<UpdateDbVariableProcessor>(); services.AddSingleton<UpdateDbVariableProcessor>();
services.AddSingleton<HistoryProcessor>(); services.AddSingleton<HistoryProcessor>();
services.AddSingleton<MqttPublishProcessor>(); services.AddSingleton<MqttPublishProcessor>();
// 注册报警服务和处理器
services.AddSingleton<DMS.Application.Interfaces.IAlarmService, DMS.Application.Services.AlarmService>();
services.AddSingleton<DMS.Application.Services.Processors.AlarmProcessor>();
services.AddSingleton<DMS.Application.EventHandlers.AlarmEventHandler>();
// 注册Core中的仓库 // 注册Core中的仓库
services.AddSingleton<AppSettings>(); services.AddSingleton<AppSettings>();
@@ -204,6 +215,7 @@ public partial class App : System.Windows.Application
services.AddSingleton<IUserRepository, UserRepository>(); services.AddSingleton<IUserRepository, UserRepository>();
services.AddSingleton<INlogRepository, NlogRepository>(); services.AddSingleton<INlogRepository, NlogRepository>();
services.AddSingleton<IRepositoryManager, RepositoryManager>(); services.AddSingleton<IRepositoryManager, RepositoryManager>();
services.AddSingleton<IAlarmHistoryRepository, AlarmHistoryRepository>(); // 添加这行
services.AddSingleton<IExcelService, ExcelService>(); services.AddSingleton<IExcelService, ExcelService>();
services.AddTransient<IOpcUaService, OpcUaService>(); services.AddTransient<IOpcUaService, OpcUaService>();

View File

@@ -54,6 +54,11 @@ public partial class VariableItemViewModel : ObservableObject
[ObservableProperty] [ObservableProperty]
private string? _displayValue; private string? _displayValue;
/// <summary>
/// 上一次的数值,用于死区报警计算
/// </summary>
private double _previousNumericValue;
partial void OnDataValueChanged(string? oldValue, string? newValue) partial void OnDataValueChanged(string? oldValue, string? newValue)
{ {
// 当DataValue发生变化时更新NumericValue // 当DataValue发生变化时更新NumericValue
@@ -66,16 +71,22 @@ public partial class VariableItemViewModel : ObservableObject
// 尝试将字符串转换为数值 // 尝试将字符串转换为数值
if (double.TryParse(newValue, out double numericValue)) if (double.TryParse(newValue, out double numericValue))
{ {
// 更新上一次的数值
_previousNumericValue = NumericValue;
NumericValue = numericValue; NumericValue = numericValue;
} }
// 如果是布尔值 // 如果是布尔值
else if (bool.TryParse(newValue, out bool boolValue)) else if (bool.TryParse(newValue, out bool boolValue))
{ {
// 更新上一次的数值
_previousNumericValue = NumericValue;
NumericValue = boolValue ? 1.0 : 0.0; NumericValue = boolValue ? 1.0 : 0.0;
} }
// 如果无法转换保持为0.0 // 如果无法转换保持为0.0
else else
{ {
// 更新上一次的数值
_previousNumericValue = NumericValue;
NumericValue = 0.0; NumericValue = 0.0;
} }
} }
@@ -224,4 +235,45 @@ public partial class VariableItemViewModel : ObservableObject
private OpcUaUpdateType _opcUaUpdateType; private OpcUaUpdateType _opcUaUpdateType;
/// <summary>
/// 上一次的布尔值,用于布尔值变化报警计算
/// </summary>
private bool? _previousBoolValue = null;
/// <summary>
/// 检查死区报警和布尔值变化报警
/// </summary>
public void CheckAdvancedAlarms(VariableDto variable)
{
// 检查死区报警
if (variable.IsAlarmEnabled && variable.AlarmDeadband > 0)
{
double difference = Math.Abs(NumericValue - _previousNumericValue);
if (difference > variable.AlarmDeadband)
{
// 触发死区报警
// 这里可以触发一个事件或调用报警服务
// 为了简单起见,我们暂时只打印日志
Console.WriteLine($"变量 {variable.Name} 的值变化 {difference} 超过了死区 {variable.AlarmDeadband}。");
}
}
// 检查布尔值变化报警
if (variable.IsAlarmEnabled && variable.DataType == DataType.Bool)
{
if (bool.TryParse(DataValue, out bool currentBoolValue))
{
// 如果上一次的值不为空,并且当前值与上一次的值不同,则触发报警
if (_previousBoolValue.HasValue && _previousBoolValue.Value != currentBoolValue)
{
// 触发布尔值变化报警
// 这里可以触发一个事件或调用报警服务
// 为了简单起见,我们暂时只打印日志
Console.WriteLine($"变量 {variable.Name} 的布尔值从 {_previousBoolValue.Value} 变化为 {currentBoolValue}。");
}
// 更新上一次的布尔值
_previousBoolValue = currentBoolValue;
}
}
}
} }