Files
DMS/DMS.Application/Services/Triggers/Impl/TriggerEvaluationService.cs
David P.G cb984b1b91 1 feat: 重构触发器定义模型并完善触发器-变量关联功能
2
    3 - 重命名TriggerDefinition类为Trigger,移除冗余的Definition后缀
    4 - 更新所有相关接口和实现类以使用新的Trigger类名
    5 - 在Variable模型中添加Triggers集合,实现变量到触发器的反向关联
    6 - 在TriggerVariableManagementService中完善触发器与变量的双向关联逻辑
    7 - 将触发器-变量关联数据加载集成到DataLoaderService中
    8 - 在依赖注入容器中注册ITriggerVariableManagementService和ITriggerVariableAppService
    9 - 更新触发器相关的DTO映射配置
   10 - 修复触发器评估服务中的类型引用问题
   11
   12 此更改统一了触发器模型命名,完善了触发器与变量的双向关联关系,提高了代码的一致性和可维护性。
2025-10-18 18:23:14 +08:00

170 lines
8.1 KiB
C#
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.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DMS.Application.DTOs;
using DMS.Application.Interfaces.Management;
using DMS.Application.Services.Management;
// 明确指定 Timer 类型,避免歧义
using ThreadingTimer = System.Threading.Timer;
using TimersTimer = System.Timers.Timer;
using DMS.Application.Services.Triggers;
using DMS.Core.Models.Triggers;
using Microsoft.Extensions.Logging; // 使用 Microsoft.Extensions.Logging.ILogger
namespace DMS.Application.Services.Triggers.Impl
{
/// <summary>
/// 触发器评估服务实现
/// </summary>
public class TriggerEvaluationService : ITriggerEvaluationService, IDisposable
{
private readonly ITriggerManagementService _triggerManagementService;
// 移除了 IVariableAppService 依赖
private readonly ITriggerActionExecutor _actionExecutor;
private readonly ILogger<TriggerEvaluationService> _logger; // 使用标准日志接口
// 为每个触发器存储抑制定时器
private readonly ConcurrentDictionary<int, ThreadingTimer> _suppressionTimers = new();
public TriggerEvaluationService(
ITriggerManagementService triggerManagementService,
// IVariableAppService variableAppService, // 移除此参数
ITriggerActionExecutor actionExecutor,
ILogger<TriggerEvaluationService> logger) // 使用标准日志接口
{
_triggerManagementService = triggerManagementService ?? throw new ArgumentNullException(nameof(triggerManagementService));
// _variableAppService = variableAppService ?? throw new ArgumentNullException(nameof(variableAppService));
_actionExecutor = actionExecutor ?? throw new ArgumentNullException(nameof(actionExecutor));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// 评估与指定变量关联的所有激活状态的触发器
/// </summary>
public async Task EvaluateTriggersAsync(int variableId, object currentValue)
{
try
{
var triggers = await _triggerManagementService.GetTriggersForVariableAsync(variableId);
// 注意:这里不再通过 _variableAppService 获取 Variable
// 如果需要 Variable 信息,可以在 ExecuteActionAsync 的 TriggerContext 中携带。
// 创建一个临时的上下文对象,其中 Variable 可以为 null
// 在实际应用中,你可能需要通过某种方式获取 Variable。
_logger.LogDebug($"Evaluating {triggers.Count(t => t.IsActive)} active triggers for variable ID: {variableId}");
foreach (var trigger in triggers.Where(t => t.IsActive))
{
if (!IsWithinSuppressionWindow(trigger)) // Check suppression first
{
if (EvaluateCondition(trigger, currentValue))
{
var context = new TriggerContext(trigger, currentValue, null);
await _actionExecutor.ExecuteActionAsync(context);
// Update last triggered time and start suppression timer if needed
trigger.LastTriggeredAt = DateTime.UtcNow;
// For simplicity, we'll assume it's updated periodically or on next load.
// In a production scenario, you'd likely want to persist this back to the database.
// Start suppression timer if duration is set (in-memory suppression)
if (trigger.SuppressionDuration.HasValue)
{
// 使用 ThreadingTimer 避免歧义
var timer = new ThreadingTimer(_ =>
{
trigger.LastTriggeredAt = null; // Reset suppression flag after delay
_logger.LogInformation($"Suppression lifted for trigger {trigger.Id}");
// Note: Modifying 'trigger' directly affects the object in the list returned by GetTriggersForVariableAsync().
// This works for in-memory state but won't persist changes. Consider updating DB explicitly if needed.
}, null, trigger.SuppressionDuration.Value, Timeout.InfiniteTimeSpan); // Single shot timer
// Replace any existing timer for this trigger ID
_suppressionTimers.AddOrUpdate(trigger.Id, timer, (key, oldTimer) => {
oldTimer?.Dispose();
return timer;
});
}
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while evaluating triggers for variable ID: {VariableId}", variableId);
}
}
/// <summary>
/// 内部方法:评估单个触发器的条件
/// </summary>
private bool EvaluateCondition(Trigger trigger, object currentValueObj)
{
if (currentValueObj == null)
{
_logger.LogWarning("Cannot evaluate trigger condition: Current value is null for trigger ID: {TriggerId}", trigger.Id);
return false; // Cannot evaluate null
}
// Attempt conversion from object to double - adjust parsing logic as needed for your data types
if (!double.TryParse(currentValueObj.ToString(), out double currentValue))
{
_logger.LogWarning("Could not parse current value '{CurrentValue}' to double for trigger evaluation (trigger ID: {TriggerId}).", currentValueObj, trigger.Id);
return false;
}
bool result = trigger.Condition switch
{
ConditionType.GreaterThan => currentValue > trigger.Threshold,
ConditionType.LessThan => currentValue < trigger.Threshold,
ConditionType.EqualTo => Math.Abs(currentValue - trigger.Threshold.GetValueOrDefault()) < double.Epsilon,
ConditionType.NotEqualTo => Math.Abs(currentValue - trigger.Threshold.GetValueOrDefault()) >= double.Epsilon,
ConditionType.InRange => currentValue >= trigger.LowerBound && currentValue <= trigger.UpperBound,
ConditionType.OutOfRange => currentValue < trigger.LowerBound || currentValue > trigger.UpperBound,
_ => false
};
if(result)
{
_logger.LogInformation("Trigger condition met: Variable value {CurrentValue} satisfies {Condition} for trigger ID: {TriggerId}",
currentValue, trigger.Condition, trigger.Id);
}
return result;
}
/// <summary>
/// 内部方法:检查触发器是否处于抑制窗口期内
/// </summary>
private bool IsWithinSuppressionWindow(Trigger trigger)
{
if (!trigger.SuppressionDuration.HasValue || !trigger.LastTriggeredAt.HasValue)
return false;
var suppressionEndTime = trigger.LastTriggeredAt.Value.Add(trigger.SuppressionDuration.Value);
bool isSuppressed = DateTime.UtcNow < suppressionEndTime;
if(isSuppressed)
{
_logger.LogTrace("Trigger is suppressed (until {SuppressionEnd}) for trigger ID: {TriggerId}", suppressionEndTime, trigger.Id);
}
return isSuppressed;
}
/// <summary>
/// 实现 IDisposable 以清理计时器资源
/// </summary>
public void Dispose()
{
foreach (var kvp in _suppressionTimers)
{
kvp.Value?.Dispose();
}
_suppressionTimers.Clear();
}
}
}