版本260406

This commit is contained in:
2026-04-06 22:04:05 +08:00
parent 7dc5e73af7
commit 0b150470be
216 changed files with 98993 additions and 33 deletions

View File

@@ -0,0 +1,462 @@
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();
}