Files
OrpaonVision/OrpaonVision.SiteApp/Runtime/Services/ProductSwitchManagerService.cs
2026-04-06 22:04:05 +08:00

463 lines
17 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OrpaonVision.Core.Results;
using OrpaonVision.Core.Security;
using OrpaonVision.Model.Configuration;
using OrpaonVision.Model.Security;
using OrpaonVision.SiteApp.Runtime.Contracts;
using OrpaonVision.SiteApp.Runtime.Options;
namespace OrpaonVision.SiteApp.Runtime.Services;
/// <summary>
/// 机种切换管理服务。
/// </summary>
public sealed class ProductSwitchManagerService : IProductSwitchManagerService
{
private readonly ILogger<ProductSwitchManagerService> _logger;
private readonly IProductPermissionService _permissionService;
private readonly IRuntimeStateMachineService _stateMachineService;
private readonly RuntimeOptions _runtimeOptions;
private readonly object _switchLock = new();
private Guid? _currentProductTypeId;
private ProductTypeModel? _currentProduct;
private DateTime _lastSwitchTime;
private readonly List<ProductSwitchRecordModel> _recentSwitches = new();
/// <summary>
/// 构造函数。
/// </summary>
public ProductSwitchManagerService(
ILogger<ProductSwitchManagerService> logger,
IProductPermissionService permissionService,
IRuntimeStateMachineService stateMachineService,
IOptions<RuntimeOptions> runtimeOptions)
{
_logger = logger;
_permissionService = permissionService;
_stateMachineService = stateMachineService;
_runtimeOptions = runtimeOptions.Value;
_lastSwitchTime = DateTime.UtcNow;
}
/// <summary>
/// 当前机种ID。
/// </summary>
public Guid? CurrentProductTypeId => _currentProductTypeId;
/// <summary>
/// 当前机种信息。
/// </summary>
public ProductTypeModel? CurrentProduct => _currentProduct;
/// <inheritdoc />
public async Task<Result<ProductSwitchResult>> SwitchProductAsync(Guid userId, string userName, string userRole, Guid targetProductTypeId, string switchReason, bool isForced = false, string? forcedReason = null, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("开始机种切换: {UserId}, {UserName}, {TargetProductTypeId}, 强制: {IsForced}",
userId, userName, targetProductTypeId, isForced);
lock (_switchLock)
{
// 检查切换频率限制
var switchFrequencyResult = CheckSwitchFrequency();
if (!switchFrequencyResult.Succeeded)
{
return switchFrequencyResult;
}
// 检查运行时状态
var runtimeStateResult = CheckRuntimeState();
if (!runtimeStateResult.Succeeded)
{
return runtimeStateResult;
}
}
// 验证切换权限
var validationResult = await _permissionService.ValidateSwitchPermissionAsync(
userId, _currentProductTypeId, targetProductTypeId, isForced, cancellationToken);
if (!validationResult.Succeeded)
{
return Result<ProductSwitchResult>.Fail(validationResult.Code, validationResult.Message, validationResult.Errors.ToArray());
}
if (validationResult.Data == null || !validationResult.Data.CanSwitch)
{
return Result<ProductSwitchResult>.Fail("SWITCH_NOT_ALLOWED",
$"不允许切换机种: {string.Join(", ", validationResult.Data?.Errors ?? new List<string>())}");
}
// 执行切换
var switchResult = await ExecuteSwitchAsync(
userId, userName, userRole, targetProductTypeId, switchReason, isForced, forcedReason, validationResult.Data, cancellationToken);
return switchResult;
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "机种切换失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "SWITCH_PRODUCT_FAILED", "机种切换失败。", traceId);
return Result<ProductSwitchResult>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<ProductTypeModel>> GetCurrentProductAsync(CancellationToken cancellationToken = default)
{
try
{
if (_currentProduct == null)
{
return Result<ProductTypeModel>.Fail("NO_CURRENT_PRODUCT", "当前没有选中的机种");
}
return Result<ProductTypeModel>.Success(_currentProduct);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取当前机种失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_CURRENT_PRODUCT_FAILED", "获取当前机种失败。", traceId);
return Result<ProductTypeModel>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<IReadOnlyList<ProductSwitchRecordModel>>> GetRecentSwitchesAsync(int count = 10, CancellationToken cancellationToken = default)
{
try
{
var recentSwitches = _recentSwitches
.OrderByDescending(s => s.SwitchedAtUtc)
.Take(count)
.ToList();
return Result<IReadOnlyList<ProductSwitchRecordModel>>.Success(recentSwitches);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "获取最近切换记录失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "GET_RECENT_SWITCHES_FAILED", "获取最近切换记录失败。", traceId);
return Result<IReadOnlyList<ProductSwitchRecordModel>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<bool>> CanSwitchProductAsync(Guid userId, Guid targetProductTypeId, bool isForced = false, CancellationToken cancellationToken = default)
{
try
{
// 检查基本条件
var frequencyResult = CheckSwitchFrequency();
if (!frequencyResult.Succeeded)
{
return Result<bool>.Success(false);
}
var runtimeStateResult = CheckRuntimeState();
if (!runtimeStateResult.Succeeded)
{
return Result<bool>.Success(false);
}
// 验证权限
var validationResult = await _permissionService.ValidateSwitchPermissionAsync(
userId, _currentProductTypeId, targetProductTypeId, isForced, cancellationToken);
return Result<bool>.Success(validationResult.Data?.CanSwitch ?? false);
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "检查机种切换权限失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "CAN_SWITCH_FAILED", "检查机种切换权限失败。", traceId);
return Result<bool>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result> InitializeProductAsync(Guid productTypeId, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("初始化机种: {ProductTypeId}", productTypeId);
// 获取机种信息
var product = await GetProductTypeAsync(productTypeId, cancellationToken);
if (product == null)
{
return Result.Fail("PRODUCT_NOT_FOUND", "机种不存在");
}
lock (_switchLock)
{
_currentProductTypeId = productTypeId;
_currentProduct = product;
_lastSwitchTime = DateTime.UtcNow;
}
_logger.LogInformation("机种初始化成功: {ProductCode}", product.Code);
return Result.Success();
}
catch (Exception ex)
{
var traceId = Guid.NewGuid().ToString("N");
_logger.LogError(ex, "初始化机种失败。TraceId: {TraceId}", traceId);
var result = Result.FromException(ex, "INITIALIZE_PRODUCT_FAILED", "初始化机种失败。", traceId);
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
#region
/// <summary>
/// 检查切换频率限制。
/// </summary>
private Result<ProductSwitchResult> CheckSwitchFrequency()
{
var minSwitchInterval = TimeSpan.FromMinutes(_runtimeOptions.MinSwitchIntervalMinutes);
var timeSinceLastSwitch = DateTime.UtcNow - _lastSwitchTime;
if (timeSinceLastSwitch < minSwitchInterval)
{
var remainingTime = minSwitchInterval - timeSinceLastSwitch;
return Result<ProductSwitchResult>.Fail("SWITCH_TOO_FREQUENT",
$"切换过于频繁,请等待 {remainingTime.TotalMinutes:F1} 分钟后再试");
}
return Result<ProductSwitchResult>.Success(new ProductSwitchResult());
}
/// <summary>
/// 检查运行时状态。
/// </summary>
private Result<ProductSwitchResult> CheckRuntimeState()
{
var snapshot = _stateMachineService.GetSnapshot();
// 如果正在运行中,不允许切换
if (string.Equals(snapshot.StateText, "Running", StringComparison.OrdinalIgnoreCase)
|| string.Equals(snapshot.StateText, "运行中", StringComparison.OrdinalIgnoreCase))
{
return Result<ProductSwitchResult>.Fail("RUNTIME_RUNNING",
"运行时正在运行中,请先暂停或停止运行时再切换机种");
}
// 如果已完成,需要重置
if (snapshot.CurrentLayer > snapshot.TotalLayers)
{
return Result<ProductSwitchResult>.Fail("RUNTIME_COMPLETED",
"当前批次已完成,请重置运行时再切换机种");
}
return Result<ProductSwitchResult>.Success(new ProductSwitchResult());
}
/// <summary>
/// 执行机种切换。
/// </summary>
private async Task<Result<ProductSwitchResult>> ExecuteSwitchAsync(
Guid userId, string userName, string userRole, Guid targetProductTypeId,
string switchReason, bool isForced, string? forcedReason,
ProductSwitchValidationResult validationResult, CancellationToken cancellationToken)
{
// 获取目标机种信息
var targetProduct = await GetProductTypeAsync(targetProductTypeId, cancellationToken);
if (targetProduct == null)
{
return Result<ProductSwitchResult>.Fail("TARGET_PRODUCT_NOT_FOUND", "目标机种不存在");
}
var sourceProductTypeId = _currentProductTypeId;
var sourceProductCode = _currentProduct?.Code;
// 记录切换
var switchRecord = await _permissionService.RecordSwitchAsync(
userId, userName, userRole, sourceProductTypeId, targetProductTypeId,
isForced ? ProductSwitchType.Forced : ProductSwitchType.Normal,
switchReason, isForced, forcedReason, cancellationToken);
if (!switchRecord.Succeeded)
{
return Result<ProductSwitchResult>.Fail(switchRecord.Code, switchRecord.Message, switchRecord.Errors.ToArray());
}
// 更新当前机种
lock (_switchLock)
{
_currentProductTypeId = targetProductTypeId;
_currentProduct = targetProduct;
_lastSwitchTime = DateTime.UtcNow;
// 添加到最近切换记录
if (switchRecord.Data != null)
{
_recentSwitches.Add(switchRecord.Data);
}
// 保持最近100条记录
if (_recentSwitches.Count > 100)
{
_recentSwitches.RemoveAt(0);
}
}
// 重置运行时状态
_stateMachineService.Reset();
var result = new ProductSwitchResult
{
Success = true,
SourceProductCode = sourceProductCode,
TargetProductCode = targetProduct.Code,
SwitchType = isForced ? ProductSwitchType.Forced : ProductSwitchType.Normal,
SwitchedAtUtc = DateTime.UtcNow,
SwitchRecordId = switchRecord.Data?.Id ?? Guid.Empty,
Warnings = validationResult.Warnings
};
_logger.LogInformation("机种切换成功: {SourceProductCode} -> {TargetProductCode}",
sourceProductCode, targetProduct.Code);
return Result<ProductSwitchResult>.Success(result);
}
/// <summary>
/// 获取机种信息。
/// </summary>
private async Task<ProductTypeModel?> GetProductTypeAsync(Guid productTypeId, CancellationToken cancellationToken = default)
{
// 模拟实现,实际应该从数据库或缓存获取
await Task.Delay(10, cancellationToken);
return new ProductTypeModel
{
Id = productTypeId,
Code = productTypeId.ToString()[..8], // 取前8位作为编码
Name = $"机种 {productTypeId.ToString()[..8]}",
Status = ProductTypeStatus.Published,
TotalLayers = 3,
IsPublished = true,
CreatedAtUtc = DateTime.UtcNow.AddDays(-30),
CreatedBy = "System"
};
}
#endregion
}
/// <summary>
/// 机种切换管理服务接口。
/// </summary>
public interface IProductSwitchManagerService
{
/// <summary>
/// 当前机种ID。
/// </summary>
Guid? CurrentProductTypeId { get; }
/// <summary>
/// 当前机种信息。
/// </summary>
ProductTypeModel? CurrentProduct { get; }
/// <summary>
/// 切换机种。
/// </summary>
/// <param name="userId">用户ID。</param>
/// <param name="userName">用户姓名。</param>
/// <param name="userRole">用户角色。</param>
/// <param name="targetProductTypeId">目标机种ID。</param>
/// <param name="switchReason">切换原因。</param>
/// <param name="isForced">是否强制切换。</param>
/// <param name="forcedReason">强制切换原因。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>切换结果。</returns>
Task<Result<ProductSwitchResult>> SwitchProductAsync(Guid userId, string userName, string userRole, Guid targetProductTypeId, string switchReason, bool isForced = false, string? forcedReason = null, CancellationToken cancellationToken = default);
/// <summary>
/// 获取当前机种。
/// </summary>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>当前机种信息。</returns>
Task<Result<ProductTypeModel>> GetCurrentProductAsync(CancellationToken cancellationToken = default);
/// <summary>
/// 获取最近切换记录。
/// </summary>
/// <param name="count">记录数量。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>切换记录列表。</returns>
Task<Result<IReadOnlyList<ProductSwitchRecordModel>>> GetRecentSwitchesAsync(int count = 10, CancellationToken cancellationToken = default);
/// <summary>
/// 检查是否可以切换机种。
/// </summary>
/// <param name="userId">用户ID。</param>
/// <param name="targetProductTypeId">目标机种ID。</param>
/// <param name="isForced">是否强制切换。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>是否可以切换。</returns>
Task<Result<bool>> CanSwitchProductAsync(Guid userId, Guid targetProductTypeId, bool isForced = false, CancellationToken cancellationToken = default);
/// <summary>
/// 初始化机种。
/// </summary>
/// <param name="productTypeId">机种ID。</param>
/// <param name="cancellationToken">取消令牌。</param>
/// <returns>初始化结果。</returns>
Task<Result> InitializeProductAsync(Guid productTypeId, CancellationToken cancellationToken = default);
}
/// <summary>
/// 机种切换结果。
/// </summary>
public sealed class ProductSwitchResult
{
/// <summary>
/// 是否成功。
/// </summary>
public bool Success { get; init; }
/// <summary>
/// 源机种编码。
/// </summary>
public string? SourceProductCode { get; init; }
/// <summary>
/// 目标机种编码。
/// </summary>
public string? TargetProductCode { get; init; }
/// <summary>
/// 切换类型。
/// </summary>
public ProductSwitchType SwitchType { get; init; }
/// <summary>
/// 切换时间UTC
/// </summary>
public DateTime SwitchedAtUtc { get; init; }
/// <summary>
/// 切换记录ID。
/// </summary>
public Guid SwitchRecordId { get; init; }
/// <summary>
/// 警告信息。
/// </summary>
public List<string> Warnings { get; init; } = new();
/// <summary>
/// 错误信息。
/// </summary>
public List<string> Errors { get; init; } = new();
}