1168 lines
52 KiB
C#
1168 lines
52 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using OrpaonVision.Core.LayerTransition;
|
||
using OrpaonVision.Core.Results;
|
||
using OrpaonVision.SiteApp.Runtime.Options;
|
||
using System.Collections.Concurrent;
|
||
using System.Text.Json;
|
||
|
||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||
|
||
/// <summary>
|
||
/// 层级转换服务实现。
|
||
/// </summary>
|
||
#if false
|
||
public sealed class LayerTransitionService : ILayerTransitionService
|
||
{
|
||
private readonly ILogger<LayerTransitionService> _logger;
|
||
private readonly RuntimeOptions _options;
|
||
private readonly ConcurrentDictionary<Guid, LayerTransitionProtectionConfig> _protectionConfigs = new();
|
||
private readonly ConcurrentDictionary<Guid, LayerTransitionHistory> _transitionHistory = new();
|
||
private readonly ConcurrentDictionary<string, DateTime> _layerEntryTimes = new();
|
||
private readonly object _lock = new();
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public LayerTransitionService(ILogger<LayerTransitionService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
InitializeDefaultProtectionConfigs();
|
||
}
|
||
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// 层级转换服务最小实现。
|
||
/// </summary>
|
||
public sealed class LayerTransitionService : ILayerTransitionService
|
||
{
|
||
private readonly ILogger<LayerTransitionService> _logger;
|
||
private readonly ConcurrentDictionary<Guid, LayerTransitionProtectionConfig> _protectionConfigs = new();
|
||
private readonly ConcurrentDictionary<Guid, LayerTransitionHistory> _transitionHistory = new();
|
||
|
||
public LayerTransitionService(ILogger<LayerTransitionService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_ = options;
|
||
}
|
||
|
||
public async Task<Result<LayerTransitionCheckResult>> CheckCanMoveForwardAsync(int currentLayer, LayerTransitionContext transitionContext, CancellationToken cancellationToken = default)
|
||
{
|
||
var configResult = await GetProtectionConfigAsync(transitionContext.ProductTypeCode, currentLayer, cancellationToken);
|
||
if (!configResult.Succeeded || configResult.Data == null)
|
||
{
|
||
return Result<LayerTransitionCheckResult>.Fail("CONFIG_NOT_FOUND", "未找到保护配置");
|
||
}
|
||
|
||
return Result<LayerTransitionCheckResult>.Success(new LayerTransitionCheckResult
|
||
{
|
||
CanTransition = true,
|
||
TransitionType = LayerTransitionType.Forward,
|
||
CheckTimeUtc = DateTime.UtcNow,
|
||
AllowReason = "最小实现:允许前进",
|
||
ProtectionStrategy = configResult.Data.ProtectionStrategy,
|
||
ProtectionConditionChecks = Array.Empty<ProtectionConditionCheckResult>(),
|
||
SuggestedWaitTimeSeconds = 0
|
||
});
|
||
}
|
||
|
||
public async Task<Result<LayerTransitionCheckResult>> CheckCanMoveBackwardAsync(int currentLayer, LayerTransitionContext transitionContext, CancellationToken cancellationToken = default)
|
||
{
|
||
var configResult = await GetProtectionConfigAsync(transitionContext.ProductTypeCode, currentLayer, cancellationToken);
|
||
if (!configResult.Succeeded || configResult.Data == null)
|
||
{
|
||
return Result<LayerTransitionCheckResult>.Fail("CONFIG_NOT_FOUND", "未找到保护配置");
|
||
}
|
||
|
||
return Result<LayerTransitionCheckResult>.Success(new LayerTransitionCheckResult
|
||
{
|
||
CanTransition = currentLayer > 1,
|
||
TransitionType = LayerTransitionType.Backward,
|
||
CheckTimeUtc = DateTime.UtcNow,
|
||
AllowReason = currentLayer > 1 ? "最小实现:允许后退" : null,
|
||
DenyReason = currentLayer > 1 ? null : "已在第一层,无法后退",
|
||
ProtectionStrategy = configResult.Data.ProtectionStrategy,
|
||
ProtectionConditionChecks = Array.Empty<ProtectionConditionCheckResult>(),
|
||
SuggestedWaitTimeSeconds = 0
|
||
});
|
||
}
|
||
|
||
public Task<Result<LayerTransitionResult>> ExecuteTransitionAsync(LayerTransitionRequest transitionRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
var now = DateTime.UtcNow;
|
||
var transitionId = Guid.NewGuid();
|
||
|
||
var result = new LayerTransitionResult
|
||
{
|
||
IsSuccess = true,
|
||
TransitionId = transitionId,
|
||
RequestId = transitionRequest.RequestId,
|
||
TransitionType = transitionRequest.TransitionType,
|
||
SourceLayer = transitionRequest.CurrentLayer,
|
||
TargetLayer = transitionRequest.TargetLayer,
|
||
TransitionTimeUtc = now,
|
||
TransitionElapsedMs = 0,
|
||
ResultDescription = "最小实现:转换成功",
|
||
ProtectionTriggered = transitionRequest.IsForced,
|
||
TriggeredProtectionStrategy = transitionRequest.IsForced
|
||
? OrpaonVision.Core.LayerTransition.LayerTransitionProtectionStrategy.Strict
|
||
: OrpaonVision.Core.LayerTransition.LayerTransitionProtectionStrategy.None
|
||
};
|
||
|
||
_transitionHistory[transitionId] = new LayerTransitionHistory
|
||
{
|
||
TransitionId = transitionId,
|
||
ProductTypeCode = transitionRequest.ProductTypeCode,
|
||
SessionId = transitionRequest.SessionId,
|
||
TransitionType = transitionRequest.TransitionType,
|
||
SourceLayer = transitionRequest.CurrentLayer,
|
||
TargetLayer = transitionRequest.TargetLayer,
|
||
TransitionTimeUtc = now,
|
||
IsSuccess = true,
|
||
Reason = transitionRequest.Reason,
|
||
OperatorUser = transitionRequest.RequestUser,
|
||
IsForced = transitionRequest.IsForced,
|
||
ProtectionTriggered = transitionRequest.IsForced
|
||
};
|
||
|
||
_logger.LogInformation("最小层级转换执行:{TransitionId}", transitionId);
|
||
return Task.FromResult(Result<LayerTransitionResult>.Success(result));
|
||
}
|
||
|
||
public Task<Result<LayerTransitionResult>> RollbackTransitionAsync(LayerTransitionRollbackRequest rollbackRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
var now = DateTime.UtcNow;
|
||
var transitionId = Guid.NewGuid();
|
||
var rollbackInfo = new LayerTransitionRollbackInfo
|
||
{
|
||
RollbackId = Guid.NewGuid(),
|
||
OriginalTransitionId = rollbackRequest.OriginalTransitionId,
|
||
RollbackTimeUtc = now,
|
||
RollbackReason = rollbackRequest.Reason,
|
||
RollbackUser = rollbackRequest.RollbackUser
|
||
};
|
||
|
||
var result = new LayerTransitionResult
|
||
{
|
||
IsSuccess = true,
|
||
TransitionId = transitionId,
|
||
RequestId = rollbackRequest.RequestId,
|
||
TransitionType = LayerTransitionType.Reset,
|
||
SourceLayer = rollbackRequest.CurrentLayer,
|
||
TargetLayer = rollbackRequest.RollbackTargetLayer,
|
||
TransitionTimeUtc = now,
|
||
TransitionElapsedMs = 0,
|
||
ResultDescription = "最小实现:回滚成功",
|
||
ProtectionTriggered = false,
|
||
TriggeredProtectionStrategy = OrpaonVision.Core.LayerTransition.LayerTransitionProtectionStrategy.None,
|
||
RollbackInfo = rollbackInfo
|
||
};
|
||
|
||
_transitionHistory[transitionId] = new LayerTransitionHistory
|
||
{
|
||
TransitionId = transitionId,
|
||
ProductTypeCode = rollbackRequest.ProductTypeCode,
|
||
SessionId = rollbackRequest.SessionId,
|
||
TransitionType = LayerTransitionType.Reset,
|
||
SourceLayer = rollbackRequest.CurrentLayer,
|
||
TargetLayer = rollbackRequest.RollbackTargetLayer,
|
||
TransitionTimeUtc = now,
|
||
IsSuccess = true,
|
||
Reason = rollbackRequest.Reason,
|
||
OperatorUser = rollbackRequest.RollbackUser,
|
||
IsForced = false,
|
||
ProtectionTriggered = false,
|
||
RollbackInfo = rollbackInfo
|
||
};
|
||
|
||
return Task.FromResult(Result<LayerTransitionResult>.Success(result));
|
||
}
|
||
|
||
public Task<Result<LayerTransitionProtectionConfig>> GetProtectionConfigAsync(string productTypeCode, int layerNumber, CancellationToken cancellationToken = default)
|
||
{
|
||
var config = _protectionConfigs.Values.FirstOrDefault(c =>
|
||
string.Equals(c.ProductTypeCode, productTypeCode, StringComparison.OrdinalIgnoreCase) && c.LayerNumber == layerNumber);
|
||
|
||
if (config == null)
|
||
{
|
||
config = new LayerTransitionProtectionConfig
|
||
{
|
||
ConfigId = Guid.NewGuid(),
|
||
ProductTypeCode = productTypeCode,
|
||
LayerNumber = layerNumber,
|
||
ProtectionStrategy = OrpaonVision.Core.LayerTransition.LayerTransitionProtectionStrategy.None,
|
||
MinimumStayTimeSeconds = 0,
|
||
MaximumStayTimeSeconds = 300,
|
||
CompletionThreshold = 0,
|
||
AllowForceTransition = true,
|
||
RequiredPermissionForForce = "",
|
||
EnableAutoRollback = false,
|
||
AutoRollbackConditions = Array.Empty<AutoRollbackCondition>(),
|
||
ProtectionConditions = Array.Empty<ProtectionCondition>(),
|
||
IsEnabled = true,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
};
|
||
|
||
_protectionConfigs[config.ConfigId] = config;
|
||
}
|
||
|
||
return Task.FromResult(Result<LayerTransitionProtectionConfig>.Success(config));
|
||
}
|
||
|
||
public Task<Result> UpdateProtectionConfigAsync(LayerTransitionProtectionConfig config, CancellationToken cancellationToken = default)
|
||
{
|
||
var updated = new LayerTransitionProtectionConfig
|
||
{
|
||
ConfigId = config.ConfigId == Guid.Empty ? Guid.NewGuid() : config.ConfigId,
|
||
ProductTypeCode = config.ProductTypeCode,
|
||
LayerNumber = config.LayerNumber,
|
||
ProtectionStrategy = config.ProtectionStrategy,
|
||
MinimumStayTimeSeconds = config.MinimumStayTimeSeconds,
|
||
MaximumStayTimeSeconds = config.MaximumStayTimeSeconds,
|
||
CompletionThreshold = config.CompletionThreshold,
|
||
AllowForceTransition = config.AllowForceTransition,
|
||
RequiredPermissionForForce = config.RequiredPermissionForForce,
|
||
EnableAutoRollback = config.EnableAutoRollback,
|
||
AutoRollbackConditions = config.AutoRollbackConditions,
|
||
ProtectionConditions = config.ProtectionConditions,
|
||
IsEnabled = config.IsEnabled,
|
||
CreatedAtUtc = config.CreatedAtUtc,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = config.Version,
|
||
ExtendedProperties = new Dictionary<string, object>(config.ExtendedProperties)
|
||
};
|
||
|
||
_protectionConfigs[updated.ConfigId] = updated;
|
||
return Task.FromResult(Result.Success());
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<LayerTransitionHistory>>> GetTransitionHistoryAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
IReadOnlyList<LayerTransitionHistory> history = _transitionHistory.Values
|
||
.Where(h => h.TransitionTimeUtc >= startTime && h.TransitionTimeUtc <= endTime)
|
||
.OrderByDescending(h => h.TransitionTimeUtc)
|
||
.ToList();
|
||
return Task.FromResult(Result<IReadOnlyList<LayerTransitionHistory>>.Success(history));
|
||
}
|
||
|
||
public Task<Result<LayerTransitionStatistics>> GetTransitionStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
var history = _transitionHistory.Values
|
||
.Where(h => h.TransitionTimeUtc >= startTime && h.TransitionTimeUtc <= endTime)
|
||
.ToList();
|
||
|
||
var byType = history
|
||
.GroupBy(h => h.TransitionType)
|
||
.ToDictionary(
|
||
g => g.Key,
|
||
g => new TransitionTypeStatistics
|
||
{
|
||
TransitionType = g.Key,
|
||
TransitionCount = g.Count(),
|
||
SuccessCount = g.Count(x => x.IsSuccess),
|
||
AverageElapsedMs = 0
|
||
});
|
||
|
||
var byProduct = history
|
||
.GroupBy(h => h.ProductTypeCode)
|
||
.ToDictionary(
|
||
g => g.Key,
|
||
g => new ProductTypeTransitionStatistics
|
||
{
|
||
ProductTypeCode = g.Key,
|
||
TransitionCount = g.Count(),
|
||
SuccessCount = g.Count(x => x.IsSuccess),
|
||
AverageElapsedMs = 0
|
||
});
|
||
|
||
var byLayer = history
|
||
.GroupBy(h => h.SourceLayer)
|
||
.ToDictionary(
|
||
g => g.Key,
|
||
g => new LayerTransitionStatisticsByLayer
|
||
{
|
||
LayerNumber = g.Key,
|
||
TransitionCount = g.Count(),
|
||
SuccessCount = g.Count(x => x.IsSuccess),
|
||
AverageStayTimeSeconds = 0
|
||
});
|
||
|
||
var forced = history.Where(h => h.IsForced).ToList();
|
||
var triggered = history.Where(h => h.ProtectionTriggered).ToList();
|
||
|
||
var statistics = new LayerTransitionStatistics
|
||
{
|
||
StartTimeUtc = startTime,
|
||
EndTimeUtc = endTime,
|
||
TotalTransitions = history.Count,
|
||
SuccessfulTransitions = history.Count(h => h.IsSuccess),
|
||
FailedTransitions = history.Count(h => !h.IsSuccess),
|
||
ByTransitionType = byType,
|
||
ByProductType = byProduct,
|
||
ByLayer = byLayer,
|
||
ForceTransitions = new ForceTransitionStatistics
|
||
{
|
||
ForceTransitionCount = forced.Count,
|
||
SuccessCount = forced.Count(h => h.IsSuccess),
|
||
ByUser = forced.GroupBy(h => h.OperatorUser).ToDictionary(g => g.Key, g => g.Count())
|
||
},
|
||
ProtectionTriggers = new ProtectionTriggerStatistics
|
||
{
|
||
ProtectionTriggerCount = triggered.Count,
|
||
TransitionBlockedCount = 0,
|
||
ForceSuccessCount = triggered.Count(h => h.IsSuccess),
|
||
ByStrategy = triggered
|
||
.GroupBy(_ => OrpaonVision.Core.LayerTransition.LayerTransitionProtectionStrategy.None)
|
||
.ToDictionary(g => g.Key, g => g.Count())
|
||
}
|
||
};
|
||
|
||
return Task.FromResult(Result<LayerTransitionStatistics>.Success(statistics));
|
||
}
|
||
}
|
||
|
||
#if false
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerTransitionCheckResult>> CheckCanMoveForwardAsync(int currentLayer, LayerTransitionContext transitionContext, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("检查前进转换:当前层={CurrentLayer},产品类型={ProductTypeCode},会话={SessionId}",
|
||
currentLayer, transitionContext.ProductTypeCode, transitionContext.SessionId);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
// 获取保护配置
|
||
var configResult = await GetProtectionConfigAsync(transitionContext.ProductTypeCode, currentLayer, cancellationToken);
|
||
if (!configResult.IsSuccess)
|
||
{
|
||
return Result.FailWithTrace<LayerTransitionCheckResult>(configResult.Code, configResult.Message, configResult.TraceId ?? string.Empty);
|
||
}
|
||
|
||
var config = configResult.Data;
|
||
var protectionConditionChecks = new List<ProtectionConditionCheckResult>();
|
||
|
||
// 检查保护条件
|
||
var canTransition = true;
|
||
var denyReason = string.Empty;
|
||
var suggestedWaitTime = 0;
|
||
|
||
// 1. 检查最小停留时间
|
||
if (config.ProtectionStrategy == LayerTransitionProtectionStrategy.TimeProtection ||
|
||
config.ProtectionStrategy == LayerTransitionProtectionStrategy.Comprehensive ||
|
||
config.ProtectionStrategy == LayerTransitionProtectionStrategy.Strict)
|
||
{
|
||
var stayTimeCheck = CheckStayTimeCondition(currentLayer, transitionContext, config);
|
||
protectionConditionChecks.Add(stayTimeCheck);
|
||
|
||
if (!stayTimeCheck.IsConditionMet)
|
||
{
|
||
canTransition = false;
|
||
denyReason = $"未满足最小停留时间:需要{config.MinimumStayTimeSeconds}秒,实际{stayTimeCheck.ActualValue:F1}秒";
|
||
suggestedWaitTime = config.MinimumStayTimeSeconds - (int)stayTimeCheck.ActualValue;
|
||
}
|
||
}
|
||
|
||
// 2. 检查完成度条件
|
||
if (config.ProtectionStrategy == LayerTransitionProtectionStrategy.CompletionProtection ||
|
||
config.ProtectionStrategy == LayerTransitionProtectionStrategy.Comprehensive ||
|
||
config.ProtectionStrategy == LayerTransitionProtectionStrategy.Strict)
|
||
{
|
||
var completionCheck = CheckCompletionCondition(transitionContext, config);
|
||
protectionConditionChecks.Add(completionCheck);
|
||
|
||
if (!completionCheck.IsConditionMet)
|
||
{
|
||
canTransition = false;
|
||
denyReason = $"未满足完成度要求:需要{config.CompletionThreshold:F1}%,实际{transitionContext.CompletionPercentage:F1}%";
|
||
}
|
||
}
|
||
|
||
// 3. 检查错误状态条件
|
||
if (config.ProtectionStrategy == LayerTransitionProtectionStrategy.Strict)
|
||
{
|
||
var errorCheck = CheckErrorCondition(transitionContext, config);
|
||
protectionConditionChecks.Add(errorCheck);
|
||
|
||
if (!errorCheck.IsConditionMet)
|
||
{
|
||
canTransition = false;
|
||
denyReason = "存在错误状态,需要解决后才能转换";
|
||
}
|
||
}
|
||
|
||
// 4. 检查人工干预条件
|
||
if (config.ProtectionStrategy == LayerTransitionProtectionStrategy.Strict)
|
||
{
|
||
var interventionCheck = CheckManualInterventionCondition(transitionContext, config);
|
||
protectionConditionChecks.Add(interventionCheck);
|
||
|
||
if (!interventionCheck.IsConditionMet)
|
||
{
|
||
canTransition = false;
|
||
denyReason = $"需要人工干预:{transitionContext.ManualInterventionReason}";
|
||
}
|
||
}
|
||
|
||
var result = new LayerTransitionCheckResult
|
||
{
|
||
CanTransition = canTransition,
|
||
TransitionType = LayerTransitionType.Forward,
|
||
CheckTimeUtc = startTime,
|
||
AllowReason = canTransition ? "满足所有保护条件" : null,
|
||
DenyReason = canTransition ? null : denyReason,
|
||
ProtectionStrategy = config.ProtectionStrategy,
|
||
ProtectionConditionChecks = protectionConditionChecks,
|
||
SuggestedWaitTimeSeconds = suggestedWaitTime,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["check_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds,
|
||
["product_type_code"] = transitionContext.ProductTypeCode,
|
||
["session_id"] = transitionContext.SessionId,
|
||
["current_layer"] = currentLayer
|
||
}
|
||
};
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("前进转换检查完成:当前层={CurrentLayer},结果={CanTransition},策略={Strategy},耗时={ElapsedMs:F1}ms",
|
||
currentLayer, result.CanTransition, result.ProtectionStrategy, elapsed);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "检查前进转换失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "CHECK_FORWARD_TRANSITION_FAILED", "检查前进转换失败", traceId);
|
||
return Result.FailWithTrace<LayerTransitionCheckResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerTransitionCheckResult>> CheckCanMoveBackwardAsync(int currentLayer, LayerTransitionContext transitionContext, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("检查后退转换:当前层={CurrentLayer},产品类型={ProductTypeCode},会话={SessionId}",
|
||
currentLayer, transitionContext.ProductTypeCode, transitionContext.SessionId);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
// 获取保护配置
|
||
var configResult = await GetProtectionConfigAsync(transitionContext.ProductTypeCode, currentLayer, cancellationToken);
|
||
if (!configResult.IsSuccess)
|
||
{
|
||
return Result.FailWithTrace<LayerTransitionCheckResult>(configResult.Code, configResult.Message, configResult.TraceId ?? string.Empty);
|
||
}
|
||
|
||
var config = configResult.Data;
|
||
var protectionConditionChecks = new List<ProtectionConditionCheckResult>();
|
||
|
||
// 后退转换通常比较宽松,主要检查基本条件
|
||
var canTransition = true;
|
||
var denyReason = string.Empty;
|
||
|
||
// 检查是否在第一层
|
||
if (currentLayer <= 1)
|
||
{
|
||
canTransition = false;
|
||
denyReason = "已在第一层,无法后退";
|
||
}
|
||
|
||
// 严格策略下检查错误状态
|
||
if (config.ProtectionStrategy == LayerTransitionProtectionStrategy.Strict)
|
||
{
|
||
var errorCheck = CheckErrorCondition(transitionContext, config);
|
||
protectionConditionChecks.Add(errorCheck);
|
||
|
||
if (!errorCheck.IsConditionMet)
|
||
{
|
||
canTransition = false;
|
||
denyReason = "存在错误状态,需要解决后才能转换";
|
||
}
|
||
}
|
||
|
||
var result = new LayerTransitionCheckResult
|
||
{
|
||
CanTransition = canTransition,
|
||
TransitionType = LayerTransitionType.Backward,
|
||
CheckTimeUtc = startTime,
|
||
AllowReason = canTransition ? "满足后退转换条件" : null,
|
||
DenyReason = canTransition ? null : denyReason,
|
||
ProtectionStrategy = config.ProtectionStrategy,
|
||
ProtectionConditionChecks = protectionConditionChecks,
|
||
SuggestedWaitTimeSeconds = 0,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["check_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds,
|
||
["product_type_code"] = transitionContext.ProductTypeCode,
|
||
["session_id"] = transitionContext.SessionId,
|
||
["current_layer"] = currentLayer
|
||
}
|
||
};
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("后退转换检查完成:当前层={CurrentLayer},结果={CanTransition},策略={Strategy},耗时={ElapsedMs:F1}ms",
|
||
currentLayer, result.CanTransition, result.ProtectionStrategy, elapsed);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "检查后退转换失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "CHECK_BACKWARD_TRANSITION_FAILED", "检查后退转换失败", traceId);
|
||
return Result.FailWithTrace<LayerTransitionCheckResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerTransitionResult>> ExecuteTransitionAsync(LayerTransitionRequest transitionRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("执行层级转换:产品类型={ProductTypeCode},会话={SessionId},从{SourceLayer}层到{TargetLayer}层",
|
||
transitionRequest.ProductTypeCode, transitionRequest.SessionId, transitionRequest.CurrentLayer, transitionRequest.TargetLayer);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var transitionId = Guid.NewGuid();
|
||
|
||
// 检查转换权限(如果不是强制转换)
|
||
if (!transitionRequest.IsForced)
|
||
{
|
||
var checkResult = await CheckTransitionPermissionAsync(transitionRequest, cancellationToken);
|
||
if (!checkResult.IsSuccess)
|
||
{
|
||
return Result.FailWithTrace<LayerTransitionResult>(checkResult.Code, checkResult.Message, checkResult.TraceId ?? string.Empty);
|
||
}
|
||
|
||
if (!checkResult.Data)
|
||
{
|
||
return Result.FailWithTrace<LayerTransitionResult>("TRANSITION_PERMISSION_DENIED", "转换权限不足", string.Empty);
|
||
}
|
||
}
|
||
|
||
// 执行转换
|
||
var transitionTime = DateTime.UtcNow;
|
||
var elapsed = (long)(transitionTime - startTime).TotalMilliseconds;
|
||
|
||
// 更新层级进入时间
|
||
var layerKey = $"{transitionRequest.ProductTypeCode}_{transitionRequest.SessionId}_{transitionRequest.TargetLayer}";
|
||
_layerEntryTimes[layerKey] = transitionTime;
|
||
|
||
// 记录转换历史
|
||
var transitionHistory = new LayerTransitionHistory
|
||
{
|
||
TransitionId = transitionId,
|
||
ProductTypeCode = transitionRequest.ProductTypeCode,
|
||
SessionId = transitionRequest.SessionId,
|
||
TransitionType = transitionRequest.TransitionType,
|
||
SourceLayer = transitionRequest.CurrentLayer,
|
||
TargetLayer = transitionRequest.TargetLayer,
|
||
TransitionTimeUtc = transitionTime,
|
||
IsSuccess = true,
|
||
Reason = transitionRequest.Reason,
|
||
OperatorUser = transitionRequest.RequestUser,
|
||
IsForced = transitionRequest.IsForced,
|
||
ProtectionTriggered = transitionRequest.IsForced,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["transition_elapsed_ms"] = elapsed,
|
||
["request_id"] = transitionRequest.RequestId
|
||
}
|
||
};
|
||
|
||
_transitionHistory.TryAdd(transitionId, transitionHistory);
|
||
|
||
var result = new LayerTransitionResult
|
||
{
|
||
IsSuccess = true,
|
||
TransitionId = transitionId,
|
||
RequestId = transitionRequest.RequestId,
|
||
TransitionType = transitionRequest.TransitionType,
|
||
SourceLayer = transitionRequest.CurrentLayer,
|
||
TargetLayer = transitionRequest.TargetLayer,
|
||
TransitionTimeUtc = transitionTime,
|
||
TransitionElapsedMs = elapsed,
|
||
ResultDescription = $"成功从第{transitionRequest.CurrentLayer}层转换到第{transitionRequest.TargetLayer}层",
|
||
ProtectionTriggered = transitionRequest.IsForced,
|
||
TriggeredProtectionStrategy = transitionRequest.IsForced ? LayerTransitionProtectionStrategy.Strict : LayerTransitionProtectionStrategy.None,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["operator_user"] = transitionRequest.RequestUser,
|
||
["is_forced"] = transitionRequest.IsForced
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("层级转换完成:转换ID={TransitionId},从{SourceLayer}层到{TargetLayer}层,耗时={ElapsedMs}ms",
|
||
transitionId, result.SourceLayer, result.TargetLayer, result.TransitionElapsedMs);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "执行层级转换失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "EXECUTE_TRANSITION_FAILED", "执行层级转换失败", traceId);
|
||
return Result.FailWithTrace<LayerTransitionResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerTransitionResult>> RollbackTransitionAsync(LayerTransitionRollbackRequest rollbackRequest, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("执行层级转换回滚:产品类型={ProductTypeCode},会话={SessionId},从{CurrentLayer}层回滚到{TargetLayer}层",
|
||
rollbackRequest.ProductTypeCode, rollbackRequest.SessionId, rollbackRequest.CurrentLayer, rollbackRequest.RollbackTargetLayer);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var transitionId = Guid.NewGuid();
|
||
|
||
// 查找原转换记录
|
||
var originalTransition = _transitionHistory.Values
|
||
.FirstOrDefault(t => t.TransitionId == rollbackRequest.OriginalTransitionId);
|
||
|
||
if (originalTransition == null)
|
||
{
|
||
return Result.FailWithTrace<LayerTransitionResult>("ORIGINAL_TRANSITION_NOT_FOUND", "未找到原转换记录", string.Empty);
|
||
}
|
||
|
||
// 执行回滚
|
||
var rollbackTime = DateTime.UtcNow;
|
||
var elapsed = (long)(rollbackTime - startTime).TotalMilliseconds;
|
||
|
||
// 更新层级进入时间
|
||
var layerKey = $"{rollbackRequest.ProductTypeCode}_{rollbackRequest.SessionId}_{rollbackRequest.RollbackTargetLayer}";
|
||
_layerEntryTimes[layerKey] = rollbackTime;
|
||
|
||
// 记录回滚信息
|
||
var rollbackInfo = new LayerTransitionRollbackInfo
|
||
{
|
||
RollbackId = Guid.NewGuid(),
|
||
OriginalTransitionId = rollbackRequest.OriginalTransitionId,
|
||
RollbackTimeUtc = rollbackTime,
|
||
RollbackReason = rollbackRequest.Reason,
|
||
RollbackUser = rollbackRequest.RollbackUser
|
||
};
|
||
|
||
// 记录回滚历史
|
||
var rollbackHistory = new LayerTransitionHistory
|
||
{
|
||
TransitionId = transitionId,
|
||
ProductTypeCode = rollbackRequest.ProductTypeCode,
|
||
SessionId = rollbackRequest.SessionId,
|
||
TransitionType = LayerTransitionType.Reset, // 使用Reset类型表示回滚
|
||
SourceLayer = rollbackRequest.CurrentLayer,
|
||
TargetLayer = rollbackRequest.RollbackTargetLayer,
|
||
TransitionTimeUtc = rollbackTime,
|
||
IsSuccess = true,
|
||
Reason = rollbackRequest.Reason,
|
||
OperatorUser = rollbackRequest.RollbackUser,
|
||
IsForced = false,
|
||
ProtectionTriggered = false,
|
||
RollbackInfo = rollbackInfo,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["transition_elapsed_ms"] = elapsed,
|
||
["original_transition_id"] = rollbackRequest.OriginalTransitionId,
|
||
["is_rollback"] = true
|
||
}
|
||
};
|
||
|
||
_transitionHistory.TryAdd(transitionId, rollbackHistory);
|
||
|
||
var result = new LayerTransitionResult
|
||
{
|
||
IsSuccess = true,
|
||
TransitionId = transitionId,
|
||
RequestId = rollbackRequest.RequestId,
|
||
TransitionType = LayerTransitionType.Reset,
|
||
SourceLayer = rollbackRequest.CurrentLayer,
|
||
TargetLayer = rollbackRequest.RollbackTargetLayer,
|
||
TransitionTimeUtc = rollbackTime,
|
||
TransitionElapsedMs = elapsed,
|
||
ResultDescription = $"成功从第{rollbackRequest.CurrentLayer}层回滚到第{rollbackRequest.RollbackTargetLayer}层",
|
||
ProtectionTriggered = false,
|
||
RollbackInfo = rollbackInfo,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["rollback_user"] = rollbackRequest.RollbackUser,
|
||
["original_transition_id"] = rollbackRequest.OriginalTransitionId
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("层级转换回滚完成:转换ID={TransitionId},从{SourceLayer}层回滚到{TargetLayer}层,耗时={ElapsedMs}ms",
|
||
transitionId, result.SourceLayer, result.TargetLayer, result.TransitionElapsedMs);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "执行层级转换回滚失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "ROLLBACK_TRANSITION_FAILED", "执行层级转换回滚失败", traceId);
|
||
return Result.FailWithTrace<LayerTransitionResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerTransitionProtectionConfig>> GetProtectionConfigAsync(string productTypeCode, int layerNumber, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取层级转换保护配置:产品类型={ProductTypeCode},层级={LayerNumber}", productTypeCode, layerNumber);
|
||
|
||
// 查找特定配置
|
||
var config = _protectionConfigs.Values
|
||
.FirstOrDefault(c => c.ProductTypeCode.Equals(productTypeCode, StringComparison.OrdinalIgnoreCase) && c.LayerNumber == layerNumber);
|
||
|
||
if (config == null)
|
||
{
|
||
// 返回默认配置
|
||
config = CreateDefaultProtectionConfig(productTypeCode, layerNumber);
|
||
}
|
||
|
||
_logger.LogDebug("获取层级转换保护配置完成:策略={Strategy}", config.ProtectionStrategy);
|
||
return Result.Success(config);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取层级转换保护配置失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_PROTECTION_CONFIG_FAILED", "获取层级转换保护配置失败", traceId);
|
||
return Result.FailWithTrace<LayerTransitionProtectionConfig>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result> UpdateProtectionConfigAsync(LayerTransitionProtectionConfig config, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("更新层级转换保护配置:产品类型={ProductTypeCode},层级={LayerNumber},策略={Strategy}",
|
||
config.ProductTypeCode, config.LayerNumber, config.ProtectionStrategy);
|
||
|
||
lock (_lock)
|
||
{
|
||
config.UpdatedAtUtc = DateTime.UtcNow;
|
||
_protectionConfigs[config.ConfigId] = config;
|
||
}
|
||
|
||
_logger.LogInformation("层级转换保护配置更新成功:配置ID={ConfigId}", config.ConfigId);
|
||
return Result.Success();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "更新层级转换保护配置失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "UPDATE_PROTECTION_CONFIG_FAILED", "更新层级转换保护配置失败", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<LayerTransitionHistory>>> GetTransitionHistoryAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取层级转换历史:开始时间={StartTime},结束时间={EndTime}", startTime, endTime);
|
||
|
||
var history = _transitionHistory.Values
|
||
.Where(h => h.TransitionTimeUtc >= startTime && h.TransitionTimeUtc <= endTime)
|
||
.OrderByDescending(h => h.TransitionTimeUtc)
|
||
.ToList();
|
||
|
||
_logger.LogDebug("获取层级转换历史完成:记录数量={Count}", history.Count);
|
||
return Result.Success<IReadOnlyList<LayerTransitionHistory>>(history);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取层级转换历史失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_TRANSITION_HISTORY_FAILED", "获取层级转换历史失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<LayerTransitionHistory>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerTransitionStatistics>> GetTransitionStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取层级转换统计信息:开始时间={StartTime},结束时间={EndTime}", startTime, endTime);
|
||
|
||
var history = _transitionHistory.Values
|
||
.Where(h => h.TransitionTimeUtc >= startTime && h.TransitionTimeUtc <= endTime)
|
||
.ToList();
|
||
|
||
var statistics = new LayerTransitionStatistics
|
||
{
|
||
StartTimeUtc = startTime,
|
||
EndTimeUtc = endTime,
|
||
TotalTransitions = history.Count,
|
||
SuccessfulTransitions = history.Count(h => h.IsSuccess),
|
||
FailedTransitions = history.Count(h => !h.IsSuccess)
|
||
};
|
||
|
||
// 按转换类型分组统计
|
||
var typeGroups = history.GroupBy(h => h.TransitionType);
|
||
foreach (var group in typeGroups)
|
||
{
|
||
statistics.ByTransitionType[group.Key] = new TransitionTypeStatistics
|
||
{
|
||
TransitionType = group.Key,
|
||
TransitionCount = group.Count(),
|
||
SuccessCount = group.Count(h => h.IsSuccess),
|
||
AverageElapsedMs = group.Where(h => h.ExtendedProperties.ContainsKey("transition_elapsed_ms"))
|
||
.Average(h => Convert.ToDouble(h.ExtendedProperties["transition_elapsed_ms"]))
|
||
};
|
||
}
|
||
|
||
// 按产品类型分组统计
|
||
var productTypeGroups = history.GroupBy(h => h.ProductTypeCode);
|
||
foreach (var group in productTypeGroups)
|
||
{
|
||
statistics.ByProductType[group.Key] = new ProductTypeTransitionStatistics
|
||
{
|
||
ProductTypeCode = group.Key,
|
||
TransitionCount = group.Count(),
|
||
SuccessCount = group.Count(h => h.IsSuccess),
|
||
AverageElapsedMs = group.Where(h => h.ExtendedProperties.ContainsKey("transition_elapsed_ms"))
|
||
.Average(h => Convert.ToDouble(h.ExtendedProperties["transition_elapsed_ms"]))
|
||
};
|
||
}
|
||
|
||
// 按层级分组统计
|
||
var layerGroups = history.GroupBy(h => h.SourceLayer);
|
||
foreach (var group in layerGroups)
|
||
{
|
||
statistics.ByLayer[group.Key] = new LayerTransitionStatisticsByLayer
|
||
{
|
||
LayerNumber = group.Key,
|
||
TransitionCount = group.Count(),
|
||
SuccessCount = group.Count(h => h.IsSuccess),
|
||
AverageStayTimeSeconds = CalculateAverageStayTime(group.Key, group.ToList())
|
||
};
|
||
}
|
||
|
||
// 强制转换统计
|
||
var forceTransitions = history.Where(h => h.IsForced).ToList();
|
||
statistics.ForceTransitions = new ForceTransitionStatistics
|
||
{
|
||
ForceTransitionCount = forceTransitions.Count,
|
||
SuccessCount = forceTransitions.Count(h => h.IsSuccess),
|
||
ByUser = forceTransitions.GroupBy(h => h.OperatorUser).ToDictionary(g => g.Key, g => g.Count())
|
||
};
|
||
|
||
// 保护触发统计
|
||
var protectionTriggered = history.Where(h => h.ProtectionTriggered).ToList();
|
||
statistics.ProtectionTriggers = new ProtectionTriggerStatistics
|
||
{
|
||
ProtectionTriggerCount = protectionTriggered.Count,
|
||
TransitionBlockedCount = 0, // 简化处理
|
||
ForceSuccessCount = protectionTriggered.Count(h => h.IsSuccess),
|
||
ByStrategy = protectionTriggered.GroupBy(h => h.TransitionType).ToDictionary(g => g.Key, g => g.Count())
|
||
};
|
||
|
||
_logger.LogInformation("层级转换统计信息获取完成:总转换次数={Total},成功率={SuccessRate:F2}%,平均耗时={AvgElapsedMs:F1}ms",
|
||
statistics.TotalTransitions, statistics.OverallSuccessRate,
|
||
statistics.ByTransitionType.Values.Average(v => v.AverageElapsedMs));
|
||
|
||
return Result.Success(statistics);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取层级转换统计信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_TRANSITION_STATISTICS_FAILED", "获取层级转换统计信息失败", traceId);
|
||
return Result.FailWithTrace<LayerTransitionStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
#region 私有方法
|
||
|
||
/// <summary>
|
||
/// 初始化默认保护配置。
|
||
/// </summary>
|
||
private void InitializeDefaultProtectionConfigs()
|
||
{
|
||
var defaultConfigs = new List<LayerTransitionProtectionConfig>();
|
||
|
||
// 为每个层级创建默认配置
|
||
for (int layer = 1; layer <= _options.TotalLayers; layer++)
|
||
{
|
||
var config = CreateDefaultProtectionConfig("default", layer);
|
||
defaultConfigs.Add(config);
|
||
}
|
||
|
||
foreach (var config in defaultConfigs)
|
||
{
|
||
_protectionConfigs[config.ConfigId] = config;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建默认保护配置。
|
||
/// </summary>
|
||
private LayerTransitionProtectionConfig CreateDefaultProtectionConfig(string productTypeCode, int layerNumber)
|
||
{
|
||
var strategy = layerNumber switch
|
||
{
|
||
1 => LayerTransitionProtectionStrategy.TimeProtection, // 第一层只需要时间保护
|
||
_ => LayerTransitionProtectionStrategy.Comprehensive // 其他层使用综合保护
|
||
};
|
||
|
||
var config = new LayerTransitionProtectionConfig
|
||
{
|
||
ConfigId = Guid.NewGuid(),
|
||
ProductTypeCode = productTypeCode,
|
||
LayerNumber = layerNumber,
|
||
ProtectionStrategy = strategy,
|
||
MinimumStayTimeSeconds = layerNumber == 1 ? 5 : 10, // 第一层5秒,其他层10秒
|
||
MaximumStayTimeSeconds = 300, // 5分钟
|
||
CompletionThreshold = 90.0, // 90%完成度
|
||
AllowForceTransition = true,
|
||
RequiredPermissionForForce = "supervisor",
|
||
EnableAutoRollback = false,
|
||
AutoRollbackConditions = new List<AutoRollbackCondition>(),
|
||
ProtectionConditions = CreateDefaultProtectionConditions(strategy),
|
||
IsEnabled = true,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
};
|
||
|
||
return config;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建默认保护条件。
|
||
/// </summary>
|
||
private IReadOnlyList<ProtectionCondition> CreateDefaultProtectionConditions(OrpaonVision.Core.LayerTransition.LayerTransitionProtectionStrategy strategy)
|
||
{
|
||
var conditions = new List<ProtectionCondition>();
|
||
|
||
switch (strategy)
|
||
{
|
||
case LayerTransitionProtectionStrategy.TimeProtection:
|
||
conditions.Add(new ProtectionCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "最小停留时间",
|
||
ConditionType = ProtectionConditionType.StayTime,
|
||
IsMandatory = true,
|
||
Threshold = 10.0,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
Description = "必须在本层停留足够时间"
|
||
});
|
||
break;
|
||
|
||
case LayerTransitionProtectionStrategy.CompletionProtection:
|
||
conditions.Add(new ProtectionCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "完成度要求",
|
||
ConditionType = ProtectionConditionType.Completion,
|
||
IsMandatory = true,
|
||
Threshold = 90.0,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
Description = "当前层必须达到足够的完成度"
|
||
});
|
||
break;
|
||
|
||
case LayerTransitionProtectionStrategy.Comprehensive:
|
||
conditions.Add(new ProtectionCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "最小停留时间",
|
||
ConditionType = ProtectionConditionType.StayTime,
|
||
IsMandatory = true,
|
||
Threshold = 10.0,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
Description = "必须在本层停留足够时间"
|
||
});
|
||
conditions.Add(new ProtectionCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "完成度要求",
|
||
ConditionType = ProtectionConditionType.Completion,
|
||
IsMandatory = true,
|
||
Threshold = 90.0,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
Description = "当前层必须达到足够的完成度"
|
||
});
|
||
break;
|
||
|
||
case LayerTransitionProtectionStrategy.Strict:
|
||
conditions.Add(new ProtectionCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "最小停留时间",
|
||
ConditionType = ProtectionConditionType.StayTime,
|
||
IsMandatory = true,
|
||
Threshold = 15.0,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
Description = "必须在本层停留足够时间"
|
||
});
|
||
conditions.Add(new ProtectionCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "完成度要求",
|
||
ConditionType = ProtectionConditionType.Completion,
|
||
IsMandatory = true,
|
||
Threshold = 95.0,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
Description = "当前层必须达到足够的完成度"
|
||
});
|
||
conditions.Add(new ProtectionCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "错误状态检查",
|
||
ConditionType = ProtectionConditionType.ErrorStatus,
|
||
IsMandatory = true,
|
||
Threshold = 0.0,
|
||
ComparisonOperator = ComparisonOperator.Equal,
|
||
Description = "不能存在错误状态"
|
||
});
|
||
conditions.Add(new ProtectionCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "人工干预检查",
|
||
ConditionType = ProtectionConditionType.ManualIntervention,
|
||
IsMandatory = true,
|
||
Threshold = 0.0,
|
||
ComparisonOperator = ComparisonOperator.Equal,
|
||
Description = "不能需要人工干预"
|
||
});
|
||
break;
|
||
}
|
||
|
||
return conditions;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查停留时间条件。
|
||
/// </summary>
|
||
private ProtectionConditionCheckResult CheckStayTimeCondition(int currentLayer, LayerTransitionContext context, LayerTransitionProtectionConfig config)
|
||
{
|
||
var layerKey = $"{context.ProductTypeCode}_{context.SessionId}_{currentLayer}";
|
||
var entryTime = _layerEntryTimes.GetValueOrDefault(layerKey, DateTime.UtcNow);
|
||
var stayTime = (DateTime.UtcNow - entryTime).TotalSeconds;
|
||
|
||
var isMet = stayTime >= config.MinimumStayTimeSeconds;
|
||
|
||
return new ProtectionConditionCheckResult
|
||
{
|
||
ConditionName = "最小停留时间",
|
||
IsConditionMet = isMet,
|
||
ConditionType = ProtectionConditionType.StayTime,
|
||
ActualValue = stayTime,
|
||
Threshold = config.MinimumStayTimeSeconds,
|
||
Details = isMet
|
||
? $"停留时间{stayTime:F1}秒满足最小要求{config.MinimumStayTimeSeconds}秒"
|
||
: $"停留时间{stayTime:F1}秒不满足最小要求{config.MinimumStayTimeSeconds}秒"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查完成度条件。
|
||
/// </summary>
|
||
private ProtectionConditionCheckResult CheckCompletionCondition(LayerTransitionContext context, LayerTransitionProtectionConfig config)
|
||
{
|
||
var isMet = context.CompletionPercentage >= config.CompletionThreshold;
|
||
|
||
return new ProtectionConditionCheckResult
|
||
{
|
||
ConditionName = "完成度要求",
|
||
IsConditionMet = isMet,
|
||
ConditionType = ProtectionConditionType.Completion,
|
||
ActualValue = context.CompletionPercentage,
|
||
Threshold = config.CompletionThreshold,
|
||
Details = isMet
|
||
? $"完成度{context.CompletionPercentage:F1}%满足要求{config.CompletionThreshold:F1}%"
|
||
: $"完成度{context.CompletionPercentage:F1}%不满足要求{config.CompletionThreshold:F1}%"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查错误状态条件。
|
||
/// </summary>
|
||
private ProtectionConditionCheckResult CheckErrorCondition(LayerTransitionContext context, LayerTransitionProtectionConfig config)
|
||
{
|
||
var isMet = !context.HasErrors;
|
||
|
||
return new ProtectionConditionCheckResult
|
||
{
|
||
ConditionName = "错误状态检查",
|
||
IsConditionMet = isMet,
|
||
ConditionType = ProtectionConditionType.ErrorStatus,
|
||
ActualValue = context.HasErrors ? 1 : 0,
|
||
Threshold = 0,
|
||
Details = isMet
|
||
? "无错误状态,满足要求"
|
||
: $"存在错误状态:{string.Join("; ", context.ErrorMessages)}"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查人工干预条件。
|
||
/// </summary>
|
||
private ProtectionConditionCheckResult CheckManualInterventionCondition(LayerTransitionContext context, LayerTransitionProtectionConfig config)
|
||
{
|
||
var isMet = !context.RequiresManualIntervention;
|
||
|
||
return new ProtectionConditionCheckResult
|
||
{
|
||
ConditionName = "人工干预检查",
|
||
IsConditionMet = isMet,
|
||
ConditionType = ProtectionConditionType.ManualIntervention,
|
||
ActualValue = context.RequiresManualIntervention ? 1 : 0,
|
||
Threshold = 0,
|
||
Details = isMet
|
||
? "无需人工干预,满足要求"
|
||
: $"需要人工干预:{context.ManualInterventionReason}"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查转换权限。
|
||
/// </summary>
|
||
private async Task<Result<bool>> CheckTransitionPermissionAsync(LayerTransitionRequest request, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
// 简化处理:总是允许转换
|
||
return Result<bool>.Success(true);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算平均停留时间。
|
||
/// </summary>
|
||
private double CalculateAverageStayTime(int layerNumber, IReadOnlyList<LayerTransitionHistory> transitions)
|
||
{
|
||
var stayTimes = new List<double>();
|
||
|
||
foreach (var transition in transitions)
|
||
{
|
||
var entryKey = $"{transition.ProductTypeCode}_{transition.SessionId}_{layerNumber}";
|
||
if (_layerEntryTimes.TryGetValue(entryKey, out var entryTime))
|
||
{
|
||
var stayTime = (transition.TransitionTimeUtc - entryTime).TotalSeconds;
|
||
stayTimes.Add(stayTime);
|
||
}
|
||
}
|
||
|
||
return stayTimes.Any() ? stayTimes.Average() : 0.0;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
#endif
|