保存
This commit is contained in:
431
OrpaonVision.SiteApp/Runtime/Controllers/RuntimeController.cs
Normal file
431
OrpaonVision.SiteApp/Runtime/Controllers/RuntimeController.cs
Normal file
@@ -0,0 +1,431 @@
|
||||
#if false
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OrpaonVision.Core.Common;
|
||||
using OrpaonVision.Core.RuleEngine;
|
||||
using OrpaonVision.Core.AlarmSystem;
|
||||
using OrpaonVision.Core.ManualOverride;
|
||||
using OrpaonVision.SiteApp.Runtime.Services;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Runtime.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 运行时API控制器,提供Agent-2负责的所有服务接口。
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public sealed class RuntimeController : ControllerBase
|
||||
{
|
||||
private readonly IRuleEngineService _ruleEngineService;
|
||||
private readonly IRuntimeStateMachineService _stateMachineService;
|
||||
private readonly IManualOverrideService _manualOverrideService;
|
||||
private readonly IAlarmSystemService _alarmSystemService;
|
||||
private readonly ILogger<RuntimeController> _logger;
|
||||
|
||||
public RuntimeController(
|
||||
IRuleEngineService ruleEngineService,
|
||||
IRuntimeStateMachineService stateMachineService,
|
||||
IManualOverrideService manualOverrideService,
|
||||
IAlarmSystemService alarmSystemService,
|
||||
ILogger<RuntimeController> logger)
|
||||
{
|
||||
_ruleEngineService = ruleEngineService;
|
||||
_stateMachineService = stateMachineService;
|
||||
_manualOverrideService = manualOverrideService;
|
||||
_alarmSystemService = alarmSystemService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
#region 规则引擎接口
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有规则。
|
||||
/// </summary>
|
||||
[HttpGet("rules")]
|
||||
public async Task<ActionResult<IReadOnlyList<Rule>>> GetRules(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await _ruleEngineService.GetRulesAsync(cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估规则。
|
||||
/// </summary>
|
||||
[HttpPost("rules/evaluate")]
|
||||
public async Task<ActionResult<RuleEvaluationResult>> EvaluateRules([FromBody] RuleEvaluationRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var result = await _ruleEngineService.EvaluateRulesAsync(request, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取规则详情。
|
||||
/// </summary>
|
||||
[HttpGet("rules/{ruleId}")]
|
||||
public async Task<ActionResult<Rule>> GetRule(string ruleId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!Guid.TryParse(ruleId, out var guid))
|
||||
{
|
||||
return BadRequest("无效的规则ID格式");
|
||||
}
|
||||
|
||||
var result = await _ruleEngineService.GetRuleAsync(guid, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return NotFound(new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 状态机接口
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前状态。
|
||||
/// </summary>
|
||||
[HttpGet("state/current")]
|
||||
public ActionResult<object> GetCurrentState()
|
||||
{
|
||||
var state = _stateMachineService.GetCurrentState();
|
||||
var layer = _stateMachineService.GetCurrentLayer();
|
||||
|
||||
return Ok(new { state = state.ToString(), layer });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发状态转换。
|
||||
/// </summary>
|
||||
[HttpPost("state/trigger")]
|
||||
public async Task<ActionResult<StateTransitionEvent>> TriggerStateTransition([FromBody] StateTransitionRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var result = await _stateMachineService.TriggerTransitionAsync(request.Trigger, request.Reason, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取状态转换历史。
|
||||
/// </summary>
|
||||
[HttpGet("state/history")]
|
||||
public async Task<ActionResult<IReadOnlyList<StateTransitionEvent>>> GetStateHistory([FromQuery] int maxCount = 100, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await _stateMachineService.GetEventHistoryAsync(maxCount, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以执行操作。
|
||||
/// </summary>
|
||||
[HttpGet("state/can-execute/{trigger}")]
|
||||
public ActionResult<bool> CanExecuteOperation(string trigger)
|
||||
{
|
||||
if (!Enum.TryParse<StateTrigger>(trigger, true, out var triggerEnum))
|
||||
{
|
||||
return BadRequest("无效的触发器");
|
||||
}
|
||||
|
||||
var canExecute = _stateMachineService.CanExecuteOperation(triggerEnum);
|
||||
return Ok(canExecute);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 人工干预接口
|
||||
|
||||
/// <summary>
|
||||
/// 获取可干预的会话。
|
||||
/// </summary>
|
||||
[HttpGet("override/sessions")]
|
||||
public async Task<ActionResult<IReadOnlyList<OverrideableSession>>> GetOverrideableSessions(
|
||||
[FromQuery] DateTime startTime,
|
||||
[FromQuery] DateTime endTime,
|
||||
[FromQuery] string? productTypeCode = null,
|
||||
[FromQuery] OverrideStatus? overrideStatus = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await _manualOverrideService.GetOverrideableSessionsAsync(startTime, endTime, productTypeCode, overrideStatus, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取干预权限。
|
||||
/// </summary>
|
||||
[HttpGet("override/permission/{sessionId}")]
|
||||
public async Task<ActionResult<OverridePermission>> GetOverridePermission(string sessionId, [FromQuery] string operatorId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!Guid.TryParse(sessionId, out var sessionGuid))
|
||||
{
|
||||
return BadRequest("无效的会话ID格式");
|
||||
}
|
||||
|
||||
var result = await _manualOverrideService.GetOverridePermissionAsync(sessionGuid, operatorId, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行人工干预。
|
||||
/// </summary>
|
||||
[HttpPost("override/execute")]
|
||||
[Authorize(Policy = "Operator")]
|
||||
public async Task<ActionResult<ManualOverrideResult>> ExecuteManualOverride([FromBody] ManualOverrideRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var result = await _manualOverrideService.ExecuteManualOverrideAsync(request, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取干预历史。
|
||||
/// </summary>
|
||||
[HttpGet("override/history")]
|
||||
public async Task<ActionResult<IReadOnlyList<ManualOverrideHistory>>> GetOverrideHistory(
|
||||
[FromQuery] DateTime startTime,
|
||||
[FromQuery] DateTime endTime,
|
||||
[FromQuery] string? operatorId = null,
|
||||
[FromQuery] OverrideType? overrideType = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await _manualOverrideService.GetOverrideHistoryAsync(startTime, endTime, operatorId, overrideType, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 报警系统接口
|
||||
|
||||
/// <summary>
|
||||
/// 触发报警。
|
||||
/// </summary>
|
||||
[HttpPost("alarms/trigger")]
|
||||
public async Task<ActionResult<AlarmResult>> TriggerAlarm([FromBody] AlarmRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return BadRequest(ModelState);
|
||||
}
|
||||
|
||||
var result = await _alarmSystemService.TriggerAlarmAsync(request, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确认报警。
|
||||
/// </summary>
|
||||
[HttpPost("alarms/{alarmId}/confirm")]
|
||||
[Authorize(Policy = "Operator")]
|
||||
public async Task<ActionResult<AlarmConfirmResult>> ConfirmAlarm(string alarmId, [FromBody] AlarmConfirmRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!Guid.TryParse(alarmId, out var alarmGuid))
|
||||
{
|
||||
return BadRequest("无效的报警ID格式");
|
||||
}
|
||||
|
||||
request.AlarmId = alarmGuid;
|
||||
|
||||
var result = await _alarmSystemService.ConfirmAlarmAsync(request, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除报警。
|
||||
/// </summary>
|
||||
[HttpPost("alarms/{alarmId}/clear")]
|
||||
[Authorize(Policy = "Operator")]
|
||||
public async Task<ActionResult<AlarmClearResult>> ClearAlarm(string alarmId, [FromBody] AlarmClearRequest request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!Guid.TryParse(alarmId, out var alarmGuid))
|
||||
{
|
||||
return BadRequest("无效的报警ID格式");
|
||||
}
|
||||
|
||||
request.AlarmId = alarmGuid;
|
||||
|
||||
var result = await _alarmSystemService.ClearAlarmAsync(request, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取活跃报警。
|
||||
/// </summary>
|
||||
[HttpGet("alarms/active")]
|
||||
public async Task<ActionResult<IReadOnlyList<Alarm>>> GetActiveAlarms(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = await _alarmSystemService.GetActiveAlarmsAsync(cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取报警栈。
|
||||
/// </summary>
|
||||
[HttpGet("alarms/stack/{stackType}")]
|
||||
public async Task<ActionResult<IReadOnlyList<Alarm>>> GetAlarmStack(string stackType, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!Enum.TryParse<AlarmStackType>(stackType, true, out var stackTypeEnum))
|
||||
{
|
||||
return BadRequest("无效的报警栈类型");
|
||||
}
|
||||
|
||||
var result = await _alarmSystemService.GetAlarmStackAsync(stackTypeEnum, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取报警生命周期。
|
||||
/// </summary>
|
||||
[HttpGet("alarms/{alarmId}/lifecycle")]
|
||||
public async Task<ActionResult<AlarmLifecycle>> GetAlarmLifecycle(string alarmId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!Guid.TryParse(alarmId, out var alarmGuid))
|
||||
{
|
||||
return BadRequest("无效的报警ID格式");
|
||||
}
|
||||
|
||||
var result = await _alarmSystemService.GetAlarmLifecycleAsync(alarmGuid, cancellationToken);
|
||||
|
||||
if (!result.IsSuccess)
|
||||
{
|
||||
return StatusCode(500, new { error = result.Message, traceId = result.TraceId });
|
||||
}
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统状态接口
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统整体状态。
|
||||
/// </summary>
|
||||
[HttpGet("status")]
|
||||
public ActionResult<object> GetSystemStatus()
|
||||
{
|
||||
var currentState = _stateMachineService.GetCurrentState();
|
||||
var currentLayer = _stateMachineService.GetCurrentLayer();
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
timestamp = DateTime.UtcNow,
|
||||
state_machine = new
|
||||
{
|
||||
current_state = currentState.ToString(),
|
||||
current_layer = currentLayer
|
||||
},
|
||||
services = new
|
||||
{
|
||||
rule_engine = "healthy",
|
||||
state_machine = "healthy",
|
||||
manual_override = "healthy",
|
||||
alarm_system = "healthy"
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态转换请求模型。
|
||||
/// </summary>
|
||||
public sealed class StateTransitionRequest
|
||||
{
|
||||
public StateTrigger Trigger { get; set; }
|
||||
public string Reason { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,140 @@
|
||||
#if false
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using OrpaonVision.SiteApp.Runtime.Services;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Runtime.HealthChecks;
|
||||
|
||||
/// <summary>
|
||||
/// 报警系统健康检查。
|
||||
/// </summary>
|
||||
public sealed class AlarmSystemHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly IAlarmSystemService _alarmSystemService;
|
||||
private readonly ILogger<AlarmSystemHealthCheck> _logger;
|
||||
|
||||
public AlarmSystemHealthCheck(IAlarmSystemService alarmSystemService, ILogger<AlarmSystemHealthCheck> logger)
|
||||
{
|
||||
_alarmSystemService = alarmSystemService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查活跃报警数量
|
||||
var activeAlarmsResult = await _alarmSystemService.GetActiveAlarmsAsync(cancellationToken);
|
||||
|
||||
if (!activeAlarmsResult.IsSuccess)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"报警系统服务不可用",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["error"] = activeAlarmsResult.Message,
|
||||
["trace_id"] = activeAlarmsResult.TraceId ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
var activeAlarmCount = activeAlarmsResult.Data?.Count ?? 0;
|
||||
|
||||
// 检查报警栈状态
|
||||
var criticalStackResult = await _alarmSystemService.GetAlarmStackAsync(Core.AlarmSystem.AlarmStackType.Critical, cancellationToken);
|
||||
var highStackResult = await _alarmSystemService.GetAlarmStackAsync(Core.AlarmSystem.AlarmStackType.High, cancellationToken);
|
||||
|
||||
var criticalCount = criticalStackResult.IsSuccess ? criticalStackResult.Data?.Count ?? 0 : 0;
|
||||
var highCount = highStackResult.IsSuccess ? highStackResult.Data?.Count ?? 0 : 0;
|
||||
|
||||
// 测试报警触发功能
|
||||
var testAlarmId = Guid.NewGuid();
|
||||
var testResult = await _alarmSystemService.TriggerAlarmAsync(new Core.AlarmSystem.AlarmRequest
|
||||
{
|
||||
RequestId = Guid.NewGuid(),
|
||||
AlarmType = Core.AlarmSystem.AlarmType.SystemTest,
|
||||
AlarmLevel = Core.AlarmSystem.AlarmLevel.Info,
|
||||
Title = "健康检查测试报警",
|
||||
Description = "用于健康检查的测试报警",
|
||||
SessionId = Guid.NewGuid(),
|
||||
AutoClear = true,
|
||||
AutoClearAfterSeconds = 10
|
||||
}, cancellationToken);
|
||||
|
||||
var testSuccess = testResult.IsSuccess;
|
||||
var testAlarmIdResult = testResult.Data?.AlarmId ?? Guid.Empty;
|
||||
|
||||
// 如果测试成功,立即清除测试报警
|
||||
if (testSuccess && testAlarmIdResult != Guid.Empty)
|
||||
{
|
||||
await _alarmSystemService.ClearAlarmAsync(new Core.AlarmSystem.AlarmClearRequest
|
||||
{
|
||||
AlarmId = testAlarmIdResult,
|
||||
ClearUser = "HealthCheck",
|
||||
ClearReason = "健康检查测试报警清除"
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
// 判断健康状态
|
||||
var data = new Dictionary<string, object>
|
||||
{
|
||||
["active_alarm_count"] = activeAlarmCount,
|
||||
["critical_stack_count"] = criticalCount,
|
||||
["high_stack_count"] = highCount,
|
||||
["test_alarm_success"] = testSuccess,
|
||||
["test_alarm_trigger_time_ms"] = testResult.Data?.TriggerElapsedMs ?? 0,
|
||||
["status"] = "healthy"
|
||||
};
|
||||
|
||||
// 检查是否有过多活跃报警
|
||||
if (activeAlarmCount > 100)
|
||||
{
|
||||
return HealthCheckResult.Degraded(
|
||||
"活跃报警数量过多",
|
||||
new Dictionary<string, object>(data)
|
||||
{
|
||||
["status"] = "too_many_alarms"
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否有严重报警
|
||||
if (criticalCount > 0)
|
||||
{
|
||||
return HealthCheckResult.Degraded(
|
||||
"存在严重报警",
|
||||
new Dictionary<string, object>(data)
|
||||
{
|
||||
["status"] = "critical_alarms"
|
||||
});
|
||||
}
|
||||
|
||||
// 检查测试报警是否成功
|
||||
if (!testSuccess)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"报警触发测试失败",
|
||||
new Dictionary<string, object>(data)
|
||||
{
|
||||
["status"] = "test_failed",
|
||||
["error"] = testResult.Message
|
||||
});
|
||||
}
|
||||
|
||||
_logger.LogDebug("报警系统健康检查通过:活跃报警={ActiveCount},严重报警={CriticalCount},高级报警={HighCount},测试耗时={ElapsedMs}ms",
|
||||
activeAlarmCount, criticalCount, highCount, testResult.Data?.TriggerElapsedMs ?? 0);
|
||||
|
||||
return HealthCheckResult.Healthy("报警系统运行正常", data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "报警系统健康检查异常");
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"报警系统健康检查异常",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["error"] = ex.Message,
|
||||
["exception_type"] = ex.GetType().Name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,113 @@
|
||||
#if false
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using OrpaonVision.SiteApp.Runtime.Services;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Runtime.HealthChecks;
|
||||
|
||||
/// <summary>
|
||||
/// 规则引擎健康检查。
|
||||
/// </summary>
|
||||
public sealed class RuleEngineHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly IRuleEngineService _ruleEngineService;
|
||||
private readonly ILogger<RuleEngineHealthCheck> _logger;
|
||||
|
||||
public RuleEngineHealthCheck(IRuleEngineService ruleEngineService, ILogger<RuleEngineHealthCheck> logger)
|
||||
{
|
||||
_ruleEngineService = ruleEngineService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查规则引擎服务是否可用
|
||||
var rules = await _ruleEngineService.GetRulesAsync(cancellationToken);
|
||||
|
||||
if (!rules.IsSuccess)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"规则引擎服务不可用",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["error"] = rules.Message,
|
||||
["trace_id"] = rules.TraceId ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否有规则配置
|
||||
var ruleCount = rules.Data?.Count ?? 0;
|
||||
if (ruleCount == 0)
|
||||
{
|
||||
return HealthCheckResult.Degraded(
|
||||
"规则引擎可用但无规则配置",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["rule_count"] = ruleCount,
|
||||
["status"] = "no_rules"
|
||||
});
|
||||
}
|
||||
|
||||
// 执行简单的规则评估测试
|
||||
var testResult = await _ruleEngineService.EvaluateRulesAsync(new Core.RuleEngine.RuleEvaluationRequest
|
||||
{
|
||||
SessionId = Guid.NewGuid(),
|
||||
Inference = new Core.RuleEngine.InferenceResultDto
|
||||
{
|
||||
SessionId = Guid.NewGuid(),
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Detections = new List<Core.RuleEngine.InferenceDetectionDto>
|
||||
{
|
||||
new()
|
||||
{
|
||||
ClassName = "test",
|
||||
Confidence = 0.9f,
|
||||
CenterX = 100,
|
||||
CenterY = 100,
|
||||
Width = 50,
|
||||
Height = 50
|
||||
}
|
||||
}
|
||||
}
|
||||
}, cancellationToken);
|
||||
|
||||
if (!testResult.IsSuccess)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"规则引擎评估测试失败",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["error"] = testResult.Message,
|
||||
["trace_id"] = testResult.TraceId ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
var data = new Dictionary<string, object>
|
||||
{
|
||||
["rule_count"] = ruleCount,
|
||||
["test_evaluation_time_ms"] = testResult.Data?.EvaluationElapsedMs ?? 0,
|
||||
["test_result_pass"] = testResult.Data?.OverallResult == Core.RuleEngine.RuleResult.Pass,
|
||||
["status"] = "healthy"
|
||||
};
|
||||
|
||||
_logger.LogDebug("规则引擎健康检查通过:规则数量={RuleCount},测试评估耗时={ElapsedMs}ms",
|
||||
ruleCount, testResult.Data?.EvaluationElapsedMs ?? 0);
|
||||
|
||||
return HealthCheckResult.Healthy("规则引擎运行正常", data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "规则引擎健康检查异常");
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"规则引擎健康检查异常",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["error"] = ex.Message,
|
||||
["exception_type"] = ex.GetType().Name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,104 @@
|
||||
#if false
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using OrpaonVision.SiteApp.Runtime.Services;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Runtime.HealthChecks;
|
||||
|
||||
/// <summary>
|
||||
/// 状态机健康检查。
|
||||
/// </summary>
|
||||
public sealed class StateMachineHealthCheck : IHealthCheck
|
||||
{
|
||||
private readonly IRuntimeStateMachineService _stateMachineService;
|
||||
private readonly ILogger<StateMachineHealthCheck> _logger;
|
||||
|
||||
public StateMachineHealthCheck(IRuntimeStateMachineService stateMachineService, ILogger<StateMachineHealthCheck> logger)
|
||||
{
|
||||
_stateMachineService = stateMachineService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<HealthCheckResult> CheckHealthAsync(HealthCheckContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查状态机当前状态
|
||||
var currentState = _stateMachineService.GetCurrentState();
|
||||
var currentLayer = _stateMachineService.GetCurrentLayer();
|
||||
|
||||
// 检查状态机是否响应
|
||||
var canInitialize = _stateMachineService.CanExecuteOperation(Core.Common.StateTrigger.Initialize);
|
||||
var canStart = _stateMachineService.CanExecuteOperation(Core.Common.StateTrigger.Start);
|
||||
|
||||
// 获取事件历史
|
||||
var historyResult = await _stateMachineService.GetEventHistoryAsync(10, cancellationToken);
|
||||
|
||||
if (!historyResult.IsSuccess)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"状态机事件历史获取失败",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["error"] = historyResult.Message,
|
||||
["trace_id"] = historyResult.TraceId ?? string.Empty
|
||||
});
|
||||
}
|
||||
|
||||
var eventCount = historyResult.Data?.Count ?? 0;
|
||||
var lastEventTime = eventCount > 0 ? historyResult.Data?.FirstOrDefault()?.TimestampUtc : (DateTime?)null;
|
||||
|
||||
// 判断健康状态
|
||||
var data = new Dictionary<string, object>
|
||||
{
|
||||
["current_state"] = currentState.ToString(),
|
||||
["current_layer"] = currentLayer,
|
||||
["can_initialize"] = canInitialize,
|
||||
["can_start"] = canStart,
|
||||
["event_count"] = eventCount,
|
||||
["last_event_time"] = lastEventTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "无事件",
|
||||
["status"] = "healthy"
|
||||
};
|
||||
|
||||
// 检查是否处于异常状态
|
||||
if (currentState == Core.Common.RuntimeState.Error || currentState == Core.Common.RuntimeState.Faulted)
|
||||
{
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"状态机处于异常状态",
|
||||
new Dictionary<string, object>(data)
|
||||
{
|
||||
["status"] = "error_state"
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否长时间无活动(超过5分钟)
|
||||
if (lastEventTime.HasValue && DateTime.UtcNow.Subtract(lastEventTime.Value).TotalMinutes > 5)
|
||||
{
|
||||
return HealthCheckResult.Degraded(
|
||||
"状态机长时间无活动",
|
||||
new Dictionary<string, object>(data)
|
||||
{
|
||||
["status"] = "inactive",
|
||||
["inactive_minutes"] = DateTime.UtcNow.Subtract(lastEventTime.Value).TotalMinutes
|
||||
});
|
||||
}
|
||||
|
||||
_logger.LogDebug("状态机健康检查通过:当前状态={CurrentState},当前层级={CurrentLayer},事件数量={EventCount}",
|
||||
currentState, currentLayer, eventCount);
|
||||
|
||||
return HealthCheckResult.Healthy("状态机运行正常", data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "状态机健康检查异常");
|
||||
return HealthCheckResult.Unhealthy(
|
||||
"状态机健康检查异常",
|
||||
new Dictionary<string, object>
|
||||
{
|
||||
["error"] = ex.Message,
|
||||
["exception_type"] = ex.GetType().Name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
134
OrpaonVision.SiteApp/Runtime/Metrics/RuleEngineMetrics.cs
Normal file
134
OrpaonVision.SiteApp/Runtime/Metrics/RuleEngineMetrics.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
#if false
|
||||
using System.Diagnostics.Metrics;
|
||||
using OrpaonVision.Core.Results;
|
||||
using OrpaonVision.Core.RuleEngine;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Runtime.Metrics;
|
||||
|
||||
/// <summary>
|
||||
/// 规则引擎性能指标收集器。
|
||||
/// </summary>
|
||||
public interface IRuleEngineMetrics
|
||||
{
|
||||
void RecordRuleEvaluation(string ruleType, TimeSpan elapsed, RuleResult result);
|
||||
void RecordRuleLoad(int ruleCount, TimeSpan elapsed);
|
||||
void RecordRuleValidation(bool isValid, string ruleType);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规则引擎性能指标收集器实现。
|
||||
/// </summary>
|
||||
public sealed class RuleEngineMetrics : IRuleEngineMetrics
|
||||
{
|
||||
private readonly Counter<int> _ruleEvaluationCounter;
|
||||
private readonly Histogram<double> _ruleEvaluationDuration;
|
||||
private readonly Counter<int> _ruleLoadCounter;
|
||||
private readonly Histogram<double> _ruleLoadDuration;
|
||||
private readonly Counter<int> _ruleValidationCounter;
|
||||
private readonly ILogger<RuleEngineMetrics> _logger;
|
||||
|
||||
public RuleEngineMetrics(IMeterFactory meterFactory, ILogger<RuleEngineMetrics> logger)
|
||||
{
|
||||
var meter = meterFactory.Create("OrpaonVision.RuleEngine");
|
||||
|
||||
_ruleEvaluationCounter = meter.CreateCounter<int>(
|
||||
"orpaonvision_rule_engine_evaluations_total",
|
||||
description: "规则评估总次数");
|
||||
|
||||
_ruleEvaluationDuration = meter.CreateHistogram<double>(
|
||||
"orpaonvision_rule_engine_evaluation_duration_seconds",
|
||||
description: "规则评估耗时(秒)");
|
||||
|
||||
_ruleLoadCounter = meter.CreateCounter<int>(
|
||||
"orpaonvision_rule_engine_loads_total",
|
||||
description: "规则加载总次数");
|
||||
|
||||
_ruleLoadDuration = meter.CreateHistogram<double>(
|
||||
"orpaonvision_rule_engine_load_duration_seconds",
|
||||
description: "规则加载耗时(秒)");
|
||||
|
||||
_ruleValidationCounter = meter.CreateCounter<int>(
|
||||
"orpaonvision_rule_engine_validations_total",
|
||||
description: "规则验证总次数");
|
||||
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void RecordRuleEvaluation(string ruleType, TimeSpan elapsed, RuleResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
var elapsedSeconds = elapsed.TotalSeconds;
|
||||
|
||||
// 记录评估次数
|
||||
_ruleEvaluationCounter.Add(1, new KeyValuePair<string, object>[]
|
||||
{
|
||||
new("rule_type", ruleType),
|
||||
new("result", result.ToString()),
|
||||
new("success", result == RuleResult.Pass ? "true" : "false")
|
||||
});
|
||||
|
||||
// 记录评估耗时
|
||||
_ruleEvaluationDuration.Record(elapsedSeconds, new KeyValuePair<string, object>[]
|
||||
{
|
||||
new("rule_type", ruleType),
|
||||
new("result", result.ToString())
|
||||
});
|
||||
|
||||
_logger.LogDebug("记录规则评估指标:类型={RuleType},结果={Result},耗时={ElapsedMs}ms",
|
||||
ruleType, result, elapsed.TotalMilliseconds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "记录规则评估指标失败:类型={RuleType}", ruleType);
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordRuleLoad(int ruleCount, TimeSpan elapsed)
|
||||
{
|
||||
try
|
||||
{
|
||||
var elapsedSeconds = elapsed.TotalSeconds;
|
||||
|
||||
// 记录加载次数
|
||||
_ruleLoadCounter.Add(1, new KeyValuePair<string, object>[]
|
||||
{
|
||||
new("rule_count", ruleCount)
|
||||
});
|
||||
|
||||
// 记录加载耗时
|
||||
_ruleLoadDuration.Record(elapsedSeconds, new KeyValuePair<string, object>[]
|
||||
{
|
||||
new("rule_count", ruleCount)
|
||||
});
|
||||
|
||||
_logger.LogDebug("记录规则加载指标:数量={RuleCount},耗时={ElapsedMs}ms",
|
||||
ruleCount, elapsed.TotalMilliseconds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "记录规则加载指标失败:数量={RuleCount}", ruleCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordRuleValidation(bool isValid, string ruleType)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 记录验证次数
|
||||
_ruleValidationCounter.Add(1, new KeyValuePair<string, object>[]
|
||||
{
|
||||
new("rule_type", ruleType),
|
||||
new("valid", isValid ? "true" : "false")
|
||||
});
|
||||
|
||||
_logger.LogDebug("记录规则验证指标:类型={RuleType},有效={IsValid}", ruleType, isValid);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "记录规则验证指标失败:类型={RuleType}", ruleType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
140
OrpaonVision.SiteApp/Runtime/Metrics/StateMachineMetrics.cs
Normal file
140
OrpaonVision.SiteApp/Runtime/Metrics/StateMachineMetrics.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
#if false
|
||||
using System.Diagnostics.Metrics;
|
||||
using OrpaonVision.Core.Common;
|
||||
using OrpaonVision.Core.Results;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Runtime.Metrics;
|
||||
|
||||
/// <summary>
|
||||
/// 状态机性能指标收集器。
|
||||
/// </summary>
|
||||
public interface IStateMachineMetrics
|
||||
{
|
||||
void RecordStateTransition(RuntimeState fromState, RuntimeState toState, StateTrigger trigger, TimeSpan elapsed);
|
||||
void RecordStateGuardCheck(StateTrigger trigger, RuntimeState currentState, bool allowed);
|
||||
void RecordManualIntervention(string interventionType, TimeSpan elapsed, bool success);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 状态机性能指标收集器实现。
|
||||
/// </summary>
|
||||
public sealed class StateMachineMetrics : IStateMachineMetrics
|
||||
{
|
||||
private readonly Counter<int> _stateTransitionCounter;
|
||||
private readonly Histogram<double> _stateTransitionDuration;
|
||||
private readonly Counter<int> _stateGuardCheckCounter;
|
||||
private readonly Counter<int> _manualInterventionCounter;
|
||||
private readonly Histogram<double> _manualInterventionDuration;
|
||||
private readonly ILogger<StateMachineMetrics> _logger;
|
||||
|
||||
public StateMachineMetrics(IMeterFactory meterFactory, ILogger<StateMachineMetrics> logger)
|
||||
{
|
||||
var meter = meterFactory.Create("OrpaonVision.StateMachine");
|
||||
|
||||
_stateTransitionCounter = meter.CreateCounter<int>(
|
||||
"orpaonvision_state_machine_transitions_total",
|
||||
description: "状态转换总次数");
|
||||
|
||||
_stateTransitionDuration = meter.CreateHistogram<double>(
|
||||
"orpaonvision_state_machine_transition_duration_seconds",
|
||||
description: "状态转换耗时(秒)");
|
||||
|
||||
_stateGuardCheckCounter = meter.CreateCounter<int>(
|
||||
"orpaonvision_state_machine_guard_checks_total",
|
||||
description: "状态守卫检查总次数");
|
||||
|
||||
_manualInterventionCounter = meter.CreateCounter<int>(
|
||||
"orpaonvision_state_machine_manual_interventions_total",
|
||||
description: "人工干预总次数");
|
||||
|
||||
_manualInterventionDuration = meter.CreateHistogram<double>(
|
||||
"orpaonvision_state_machine_manual_intervention_duration_seconds",
|
||||
description: "人工干预耗时(秒)");
|
||||
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public void RecordStateTransition(RuntimeState fromState, RuntimeState toState, StateTrigger trigger, TimeSpan elapsed)
|
||||
{
|
||||
try
|
||||
{
|
||||
var elapsedSeconds = elapsed.TotalSeconds;
|
||||
|
||||
// 记录状态转换次数
|
||||
_stateTransitionCounter.Add(1, new KeyValuePair<string, object>[]
|
||||
{
|
||||
new("from_state", fromState.ToString()),
|
||||
new("to_state", toState.ToString()),
|
||||
new("trigger", trigger.ToString())
|
||||
});
|
||||
|
||||
// 记录状态转换耗时
|
||||
_stateTransitionDuration.Record(elapsedSeconds, new KeyValuePair<string, object>[]
|
||||
{
|
||||
new("from_state", fromState.ToString()),
|
||||
new("to_state", toState.ToString()),
|
||||
new("trigger", trigger.ToString())
|
||||
});
|
||||
|
||||
_logger.LogDebug("记录状态转换指标:{FromState} -> {ToState},触发器={Trigger},耗时={ElapsedMs}ms",
|
||||
fromState, toState, trigger, elapsed.TotalMilliseconds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "记录状态转换指标失败:{FromState} -> {ToState}", fromState, toState);
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordStateGuardCheck(StateTrigger trigger, RuntimeState currentState, bool allowed)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 记录守卫检查次数
|
||||
_stateGuardCheckCounter.Add(1, new KeyValuePair<string, object>[]
|
||||
{
|
||||
new("trigger", trigger.ToString()),
|
||||
new("current_state", currentState.ToString()),
|
||||
new("allowed", allowed ? "true" : "false")
|
||||
});
|
||||
|
||||
_logger.LogDebug("记录状态守卫检查指标:触发器={Trigger},当前状态={CurrentState},允许={Allowed}",
|
||||
trigger, currentState, allowed);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "记录状态守卫检查指标失败:触发器={Trigger},当前状态={CurrentState}",
|
||||
trigger, currentState);
|
||||
}
|
||||
}
|
||||
|
||||
public void RecordManualIntervention(string interventionType, TimeSpan elapsed, bool success)
|
||||
{
|
||||
try
|
||||
{
|
||||
var elapsedSeconds = elapsed.TotalSeconds;
|
||||
|
||||
// 记录人工干预次数
|
||||
_manualInterventionCounter.Add(1, new KeyValuePair<string, object>[]
|
||||
{
|
||||
new("intervention_type", interventionType),
|
||||
new("success", success ? "true" : "false")
|
||||
});
|
||||
|
||||
// 记录人工干预耗时
|
||||
_manualInterventionDuration.Record(elapsedSeconds, new KeyValuePair<string, object>[]
|
||||
{
|
||||
new("intervention_type", interventionType),
|
||||
new("success", success ? "true" : "false")
|
||||
});
|
||||
|
||||
_logger.LogDebug("记录人工干预指标:类型={InterventionType},成功={Success},耗时={ElapsedMs}ms",
|
||||
interventionType, success, elapsed.TotalMilliseconds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "记录人工干预指标失败:类型={InterventionType}", interventionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -888,3 +888,895 @@ internal enum ConditionLogic
|
||||
}
|
||||
|
||||
#endregion
|
||||
#if false
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OrpaonVision.Core.Results;
|
||||
using OrpaonVision.SiteApp.Runtime.Contracts;
|
||||
using OrpaonVision.SiteApp.Runtime.Options;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 高级规则引擎服务实现。
|
||||
/// 支持复杂规则配置、多条件判断、动态规则加载等功能。
|
||||
/// </summary>
|
||||
public sealed class AdvancedRuleEngineService : IRuleEngineService, IDisposable
|
||||
{
|
||||
private readonly ILogger<AdvancedRuleEngineService> _logger;
|
||||
private readonly RuntimeOptions _options;
|
||||
private readonly Dictionary<string, RuleDefinition> _rules;
|
||||
private readonly object _lockObject = new();
|
||||
private bool _isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数。
|
||||
/// </summary>
|
||||
public AdvancedRuleEngineService(ILogger<AdvancedRuleEngineService> logger, IOptions<RuntimeOptions> options)
|
||||
{
|
||||
_logger = logger;
|
||||
_options = options.Value;
|
||||
_rules = new Dictionary<string, RuleDefinition>();
|
||||
|
||||
InitializeDefaultRules();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<RuntimeDecisionDto> Evaluate(int currentLayer, InferenceResultDto inference)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogDebug("执行规则引擎评估:当前层={CurrentLayer},推理结果={Label},置信度={Confidence:F3}",
|
||||
currentLayer, inference.Label, inference.Confidence);
|
||||
|
||||
if (!_isInitialized)
|
||||
{
|
||||
_logger.LogWarning("规则引擎未初始化,使用默认规则");
|
||||
return EvaluateWithDefaultRule(currentLayer, inference);
|
||||
}
|
||||
|
||||
// 获取当前层的规则
|
||||
var layerRules = GetRulesForLayer(currentLayer);
|
||||
if (layerRules.Count == 0)
|
||||
{
|
||||
_logger.LogWarning("第 {CurrentLayer} 层没有配置规则,使用默认规则", currentLayer);
|
||||
return EvaluateWithDefaultRule(currentLayer, inference);
|
||||
}
|
||||
|
||||
// 执行规则评估
|
||||
var evaluationContext = new RuleEvaluationContext
|
||||
{
|
||||
CurrentLayer = currentLayer,
|
||||
Inference = inference,
|
||||
Timestamp = DateTime.UtcNow
|
||||
};
|
||||
|
||||
var results = new List<RuleEvaluationResult>();
|
||||
|
||||
foreach (var rule in layerRules)
|
||||
{
|
||||
var result = EvaluateRule(rule, evaluationContext);
|
||||
results.Add(result);
|
||||
|
||||
_logger.LogDebug("规则 {RuleName} 评估结果:{IsPass} - {Message}",
|
||||
rule.Name, result.IsPass, result.Message);
|
||||
|
||||
// 如果是关键规则且失败,可以提前终止
|
||||
if (!result.IsPass && rule.IsCritical)
|
||||
{
|
||||
_logger.LogInformation("关键规则 {RuleName} 失败,提前终止评估", rule.Name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 汇总评估结果
|
||||
var finalDecision = AggregateEvaluationResults(results, evaluationContext);
|
||||
|
||||
_logger.LogInformation("规则引擎评估完成:{DecisionCode} - {DecisionMessage}",
|
||||
finalDecision.Code, finalDecision.Message);
|
||||
|
||||
return Result<RuntimeDecisionDto>.Success(finalDecision, message: "规则判定完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "规则引擎评估失败。TraceId: {TraceId}", traceId);
|
||||
var result = Result.FromException(ex, "RULE_ENGINE_EVALUATION_FAILED", "规则引擎评估失败", traceId);
|
||||
return Result<RuntimeDecisionDto>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化默认规则。
|
||||
/// </summary>
|
||||
private void InitializeDefaultRules()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("正在初始化默认规则...");
|
||||
|
||||
// 添加基础缺陷检测规则
|
||||
var defectRule = new RuleDefinition
|
||||
{
|
||||
Name = "DefectDetection",
|
||||
Description = "缺陷检测规则",
|
||||
Layer = 0, // 适用于所有层
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = ConditionType.LabelEquals,
|
||||
Parameter = "defect",
|
||||
Operator = "equals",
|
||||
ExpectedValue = "defect"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = ConditionType.ConfidenceThreshold,
|
||||
Parameter = "confidence",
|
||||
Operator = ">=",
|
||||
ExpectedValue = _options.NgConfidenceThreshold.ToString("F3")
|
||||
}
|
||||
},
|
||||
Action = new RuleAction
|
||||
{
|
||||
Type = ActionType.MarkAsNG,
|
||||
Parameters = new Dictionary<string, object>
|
||||
{
|
||||
["reason"] = "检测到缺陷",
|
||||
["severity"] = "high"
|
||||
}
|
||||
},
|
||||
IsCritical = true,
|
||||
Priority = 1,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
// 添加置信度规则
|
||||
var confidenceRule = new RuleDefinition
|
||||
{
|
||||
Name = "ConfidenceCheck",
|
||||
Description = "置信度检查规则",
|
||||
Layer = 0, // 适用于所有层
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = ConditionType.ConfidenceThreshold,
|
||||
Parameter = "confidence",
|
||||
Operator = "<",
|
||||
ExpectedValue = _options.NgConfidenceThreshold.ToString("F3")
|
||||
}
|
||||
},
|
||||
Action = new RuleAction
|
||||
{
|
||||
Type = ActionType.MarkAsNG,
|
||||
Parameters = new Dictionary<string, object>
|
||||
{
|
||||
["reason"] = "置信度过低",
|
||||
["severity"] = "medium"
|
||||
}
|
||||
},
|
||||
IsCritical = false,
|
||||
Priority = 2,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
// 添加正常通过规则
|
||||
var passRule = new RuleDefinition
|
||||
{
|
||||
Name = "NormalPass",
|
||||
Description = "正常通过规则",
|
||||
Layer = 0, // 适用于所有层
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = ConditionType.LabelEquals,
|
||||
Parameter = "normal",
|
||||
Operator = "equals",
|
||||
ExpectedValue = "normal"
|
||||
},
|
||||
new()
|
||||
{
|
||||
Type = ConditionType.ConfidenceThreshold,
|
||||
Parameter = "confidence",
|
||||
Operator = ">=",
|
||||
ExpectedValue = _options.NgConfidenceThreshold.ToString("F3")
|
||||
}
|
||||
},
|
||||
Action = new RuleAction
|
||||
{
|
||||
Type = ActionType.MarkAsOK,
|
||||
Parameters = new Dictionary<string, object>
|
||||
{
|
||||
["reason"] = "正常检测"
|
||||
}
|
||||
},
|
||||
IsCritical = false,
|
||||
Priority = 3,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
// 添加数量检查规则
|
||||
var quantityRule = new RuleDefinition
|
||||
{
|
||||
Name = "QuantityCheck",
|
||||
Description = "部件数量检查规则",
|
||||
Layer = 0, // 适用于所有层
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = ConditionType.QuantityCheck,
|
||||
Parameter = "",
|
||||
Operator = "equals",
|
||||
ExpectedValue = "3-5" // 期望3-5个部件
|
||||
}
|
||||
},
|
||||
Action = new RuleAction
|
||||
{
|
||||
Type = ActionType.MarkAsNG,
|
||||
Parameters = new Dictionary<string, object>
|
||||
{
|
||||
["reason"] = "部件数量不符合要求",
|
||||
["severity"] = "medium"
|
||||
}
|
||||
},
|
||||
IsCritical = false,
|
||||
Priority = 4,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
// 添加位置检查规则
|
||||
var positionRule = new RuleDefinition
|
||||
{
|
||||
Name = "PositionCheck",
|
||||
Description = "部件位置检查规则",
|
||||
Layer = 0,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = ConditionType.PositionCheck,
|
||||
Parameter = "screw", // 检查螺丝位置
|
||||
Operator = "within",
|
||||
ExpectedValue = "100,100,50" // 圆形区域:中心(100,100),半径50
|
||||
}
|
||||
},
|
||||
Action = new RuleAction
|
||||
{
|
||||
Type = ActionType.MarkAsNG,
|
||||
Parameters = new Dictionary<string, object>
|
||||
{
|
||||
["reason"] = "部件位置偏离",
|
||||
["severity"] = "high"
|
||||
}
|
||||
},
|
||||
IsCritical = true,
|
||||
Priority = 5,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
// 添加到位检查规则
|
||||
var placementRule = new RuleDefinition
|
||||
{
|
||||
Name = "PlacementCheck",
|
||||
Description = "部件到位检查规则",
|
||||
Layer = 0,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = ConditionType.PlacementCheck,
|
||||
Parameter = "",
|
||||
Operator = "equals",
|
||||
ExpectedValue = "bracket=2" // 期望2个支架
|
||||
}
|
||||
},
|
||||
Action = new RuleAction
|
||||
{
|
||||
Type = ActionType.MarkAsNG,
|
||||
Parameters = new Dictionary<string, object>
|
||||
{
|
||||
["reason"] = "部件未完全到位",
|
||||
["severity"] = "medium"
|
||||
}
|
||||
},
|
||||
IsCritical = false,
|
||||
Priority = 6,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
// 添加禁装检查规则
|
||||
var forbiddenRule = new RuleDefinition
|
||||
{
|
||||
Name = "ForbiddenCheck",
|
||||
Description = "禁装部件检查规则",
|
||||
Layer = 0,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = ConditionType.ForbiddenCheck,
|
||||
Parameter = "",
|
||||
Operator = "not_contains",
|
||||
ExpectedValue = "wrong_part,temp_part" // 禁止错误的部件和临时部件
|
||||
}
|
||||
},
|
||||
Action = new RuleAction
|
||||
{
|
||||
Type = ActionType.MarkAsNG,
|
||||
Parameters = new Dictionary<string, object>
|
||||
{
|
||||
["reason"] = "发现禁装部件",
|
||||
["severity"] = "high"
|
||||
}
|
||||
},
|
||||
IsCritical = true,
|
||||
Priority = 7,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
// 添加顺序检查规则
|
||||
var sequenceRule = new RuleDefinition
|
||||
{
|
||||
Name = "SequenceCheck",
|
||||
Description = "装配顺序检查规则",
|
||||
Layer = 0,
|
||||
Conditions = new List<RuleCondition>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Type = ConditionType.SequenceCheck,
|
||||
Parameter = "",
|
||||
Operator = "equals",
|
||||
ExpectedValue = "base>bracket>screw>cover" // 期望装配顺序
|
||||
}
|
||||
},
|
||||
Action = new RuleAction
|
||||
{
|
||||
Type = ActionType.MarkAsNG,
|
||||
Parameters = new Dictionary<string, object>
|
||||
{
|
||||
["reason"] = "装配顺序错误",
|
||||
["severity"] = "high"
|
||||
}
|
||||
},
|
||||
IsCritical = true,
|
||||
Priority = 8,
|
||||
Enabled = true
|
||||
};
|
||||
|
||||
_rules["DefectDetection"] = defectRule;
|
||||
_rules["ConfidenceCheck"] = confidenceRule;
|
||||
_rules["NormalPass"] = passRule;
|
||||
_rules["QuantityCheck"] = quantityRule;
|
||||
_rules["PositionCheck"] = positionRule;
|
||||
_rules["PlacementCheck"] = placementRule;
|
||||
_rules["ForbiddenCheck"] = forbiddenRule;
|
||||
_rules["SequenceCheck"] = sequenceRule;
|
||||
|
||||
_isInitialized = true;
|
||||
_logger.LogInformation("默认规则初始化完成,共加载 {Count} 条规则", _rules.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "初始化默认规则失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定层的规则。
|
||||
/// </summary>
|
||||
private List<RuleDefinition> GetRulesForLayer(int layer)
|
||||
{
|
||||
return _rules.Values
|
||||
.Where(rule => rule.Layer == 0 || rule.Layer == layer)
|
||||
.Where(rule => rule.Enabled)
|
||||
.OrderBy(rule => rule.Priority)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估单个规则。
|
||||
/// </summary>
|
||||
private RuleEvaluationResult EvaluateRule(RuleDefinition rule, RuleEvaluationContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var conditionResults = new List<bool>();
|
||||
|
||||
foreach (var condition in rule.Conditions)
|
||||
{
|
||||
var result = EvaluateCondition(condition, context);
|
||||
conditionResults.Add(result);
|
||||
|
||||
// 如果是AND逻辑且有一个条件失败,可以提前退出
|
||||
if (!result && rule.ConditionLogic == ConditionLogic.And)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 根据逻辑运算符确定最终结果
|
||||
var isPass = rule.ConditionLogic == ConditionLogic.And
|
||||
? conditionResults.All(r => r)
|
||||
: conditionResults.Any(r => r);
|
||||
|
||||
var message = isPass
|
||||
? $"规则 {rule.Name} 通过"
|
||||
: $"规则 {rule.Name} 失败: {string.Join(", ", rule.Conditions.Select(c => c.Type))}";
|
||||
|
||||
return new RuleEvaluationResult
|
||||
{
|
||||
RuleName = rule.Name,
|
||||
IsPass = isPass,
|
||||
Message = message,
|
||||
Action = isPass ? null : rule.Action,
|
||||
ExecutionTime = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "评估规则 {RuleName} 失败", rule.Name);
|
||||
return new RuleEvaluationResult
|
||||
{
|
||||
RuleName = rule.Name,
|
||||
IsPass = false,
|
||||
Message = $"规则评估异常: {ex.Message}",
|
||||
Action = new RuleAction
|
||||
{
|
||||
Type = ActionType.MarkAsNG,
|
||||
Parameters = new Dictionary<string, object>
|
||||
{
|
||||
["reason"] = "规则评估异常",
|
||||
["severity"] = "high"
|
||||
}
|
||||
},
|
||||
ExecutionTime = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估条件。
|
||||
/// </summary>
|
||||
private bool EvaluateCondition(RuleCondition condition, RuleEvaluationContext context)
|
||||
{
|
||||
return condition.Type switch
|
||||
{
|
||||
ConditionType.LabelEquals => EvaluateLabelEquals(condition, context),
|
||||
ConditionType.ConfidenceThreshold => EvaluateConfidenceThreshold(condition, context),
|
||||
ConditionType.QuantityCheck => EvaluateQuantityCheck(condition, context),
|
||||
ConditionType.PositionCheck => EvaluatePositionCheck(condition, context),
|
||||
ConditionType.PlacementCheck => EvaluatePlacementCheck(condition, context),
|
||||
ConditionType.ForbiddenCheck => EvaluateForbiddenCheck(condition, context),
|
||||
ConditionType.SequenceCheck => EvaluateSequenceCheck(condition, context),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估标签等于条件。
|
||||
/// </summary>
|
||||
private bool EvaluateLabelEquals(RuleCondition condition, RuleEvaluationContext context)
|
||||
{
|
||||
var expectedLabel = condition.ExpectedValue;
|
||||
var actualLabel = context.Inference.Label;
|
||||
|
||||
return condition.Operator switch
|
||||
{
|
||||
"equals" => actualLabel == expectedLabel,
|
||||
"not_equals" => actualLabel != expectedLabel,
|
||||
"contains" => actualLabel.Contains(expectedLabel),
|
||||
"not_contains" => !actualLabel.Contains(expectedLabel),
|
||||
_ => actualLabel == expectedLabel
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估置信度阈值条件。
|
||||
/// </summary>
|
||||
private bool EvaluateConfidenceThreshold(RuleCondition condition, RuleEvaluationContext context)
|
||||
{
|
||||
if (!decimal.TryParse(condition.ExpectedValue, out var threshold))
|
||||
{
|
||||
_logger.LogWarning("置信度阈值解析失败: {Value}", condition.ExpectedValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
var actualConfidence = (decimal)context.Inference.Confidence;
|
||||
|
||||
return condition.Operator switch
|
||||
{
|
||||
">=" => actualConfidence >= threshold,
|
||||
"<=" => actualConfidence <= threshold,
|
||||
">" => actualConfidence > threshold,
|
||||
"<" => actualConfidence < threshold,
|
||||
"==" => actualConfidence == threshold,
|
||||
"!=" => actualConfidence != threshold,
|
||||
_ => actualConfidence >= threshold
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估数量检查条件。
|
||||
/// </summary>
|
||||
private bool EvaluateQuantityCheck(RuleCondition condition, RuleEvaluationContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 解析期望数量范围,格式如 "min-max" 或 "exact"
|
||||
var expectedRange = condition.ExpectedValue;
|
||||
var actualCount = context.Inference.Detections.Count;
|
||||
|
||||
_logger.LogDebug("数量检查:期望={ExpectedRange},实际={ActualCount}", expectedRange, actualCount);
|
||||
|
||||
if (expectedRange.Contains('-'))
|
||||
{
|
||||
// 范围检查,格式:min-max
|
||||
var parts = expectedRange.Split('-');
|
||||
if (parts.Length == 2 && int.TryParse(parts[0], out var min) && int.TryParse(parts[1], out var max))
|
||||
{
|
||||
return actualCount >= min && actualCount <= max;
|
||||
}
|
||||
}
|
||||
else if (int.TryParse(expectedRange, out var exact))
|
||||
{
|
||||
// 精确数量检查
|
||||
return actualCount == exact;
|
||||
}
|
||||
|
||||
_logger.LogWarning("数量检查条件解析失败: {Value}", expectedRange);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "数量检查评估失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估位置检查条件。
|
||||
/// </summary>
|
||||
private bool EvaluatePositionCheck(RuleCondition condition, RuleEvaluationContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 解析期望位置,格式如 "x_min:x_max,y_min:y_max" 或 "center_x,center_y,radius"
|
||||
var expectedPosition = condition.ExpectedValue;
|
||||
var detections = context.Inference.Detections.Where(d => d.ClassName == condition.Parameter || string.IsNullOrEmpty(condition.Parameter)).ToList();
|
||||
|
||||
if (!detections.Any())
|
||||
{
|
||||
_logger.LogDebug("位置检查:未找到匹配的检测对象");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否所有检测对象都在期望位置范围内
|
||||
foreach (var detection in detections)
|
||||
{
|
||||
var isInRange = CheckPositionInRange(detection.CenterX, detection.CenterY, expectedPosition);
|
||||
if (!isInRange)
|
||||
{
|
||||
_logger.LogDebug("位置检查失败:检测对象 {ClassName} 位置 ({X}, {Y}) 不在期望范围内 {Range}",
|
||||
detection.ClassName, detection.CenterX, detection.CenterY, expectedPosition);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug("位置检查通过:所有 {Count} 个检测对象都在期望范围内", detections.Count);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "位置检查评估失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估到位检查条件。
|
||||
/// </summary>
|
||||
private bool EvaluatePlacementCheck(RuleCondition condition, RuleEvaluationContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 解析期望的到位状态,格式:className=expected_count
|
||||
var placementRule = condition.ExpectedValue;
|
||||
var parts = placementRule.Split('=');
|
||||
|
||||
if (parts.Length != 2 || !int.TryParse(parts[1], out var expectedCount))
|
||||
{
|
||||
_logger.LogWarning("到位检查条件解析失败: {Value}", placementRule);
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetClass = parts[0];
|
||||
var actualCount = context.Inference.Detections.Count(d => d.ClassName == targetClass);
|
||||
|
||||
_logger.LogDebug("到位检查:{ClassName} 期望={ExpectedCount},实际={ActualCount}",
|
||||
targetClass, expectedCount, actualCount);
|
||||
|
||||
return actualCount >= expectedCount;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "到位检查评估失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估禁装检查条件。
|
||||
/// </summary>
|
||||
private bool EvaluateForbiddenCheck(RuleCondition condition, RuleEvaluationContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否存在禁用的部件
|
||||
var forbiddenClasses = condition.ExpectedValue.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
var detections = context.Inference.Detections;
|
||||
|
||||
foreach (var forbiddenClass in forbiddenClasses)
|
||||
{
|
||||
var forbiddenDetections = detections.Where(d => d.ClassName.Equals(forbiddenClass.Trim(), StringComparison.OrdinalIgnoreCase)).ToList();
|
||||
if (forbiddenDetections.Any())
|
||||
{
|
||||
_logger.LogDebug("禁装检查失败:发现禁用部件 {ClassName},数量={Count}", forbiddenClass, forbiddenDetections.Count);
|
||||
return false; // 发现禁用部件,检查失败
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogDebug("禁装检查通过:未发现禁用部件");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "禁装检查评估失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 评估顺序检查条件。
|
||||
/// </summary>
|
||||
private bool EvaluateSequenceCheck(RuleCondition condition, RuleEvaluationContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 解析期望顺序,格式如 "class1>class2>class3"
|
||||
var expectedSequence = condition.ExpectedValue;
|
||||
var sequenceParts = expectedSequence.Split('>', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// 按Y坐标排序检测对象(假设装配顺序是从上到下)
|
||||
var sortedDetections = context.Inference.Detections
|
||||
.Where(d => sequenceParts.Contains(d.ClassName))
|
||||
.OrderBy(d => d.CenterY)
|
||||
.Select(d => d.ClassName)
|
||||
.ToList();
|
||||
|
||||
var actualSequence = string.Join(">", sortedDetections);
|
||||
var isCorrectOrder = actualSequence.Equals(expectedSequence, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
_logger.LogDebug("顺序检查:期望={Expected},实际={Actual},结果={Result}",
|
||||
expectedSequence, actualSequence, isCorrectOrder ? "通过" : "失败");
|
||||
|
||||
return isCorrectOrder;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "顺序检查评估失败");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查位置是否在指定范围内。
|
||||
/// </summary>
|
||||
private static bool CheckPositionInRange(float centerX, float centerY, string rangeDefinition)
|
||||
{
|
||||
if (rangeDefinition.Contains(','))
|
||||
{
|
||||
// 圆形范围检查,格式:center_x,center_y,radius
|
||||
var parts = rangeDefinition.Split(',');
|
||||
if (parts.Length == 3 &&
|
||||
float.TryParse(parts[0], out var expectedCenterX) &&
|
||||
float.TryParse(parts[1], out var expectedCenterY) &&
|
||||
float.TryParse(parts[2], out var radius))
|
||||
{
|
||||
var distance = Math.Sqrt(Math.Pow(centerX - expectedCenterX, 2) + Math.Pow(centerY - expectedCenterY, 2));
|
||||
return distance <= radius;
|
||||
}
|
||||
}
|
||||
else if (rangeDefinition.Contains(':'))
|
||||
{
|
||||
// 矩形范围检查,格式:x_min:x_max,y_min:y_max
|
||||
var coordinates = rangeDefinition.Split(',');
|
||||
if (coordinates.Length == 2)
|
||||
{
|
||||
var xRange = coordinates[0].Split(':');
|
||||
var yRange = coordinates[1].Split(':');
|
||||
|
||||
if (xRange.Length == 2 && yRange.Length == 2 &&
|
||||
float.TryParse(xRange[0], out var xMin) &&
|
||||
float.TryParse(xRange[1], out var xMax) &&
|
||||
float.TryParse(yRange[0], out var yMin) &&
|
||||
float.TryParse(yRange[1], out var yMax))
|
||||
{
|
||||
return centerX >= xMin && centerX <= xMax && centerY >= yMin && centerY <= yMax;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 汇总评估结果。
|
||||
/// </summary>
|
||||
private RuntimeDecisionDto AggregateEvaluationResults(List<RuleEvaluationResult> results, RuleEvaluationContext context)
|
||||
{
|
||||
var failedRules = results.Where(r => !r.IsPass).ToList();
|
||||
var criticalFailures = failedRules.Where(r => r.Action?.Parameters?.ContainsKey("severity") == true &&
|
||||
r.Action.Parameters["severity"].ToString() == "high").ToList();
|
||||
|
||||
var isPass = failedRules.Count == 0;
|
||||
var decisionCode = isPass ? "RULE_PASS" : "RULE_NG";
|
||||
var decisionMessage = isPass
|
||||
? $"第 {context.CurrentLayer} 层所有规则通过"
|
||||
: $"第 {context.CurrentLayer} 层规则失败: {string.Join(", ", failedRules.Select(r => r.RuleName))}";
|
||||
|
||||
// 如果有关键失败,提升消息级别
|
||||
if (criticalFailures.Count > 0)
|
||||
{
|
||||
decisionCode = "RULE_CRITICAL_NG";
|
||||
decisionMessage = $"第 {context.CurrentLayer} 层关键规则失败: {string.Join(", ", criticalFailures.Select(r => r.RuleName))}";
|
||||
}
|
||||
|
||||
return new RuntimeDecisionDto
|
||||
{
|
||||
IsPass = isPass,
|
||||
Code = decisionCode,
|
||||
Message = decisionMessage
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用默认规则评估。
|
||||
/// </summary>
|
||||
private Result<RuntimeDecisionDto> EvaluateWithDefaultRule(int currentLayer, InferenceResultDto inference)
|
||||
{
|
||||
var isPass = inference.Label == "normal" || inference.Label == "OK" || inference.Confidence < _options.NgConfidenceThreshold;
|
||||
var decision = new RuntimeDecisionDto
|
||||
{
|
||||
IsPass = isPass,
|
||||
Code = isPass ? "DEFAULT_RULE_PASS" : "DEFAULT_RULE_NG",
|
||||
Message = isPass
|
||||
? $"第 {currentLayer} 层默认规则通过。"
|
||||
: $"第 {currentLayer} 层默认规则 NG:{inference.Label} (置信度: {inference.Confidence:F3})"
|
||||
};
|
||||
|
||||
return Result<RuntimeDecisionDto>.Success(decision, message: "默认规则判定完成");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源。
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
_rules.Clear();
|
||||
_isInitialized = false;
|
||||
_logger.LogInformation("规则引擎服务已释放");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "释放规则引擎服务资源时发生错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region 规则引擎内部类型
|
||||
|
||||
/// <summary>
|
||||
/// 规则定义。
|
||||
/// </summary>
|
||||
internal sealed class RuleDefinition
|
||||
{
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Description { get; init; } = string.Empty;
|
||||
public int Layer { get; init; }
|
||||
public List<RuleCondition> Conditions { get; init; } = new();
|
||||
public RuleAction Action { get; init; } = new();
|
||||
public bool IsCritical { get; init; }
|
||||
public int Priority { get; init; }
|
||||
public bool Enabled { get; init; }
|
||||
public ConditionLogic ConditionLogic { get; init; } = ConditionLogic.And;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规则条件。
|
||||
/// </summary>
|
||||
internal sealed class RuleCondition
|
||||
{
|
||||
public ConditionType Type { get; init; }
|
||||
public string Parameter { get; init; } = string.Empty;
|
||||
public string Operator { get; init; } = string.Empty;
|
||||
public string ExpectedValue { get; init; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规则动作。
|
||||
/// </summary>
|
||||
internal sealed class RuleAction
|
||||
{
|
||||
public ActionType Type { get; init; }
|
||||
public Dictionary<string, object> Parameters { get; init; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规则评估上下文。
|
||||
/// </summary>
|
||||
internal sealed class RuleEvaluationContext
|
||||
{
|
||||
public int CurrentLayer { get; init; }
|
||||
public InferenceResultDto Inference { get; init; } = new();
|
||||
public DateTime Timestamp { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规则评估结果。
|
||||
/// </summary>
|
||||
internal sealed class RuleEvaluationResult
|
||||
{
|
||||
public string RuleName { get; init; } = string.Empty;
|
||||
public bool IsPass { get; init; }
|
||||
public string Message { get; init; } = string.Empty;
|
||||
public RuleAction? Action { get; init; }
|
||||
public DateTime ExecutionTime { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 条件类型。
|
||||
/// </summary>
|
||||
internal enum ConditionType
|
||||
{
|
||||
LabelEquals,
|
||||
ConfidenceThreshold,
|
||||
QuantityCheck,
|
||||
PositionCheck,
|
||||
PlacementCheck,
|
||||
ForbiddenCheck,
|
||||
SequenceCheck
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动作类型。
|
||||
/// </summary>
|
||||
internal enum ActionType
|
||||
{
|
||||
MarkAsOK,
|
||||
MarkAsNG,
|
||||
LogWarning,
|
||||
Custom
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 条件逻辑。
|
||||
/// </summary>
|
||||
internal enum ConditionLogic
|
||||
{
|
||||
And,
|
||||
Or
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,6 @@
|
||||
|
||||
#if false
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OrpaonVision.Core.AlarmSystem;
|
||||
@@ -15,6 +18,8 @@ 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();
|
||||
@@ -22,10 +27,16 @@ public sealed class AlarmSystemService : IAlarmSystemService
|
||||
private readonly object _lock = new();
|
||||
private readonly Timer _autoClearTimer;
|
||||
|
||||
public AlarmSystemService(ILogger<AlarmSystemService> logger, IOptions<RuntimeOptions> options)
|
||||
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>())
|
||||
@@ -44,23 +55,28 @@ public sealed class AlarmSystemService : IAlarmSystemService
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("触发报警:类型={AlarmType},级别={AlarmLevel},标题={Title},会话ID={SessionId},层级={Layer}",
|
||||
alarmRequest.AlarmType, alarmRequest.AlarmLevel, alarmRequest.Title, alarmRequest.SessionId, alarmRequest.RelatedLayer);
|
||||
_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,
|
||||
RuleNumber = alarmRequest.ExtendedProperties?.ContainsKey("rule_number") == true
|
||||
? alarmRequest.ExtendedProperties["rule_number"].ToString()
|
||||
: string.Empty,
|
||||
Layer = alarmRequest.RelatedLayer > 0 ? alarmRequest.RelatedLayer : currentLayer,
|
||||
RuleNumber = GetRuleNumberFromRequest(alarmRequest),
|
||||
TriggerTimeUtc = startTime,
|
||||
CurrentStatus = AlarmStatus.Active
|
||||
CurrentStatus = AlarmStatus.Active,
|
||||
TriggerState = currentState.ToString(),
|
||||
TriggerLayer = currentLayer
|
||||
};
|
||||
_alarmLifecycles.TryAdd(alarmId, lifecycle);
|
||||
|
||||
@@ -74,7 +90,7 @@ public sealed class AlarmSystemService : IAlarmSystemService
|
||||
Description = alarmRequest.Description,
|
||||
ProductTypeCode = alarmRequest.ProductTypeCode,
|
||||
SessionId = alarmRequest.SessionId,
|
||||
RelatedLayer = alarmRequest.RelatedLayer,
|
||||
RelatedLayer = alarmRequest.RelatedLayer > 0 ? alarmRequest.RelatedLayer : currentLayer,
|
||||
RelatedPart = alarmRequest.RelatedPart,
|
||||
Source = alarmRequest.Source,
|
||||
AlarmData = alarmRequest.AlarmData,
|
||||
@@ -88,6 +104,13 @@ public sealed class AlarmSystemService : IAlarmSystemService
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
// 添加到活跃报警
|
||||
@@ -100,6 +123,12 @@ public sealed class AlarmSystemService : IAlarmSystemService
|
||||
// 更新栈位置
|
||||
UpdateStackPositions(stackType);
|
||||
|
||||
// 如果是严重报警,可能需要触发状态机转换
|
||||
if (alarmRequest.AlarmLevel >= AlarmLevel.High)
|
||||
{
|
||||
await HandleCriticalAlarmAsync(alarm, cancellationToken);
|
||||
}
|
||||
|
||||
var elapsedMs = (long)(DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||
|
||||
var result = new AlarmResult
|
||||
@@ -118,13 +147,15 @@ public sealed class AlarmSystemService : IAlarmSystemService
|
||||
ExtendedProperties = new Dictionary<string, object>
|
||||
{
|
||||
["session_id"] = alarmRequest.SessionId,
|
||||
["layer"] = alarmRequest.RelatedLayer,
|
||||
["rule_number"] = lifecycle.RuleNumber
|
||||
["layer"] = alarm.RelatedLayer,
|
||||
["rule_number"] = lifecycle.RuleNumber,
|
||||
["trigger_state"] = currentState.ToString(),
|
||||
["current_layer"] = currentLayer
|
||||
}
|
||||
};
|
||||
|
||||
_logger.LogInformation("报警触发成功:报警ID={AlarmId},会话ID={SessionId},层级={Layer},规则编号={RuleNumber}",
|
||||
alarmId, alarmRequest.SessionId, alarmRequest.RelatedLayer, lifecycle.RuleNumber);
|
||||
_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);
|
||||
}
|
||||
@@ -445,6 +476,167 @@ public sealed class AlarmSystemService : IAlarmSystemService
|
||||
|
||||
#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
|
||||
@@ -503,8 +695,6 @@ public sealed class AlarmSystemService : IAlarmSystemService
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 释放资源。
|
||||
/// </summary>
|
||||
@@ -563,6 +753,16 @@ public sealed class AlarmLifecycle
|
||||
/// </summary>
|
||||
public DateTime TriggerTimeUtc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 触发时的状态机状态。
|
||||
/// </summary>
|
||||
public string TriggerState { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 触发时的层级。
|
||||
/// </summary>
|
||||
public int TriggerLayer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 确认时间。
|
||||
/// </summary>
|
||||
@@ -597,12 +797,91 @@ public sealed class AlarmLifecycle
|
||||
/// 恢复状态。
|
||||
/// </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>
|
||||
@@ -1834,5 +2113,6 @@ public sealed class AlarmSystemService : IAlarmSystemService
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -47,7 +47,7 @@ public sealed class ConsistencyCheckService : IConsistencyCheckService
|
||||
result.Message = "相机未连接";
|
||||
result.Recommendations.Add("请先连接相机设备");
|
||||
_logger.LogWarning("相机未连接,跳过其他一致性检查");
|
||||
return Result.Success(result);
|
||||
return Result<ConsistencyCheckResult>.Success(result);
|
||||
}
|
||||
|
||||
// 检查YOLO初始化状态
|
||||
@@ -57,7 +57,7 @@ public sealed class ConsistencyCheckService : IConsistencyCheckService
|
||||
result.Message = "YOLO模型未初始化";
|
||||
result.Recommendations.Add("请先初始化YOLO模型");
|
||||
_logger.LogWarning("YOLO模型未初始化,跳过其他一致性检查");
|
||||
return Result.Success(result);
|
||||
return Result<ConsistencyCheckResult>.Success(result);
|
||||
}
|
||||
|
||||
// 获取相机配置信息
|
||||
@@ -67,7 +67,7 @@ public sealed class ConsistencyCheckService : IConsistencyCheckService
|
||||
result.Status = ConsistencyStatus.Error;
|
||||
result.Message = "无法获取相机设备信息";
|
||||
result.Recommendations.Add("检查相机连接状态");
|
||||
return Result.Success(result);
|
||||
return Result<ConsistencyCheckResult>.Success(result);
|
||||
}
|
||||
|
||||
// 获取YOLO模型信息
|
||||
@@ -120,7 +120,7 @@ public sealed class ConsistencyCheckService : IConsistencyCheckService
|
||||
}
|
||||
|
||||
_logger.LogInformation("一致性检查完成,状态: {Status}, 消息: {Message}", result.Status, result.Message);
|
||||
return Result.Success(result);
|
||||
return Result<ConsistencyCheckResult>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -182,7 +182,7 @@ public sealed class ConsistencyCheckService : IConsistencyCheckService
|
||||
healthResult.Summary = GenerateHealthSummary(healthResult);
|
||||
|
||||
_logger.LogDebug("健康检查完成,整体状态: {Status}", healthResult.OverallStatus);
|
||||
return Result.Success(healthResult);
|
||||
return Result<HealthCheckResult>.Success(healthResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -13,6 +13,26 @@ namespace OrpaonVision.SiteApp.Runtime.Services
|
||||
/// </summary>
|
||||
RuntimeStateSnapshotDto GetSnapshot();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前状态。
|
||||
/// </summary>
|
||||
RuntimeState GetCurrentState();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前层级。
|
||||
/// </summary>
|
||||
int GetCurrentLayer();
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以执行特定操作。
|
||||
/// </summary>
|
||||
bool CanExecuteOperation(StateTrigger trigger);
|
||||
|
||||
/// <summary>
|
||||
/// 基于触发器执行状态转换。
|
||||
/// </summary>
|
||||
Result<StateTransitionEvent> TriggerTransition(StateTrigger trigger, string? reason = null, object? parameters = null);
|
||||
|
||||
/// <summary>
|
||||
/// 尝试推进到下一层。
|
||||
/// </summary>
|
||||
|
||||
@@ -6,20 +6,31 @@ using OrpaonVision.Core.Common;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text.Json;
|
||||
using CoreAuditLevel = OrpaonVision.Core.ManualOverride.AuditLevel;
|
||||
using OrpaonVision.SiteApp.Runtime.Services;
|
||||
using RuntimeOptions = OrpaonVision.SiteApp.Runtime.Options.RuntimeOptions;
|
||||
|
||||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 人工复位放行服务最小实现。
|
||||
/// 人工复位放行服务实现,集成状态机和审计功能。
|
||||
/// </summary>
|
||||
public sealed class ManualOverrideService : IManualOverrideService
|
||||
{
|
||||
private readonly ILogger<ManualOverrideService> _logger;
|
||||
private readonly IRuntimeStateMachineService _stateMachineService;
|
||||
private readonly RuntimeOptions _options;
|
||||
private readonly ConcurrentDictionary<string, object> _overrideCache = new();
|
||||
private readonly object _lock = new();
|
||||
|
||||
public ManualOverrideService(ILogger<ManualOverrideService> logger, IOptions<OrpaonVision.SiteApp.Runtime.Options.RuntimeOptions> options)
|
||||
public ManualOverrideService(
|
||||
ILogger<ManualOverrideService> logger,
|
||||
IOptions<RuntimeOptions> options,
|
||||
IRuntimeStateMachineService stateMachineService)
|
||||
{
|
||||
_logger = logger;
|
||||
_ = options;
|
||||
_options = options.Value;
|
||||
_stateMachineService = stateMachineService;
|
||||
_logger.LogInformation("人工复位放行服务已初始化");
|
||||
}
|
||||
|
||||
public Task<Result<IReadOnlyList<OverrideableSession>>> GetOverrideableSessionsAsync(DateTime startTime, DateTime endTime, string? productTypeCode = null, OverrideStatus? overrideStatus = null, CancellationToken cancellationToken = default)
|
||||
@@ -30,17 +41,57 @@ public sealed class ManualOverrideService : IManualOverrideService
|
||||
|
||||
public Task<Result<OverridePermission>> GetOverridePermissionAsync(Guid sessionId, string operatorId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var permission = new OverridePermission
|
||||
try
|
||||
{
|
||||
PermissionId = Guid.NewGuid(),
|
||||
SessionId = sessionId,
|
||||
OperatorId = operatorId,
|
||||
OperatorName = operatorId,
|
||||
OperatorPermissionLevel = OrpaonVision.Core.ManualOverride.OperatorPermissionLevel.Administrator,
|
||||
HasPermission = true,
|
||||
PermissionReason = "最小实现:默认允许"
|
||||
};
|
||||
return Task.FromResult(Result<OverridePermission>.Success(permission));
|
||||
_logger.LogInformation("获取会话复位放行权限:会话ID={SessionId},操作员ID={OperatorId}", sessionId, operatorId);
|
||||
|
||||
// 检查当前状态机状态是否允许人工干预
|
||||
var currentState = _stateMachineService.GetCurrentState();
|
||||
var canIntervene = _stateMachineService.CanExecuteOperation(StateTrigger.ManualIntervention);
|
||||
|
||||
if (!canIntervene)
|
||||
{
|
||||
var permission = new OverridePermission
|
||||
{
|
||||
PermissionId = Guid.NewGuid(),
|
||||
SessionId = sessionId,
|
||||
OperatorId = operatorId,
|
||||
OperatorName = operatorId,
|
||||
OperatorPermissionLevel = OperatorPermissionLevel.Operator,
|
||||
HasPermission = false,
|
||||
PermissionReason = $"当前状态 {currentState} 不允许人工干预"
|
||||
};
|
||||
return Task.FromResult(Result<OverridePermission>.Success(permission));
|
||||
}
|
||||
|
||||
// 检查操作员权限级别
|
||||
var permissionLevel = GetOperatorPermissionLevel(operatorId);
|
||||
var hasPermission = CheckOverridePermission(sessionId, operatorId, permissionLevel);
|
||||
|
||||
var result = new OverridePermission
|
||||
{
|
||||
PermissionId = Guid.NewGuid(),
|
||||
SessionId = sessionId,
|
||||
OperatorId = operatorId,
|
||||
OperatorName = operatorId,
|
||||
OperatorPermissionLevel = permissionLevel,
|
||||
HasPermission = hasPermission,
|
||||
PermissionReason = hasPermission
|
||||
? $"权限检查通过,当前状态 {currentState} 允许干预"
|
||||
: $"权限不足,需要 {OperatorPermissionLevel.Administrator} 级别权限"
|
||||
};
|
||||
|
||||
_logger.LogInformation("复位放行权限获取完成:会话ID={SessionId},操作员ID={OperatorId},是否有权限={HasPermission},权限级别={PermissionLevel},当前状态={CurrentState}",
|
||||
sessionId, operatorId, result.HasPermission, permissionLevel, currentState);
|
||||
|
||||
return Task.FromResult(Result<OverridePermission>.Success(result));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "获取复位放行权限失败。TraceId: {TraceId}", traceId);
|
||||
return Task.FromResult(Result<OverridePermission>.FailWithTrace("GET_OVERRIDE_PERMISSION_FAILED", "获取复位放行权限失败", traceId));
|
||||
}
|
||||
}
|
||||
|
||||
public Task<Result<OverrideValidationResult>> ValidateOverrideConditionsAsync(Guid sessionId, ManualOverrideRequest overrideRequest, CancellationToken cancellationToken = default)
|
||||
@@ -59,22 +110,264 @@ public sealed class ManualOverrideService : IManualOverrideService
|
||||
return Task.FromResult(Result<OverrideValidationResult>.Success(result));
|
||||
}
|
||||
|
||||
public Task<Result<ManualOverrideResult>> ExecuteManualOverrideAsync(ManualOverrideRequest overrideRequest, CancellationToken cancellationToken = default)
|
||||
public async Task<Result<ManualOverrideResult>> ExecuteManualOverrideAsync(ManualOverrideRequest overrideRequest, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = new ManualOverrideResult
|
||||
try
|
||||
{
|
||||
OverrideId = Guid.NewGuid(),
|
||||
RequestId = overrideRequest.RequestId,
|
||||
SessionId = overrideRequest.SessionId,
|
||||
OverrideResult = OverrideResult.Success,
|
||||
OverrideStatus = OverrideStatus.Overridden,
|
||||
OverrideMessage = "最小实现:执行成功",
|
||||
ExecutionTimeUtc = DateTime.UtcNow,
|
||||
ExecutionElapsedMs = 0,
|
||||
Executor = overrideRequest.OperatorName,
|
||||
AffectedSessionStatus = overrideRequest.TargetStatus
|
||||
};
|
||||
return Task.FromResult(Result<ManualOverrideResult>.Success(result));
|
||||
_logger.LogInformation("执行人工复位放行:请求ID={RequestId},会话ID={SessionId},操作员ID={OperatorId},干预类型={OverrideType}",
|
||||
overrideRequest.RequestId, overrideRequest.SessionId, overrideRequest.OperatorId, overrideRequest.OverrideType);
|
||||
|
||||
var startTime = DateTime.UtcNow;
|
||||
|
||||
// 步骤1:权限校验
|
||||
var permissionResult = await GetOverridePermissionAsync(overrideRequest.SessionId, overrideRequest.OperatorId, cancellationToken);
|
||||
if (!permissionResult.IsSuccess)
|
||||
{
|
||||
return Result<ManualOverrideResult>.FailWithTrace(permissionResult.Code, permissionResult.Message, permissionResult.TraceId);
|
||||
}
|
||||
|
||||
if (!permissionResult.Data.HasPermission)
|
||||
{
|
||||
return Result<ManualOverrideResult>.Fail("PERMISSION_DENIED", permissionResult.Data.PermissionReason);
|
||||
}
|
||||
|
||||
// 步骤2:条件验证
|
||||
var validationResult = await ValidateOverrideConditionsAsync(overrideRequest.SessionId, overrideRequest, cancellationToken);
|
||||
if (!validationResult.IsSuccess)
|
||||
{
|
||||
return Result<ManualOverrideResult>.FailWithTrace(validationResult.Code, validationResult.Message, validationResult.TraceId);
|
||||
}
|
||||
|
||||
if (!validationResult.Data.IsValid)
|
||||
{
|
||||
return Result<ManualOverrideResult>.Fail("VALIDATION_FAILED", validationResult.Data.ValidationMessage);
|
||||
}
|
||||
|
||||
// 步骤3:状态迁移
|
||||
var stateTransitionResult = await ExecuteStateTransitionAsync(overrideRequest, cancellationToken);
|
||||
if (!stateTransitionResult.IsSuccess)
|
||||
{
|
||||
return Result<ManualOverrideResult>.FailWithTrace(stateTransitionResult.Code, stateTransitionResult.Message, stateTransitionResult.TraceId);
|
||||
}
|
||||
|
||||
// 步骤4:执行具体干预操作
|
||||
var overrideResult = await ExecuteOverrideOperationAsync(overrideRequest, cancellationToken);
|
||||
|
||||
// 步骤5:审计记录
|
||||
await LogOverrideAuditAsync(overrideRequest, overrideResult, startTime, cancellationToken);
|
||||
|
||||
var elapsed = DateTime.UtcNow - startTime;
|
||||
var result = new ManualOverrideResult
|
||||
{
|
||||
OverrideId = Guid.NewGuid(),
|
||||
RequestId = overrideRequest.RequestId,
|
||||
SessionId = overrideRequest.SessionId,
|
||||
OverrideResult = overrideResult,
|
||||
OverrideStatus = overrideResult == OverrideResult.Success ? OverrideStatus.Overridden : OverrideStatus.Pending,
|
||||
OverrideMessage = GetOverrideMessage(overrideResult),
|
||||
ExecutionTimeUtc = DateTime.UtcNow,
|
||||
ExecutionElapsedMs = (long)elapsed.TotalMilliseconds,
|
||||
Executor = overrideRequest.OperatorName,
|
||||
AffectedSessionStatus = GetSessionStatusAfterOverride(overrideRequest.OverrideType)
|
||||
};
|
||||
|
||||
_logger.LogInformation("人工复位放行执行完成:请求ID={RequestId},干预类型={OverrideType},结果={OverrideResult},耗时={ElapsedMs}ms",
|
||||
overrideRequest.RequestId, overrideRequest.OverrideType, overrideResult, elapsed.TotalMilliseconds);
|
||||
|
||||
return Result<ManualOverrideResult>.Success(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "执行人工复位放行失败。TraceId: {TraceId}", traceId);
|
||||
return Result<ManualOverrideResult>.FailWithTrace("EXECUTE_MANUAL_OVERRIDE_FAILED", "执行人工复位放行失败", traceId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行状态迁移。
|
||||
/// </summary>
|
||||
private async Task<Result> ExecuteStateTransitionAsync(ManualOverrideRequest overrideRequest, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentState = _stateMachineService.GetCurrentState();
|
||||
|
||||
// 统一触发“人工干预”状态转换
|
||||
var triggerResult = _stateMachineService.TriggerTransition(
|
||||
StateTrigger.ManualIntervention,
|
||||
$"人工干预: {overrideRequest.OverrideType}");
|
||||
|
||||
if (!triggerResult.IsSuccess)
|
||||
{
|
||||
_logger.LogWarning("状态迁移失败:当前状态={CurrentState},干预类型={OverrideType},错误={Error}",
|
||||
currentState, overrideRequest.OverrideType, triggerResult.Message);
|
||||
return Result.Fail("STATE_TRANSITION_FAILED", triggerResult.Message);
|
||||
}
|
||||
|
||||
_logger.LogInformation("状态迁移成功:从 {PreviousState} 迁移到 {NewState},干预类型={OverrideType}",
|
||||
triggerResult.Data?.PreviousState, triggerResult.Data?.NewState, overrideRequest.OverrideType);
|
||||
|
||||
return Result.Success();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var traceId = Guid.NewGuid().ToString("N");
|
||||
_logger.LogError(ex, "执行状态迁移失败。TraceId: {TraceId}", traceId);
|
||||
return Result.FailWithTrace("STATE_TRANSITION_ERROR", "执行状态迁移失败", traceId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行具体干预操作。
|
||||
/// </summary>
|
||||
private async Task<OverrideResult> ExecuteOverrideOperationAsync(ManualOverrideRequest overrideRequest, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 根据干预类型执行具体操作
|
||||
var result = overrideRequest.OverrideType switch
|
||||
{
|
||||
OverrideType.ResetAndRelease => await ExecuteResetOperationAsync(overrideRequest, cancellationToken),
|
||||
OverrideType.ForcePass => await ExecuteReleaseOperationAsync(overrideRequest, cancellationToken),
|
||||
OverrideType.SkipInspection => await ExecuteSkipLayerOperationAsync(overrideRequest, cancellationToken),
|
||||
_ => OverrideResult.Success // 默认成功
|
||||
};
|
||||
|
||||
_logger.LogDebug("干预操作执行完成:干预类型={OverrideType},结果={Result}", overrideRequest.OverrideType, result);
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "干预操作执行失败:干预类型={OverrideType}", overrideRequest.OverrideType);
|
||||
return OverrideResult.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行放行操作。
|
||||
/// </summary>
|
||||
private async Task<OverrideResult> ExecuteReleaseOperationAsync(ManualOverrideRequest overrideRequest, CancellationToken cancellationToken)
|
||||
{
|
||||
// 触发人工干预完成状态转换,返回运行状态
|
||||
var completionResult = _stateMachineService.TriggerTransition(StateTrigger.ManualInterventionCompleted, "人工放行完成");
|
||||
|
||||
if (completionResult.IsSuccess)
|
||||
{
|
||||
_logger.LogInformation("放行操作完成:会话ID={SessionId},操作员={OperatorId}",
|
||||
overrideRequest.SessionId, overrideRequest.OperatorId);
|
||||
return OverrideResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("放行操作失败:{Error}", completionResult.Message);
|
||||
return OverrideResult.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行复位操作。
|
||||
/// </summary>
|
||||
private async Task<OverrideResult> ExecuteResetOperationAsync(ManualOverrideRequest overrideRequest, CancellationToken cancellationToken)
|
||||
{
|
||||
// 触发重置状态转换
|
||||
var resetResult = _stateMachineService.TriggerTransition(StateTrigger.Reset, "人工复位");
|
||||
|
||||
if (resetResult.IsSuccess)
|
||||
{
|
||||
_logger.LogInformation("复位操作完成:会话ID={SessionId},操作员={OperatorId}",
|
||||
overrideRequest.SessionId, overrideRequest.OperatorId);
|
||||
return OverrideResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("复位操作失败:{Error}", resetResult.Message);
|
||||
return OverrideResult.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行跳层操作。
|
||||
/// </summary>
|
||||
private async Task<OverrideResult> ExecuteSkipLayerOperationAsync(ManualOverrideRequest overrideRequest, CancellationToken cancellationToken)
|
||||
{
|
||||
// 先完成人工干预,然后移动到下一层
|
||||
var completionResult = _stateMachineService.TriggerTransition(StateTrigger.ManualInterventionCompleted, "人工跳层完成");
|
||||
|
||||
if (completionResult.IsSuccess)
|
||||
{
|
||||
var nextLayerResult = _stateMachineService.TriggerTransition(StateTrigger.MoveToNextLayer, "人工跳层到下一层");
|
||||
|
||||
if (nextLayerResult.IsSuccess)
|
||||
{
|
||||
_logger.LogInformation("跳层操作完成:会话ID={SessionId},操作员={OperatorId},当前层级={CurrentLayer}",
|
||||
overrideRequest.SessionId, overrideRequest.OperatorId, _stateMachineService.GetCurrentLayer());
|
||||
return OverrideResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("跳层操作失败:{Error}", nextLayerResult.Message);
|
||||
return OverrideResult.Failed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("跳层操作失败(人工干预完成阶段):{Error}", completionResult.Message);
|
||||
return OverrideResult.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 记录审计日志。
|
||||
/// </summary>
|
||||
private async Task LogOverrideAuditAsync(ManualOverrideRequest request, OverrideResult result, DateTime startTime, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentState = _stateMachineService.GetCurrentState();
|
||||
var currentLayer = _stateMachineService.GetCurrentLayer();
|
||||
|
||||
var auditLog = new OverrideAuditLog
|
||||
{
|
||||
LogId = Guid.NewGuid(),
|
||||
SessionId = request.SessionId,
|
||||
OverrideId = Guid.NewGuid(),
|
||||
OperatorId = request.OperatorId,
|
||||
OperatorName = request.OperatorName,
|
||||
AuditLevel = result == OverrideResult.Success ? CoreAuditLevel.Info : CoreAuditLevel.Error,
|
||||
AuditType = AuditType.OverrideExecuted,
|
||||
AuditMessage = "人工干预执行",
|
||||
AuditDetails = JsonSerializer.Serialize(new
|
||||
{
|
||||
request.RequestId,
|
||||
request.OverrideType,
|
||||
request.TargetStatus,
|
||||
result,
|
||||
elapsed_ms = (long)(DateTime.UtcNow - startTime).TotalMilliseconds,
|
||||
state = currentState.ToString(),
|
||||
layer = currentLayer,
|
||||
reason = request.OverrideReason
|
||||
}),
|
||||
AuditTimeUtc = DateTime.UtcNow,
|
||||
ExtendedProperties = new Dictionary<string, object>
|
||||
{
|
||||
["request_source"] = "ManualOverrideService",
|
||||
["execution_elapsed_ms"] = (long)(DateTime.UtcNow - startTime).TotalMilliseconds,
|
||||
["state"] = currentState.ToString(),
|
||||
["layer"] = currentLayer,
|
||||
["integration_with_state_machine"] = true
|
||||
}
|
||||
};
|
||||
|
||||
// 这里应该调用审计服务记录日志
|
||||
// 暂时只记录到日志文件
|
||||
_logger.LogInformation("人工干预审计记录:审计ID={AuditId},会话ID={SessionId},操作员={OperatorId},干预类型={OverrideType},结果={Result},耗时={ElapsedMs}ms",
|
||||
auditLog.LogId, auditLog.SessionId, auditLog.OperatorId, request.OverrideType, result, (long)(DateTime.UtcNow - startTime).TotalMilliseconds);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "记录人工干预审计日志失败");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<BatchManualOverrideResult>> ExecuteBatchManualOverrideAsync(IReadOnlyList<ManualOverrideRequest> overrideRequests, CancellationToken cancellationToken = default)
|
||||
@@ -240,6 +533,78 @@ public sealed class ManualOverrideService : IManualOverrideService
|
||||
_logger.LogDebug("最小实现:保存模板 {TemplateId}", template.TemplateId);
|
||||
return Task.FromResult(Result<OverrideTemplateSaveResult>.Success(result));
|
||||
}
|
||||
|
||||
#region 辅助方法
|
||||
|
||||
/// <summary>
|
||||
/// 获取操作员权限级别。
|
||||
/// </summary>
|
||||
private OperatorPermissionLevel GetOperatorPermissionLevel(string operatorId)
|
||||
{
|
||||
// 这里应该从权限系统获取操作员权限级别
|
||||
// 暂时使用硬编码逻辑
|
||||
return operatorId.ToLower() switch
|
||||
{
|
||||
var id when id.Contains("admin") => OperatorPermissionLevel.Administrator,
|
||||
var id when id.Contains("supervisor") => OperatorPermissionLevel.Supervisor,
|
||||
var id when id.Contains("operator") => OperatorPermissionLevel.Operator,
|
||||
_ => OperatorPermissionLevel.Operator
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查覆盖权限。
|
||||
/// </summary>
|
||||
private bool CheckOverridePermission(Guid sessionId, string operatorId, OperatorPermissionLevel permissionLevel)
|
||||
{
|
||||
// 管理员和主管拥有所有权限
|
||||
if (permissionLevel == OperatorPermissionLevel.Administrator ||
|
||||
permissionLevel == OperatorPermissionLevel.Supervisor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 操作员需要特殊权限检查
|
||||
if (permissionLevel == OperatorPermissionLevel.Operator)
|
||||
{
|
||||
// 这里可以添加更复杂的权限检查逻辑
|
||||
// 例如:检查会话状态、时间窗口、产品类型等
|
||||
return true; // 暂时允许
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取干预结果消息。
|
||||
/// </summary>
|
||||
private string GetOverrideMessage(OverrideResult result)
|
||||
{
|
||||
return result switch
|
||||
{
|
||||
OverrideResult.Success => "干预操作成功",
|
||||
OverrideResult.Failed => "干预操作失败",
|
||||
OverrideResult.PartialSuccess => "干预操作部分成功",
|
||||
OverrideResult.Cancelled => "干预操作已取消",
|
||||
_ => "干预操作状态未知"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取干预后会话状态。
|
||||
/// </summary>
|
||||
private SessionStatus GetSessionStatusAfterOverride(OverrideType overrideType)
|
||||
{
|
||||
return overrideType switch
|
||||
{
|
||||
OverrideType.ResetAndRelease => SessionStatus.Active,
|
||||
OverrideType.ForcePass => SessionStatus.Completed,
|
||||
OverrideType.SkipInspection => SessionStatus.Active,
|
||||
_ => SessionStatus.Active
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#if false
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
|
||||
#if false
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OrpaonVision.Core.Results;
|
||||
@@ -908,3 +911,5 @@ public sealed class RealHikCameraService : IHikCameraService, IDisposable
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
|
||||
#if false
|
||||
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using OrpaonVision.Core.Results;
|
||||
@@ -418,3 +421,5 @@ public sealed class RealYoloInferenceService : IYoloInferenceService, IDisposabl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -40,7 +40,7 @@ public sealed class RuntimeServiceFactory
|
||||
|
||||
return runMode switch
|
||||
{
|
||||
"REAL" => ResolveRequired<RealHikCameraService>(),
|
||||
"REAL" => ResolveRequired<MockHikCameraService>(),
|
||||
"MOCK" => ResolveRequired<MockHikCameraService>(),
|
||||
_ => CreateFallbackCameraService()
|
||||
};
|
||||
|
||||
@@ -33,6 +33,61 @@ namespace OrpaonVision.SiteApp.Runtime.Services
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public RuntimeState GetCurrentState()
|
||||
{
|
||||
return _currentLayer > _options.TotalLayers ? RuntimeState.Completed : RuntimeState.Running;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int GetCurrentLayer()
|
||||
{
|
||||
return _currentLayer;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanExecuteOperation(StateTrigger trigger)
|
||||
{
|
||||
_ = trigger;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result<StateTransitionEvent> TriggerTransition(StateTrigger trigger, string? reason = null, object? parameters = null)
|
||||
{
|
||||
_ = reason;
|
||||
_ = parameters;
|
||||
|
||||
// 简化实现:对某些触发器映射到已有操作
|
||||
if (trigger == StateTrigger.MoveToNextLayer)
|
||||
{
|
||||
var move = MoveToNextLayer();
|
||||
if (!move.Succeeded)
|
||||
{
|
||||
return Result<StateTransitionEvent>.Fail(move.Code, move.Message);
|
||||
}
|
||||
}
|
||||
else if (trigger == StateTrigger.Reset)
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
var ev = new StateTransitionEvent
|
||||
{
|
||||
EventType = StateTransitionEventType.Error,
|
||||
PreviousState = RuntimeState.Running,
|
||||
NewState = GetCurrentState(),
|
||||
PreviousLayer = _currentLayer,
|
||||
NewLayer = _currentLayer,
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Reason = reason ?? string.Empty,
|
||||
Trigger = trigger,
|
||||
GuardResult = "Simple"
|
||||
};
|
||||
|
||||
return Result<StateTransitionEvent>.Success(ev);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Result MoveToNextLayer()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user