using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrpaonVision.Core.Results; using OrpaonVision.Core.Training; using OrpaonVision.Core.Training.Contracts; using OrpaonVision.Core.Training.Contracts.Commands; using System.IO; using System.Text.Json; namespace OrpaonVision.ConfigApp.Infrastructure.Services; /// /// 运行时部署快照配置选项。 /// public class RuntimeDeploymentSnapshotOptions { /// /// 快照基础路径。 /// public string BasePath { get; set; } = "RuntimeDeploymentSnapshots"; } /// /// 运行时部署快照应用服务实现。 /// public class RuntimeDeploymentSnapshotAppService : IRuntimeDeploymentSnapshotAppService { private readonly ILogger _logger; private readonly RuntimeDeploymentSnapshotOptions _options; private readonly string _basePath; /// /// 初始化运行时部署快照应用服务。 /// /// 日志记录器。 /// 配置选项。 public RuntimeDeploymentSnapshotAppService( ILogger logger, IOptions options) { _logger = logger; _options = options.Value; _basePath = _options.BasePath ?? "RuntimeDeploymentSnapshots"; // 确保基础目录存在 if (!Directory.Exists(_basePath)) { Directory.CreateDirectory(_basePath); } } /// /// 创建运行时部署快照。 /// public async Task> CreateSnapshotAsync( CreateRuntimeDeploymentSnapshotCommand command, CancellationToken cancellationToken = default) { try { _logger.LogInformation("开始创建运行时部署快照: {Name}", command.Name); var snapshotId = Guid.NewGuid(); var snapshotDir = Path.Combine(_basePath, snapshotId.ToString("N")); // 创建快照目录 Directory.CreateDirectory(snapshotDir); // 创建快照对象 var snapshot = new RuntimeDeploymentSnapshot { Id = snapshotId, Name = command.Name, Description = command.Description, ProductTypeId = command.ProductTypeId, RuleVersionId = command.RuleVersionId, ModelPackageVersionId = command.ModelPackageVersionId, RuntimeParameterVersionId = command.RuntimeParameterVersionId, Status = RuntimeDeploymentSnapshotStatus.Draft, CreatedBy = command.CreatedBy, MetadataJson = command.MetadataJson }; // 保存快照文件 var snapshotFile = Path.Combine(snapshotDir, "snapshot.json"); var snapshotJson = JsonSerializer.Serialize(snapshot, new JsonSerializerOptions { WriteIndented = true }); await File.WriteAllTextAsync(snapshotFile, snapshotJson, cancellationToken); var result = new CreateRuntimeDeploymentSnapshotResultDto { SnapshotId = snapshotId, Name = command.Name, Status = RuntimeDeploymentSnapshotOperationStatus.Succeeded, Message = "快照创建成功", CreatedAtUtc = DateTime.UtcNow, CreatedBy = command.CreatedBy }; _logger.LogInformation("成功创建运行时部署快照: {SnapshotId}", snapshotId); return Result.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "创建运行时部署快照失败。TraceId: {TraceId}", traceId); return Result.FailWithTrace( "CREATE_SNAPSHOT_FAILED", "创建运行时部署快照失败。", traceId, ex.Message); } } /// /// 获取运行时部署快照详情。 /// public async Task> GetSnapshotAsync( Guid snapshotId, CancellationToken cancellationToken = default) { try { var snapshotDir = Path.Combine(_basePath, snapshotId.ToString("N")); var snapshotFile = Path.Combine(snapshotDir, "snapshot.json"); if (!File.Exists(snapshotFile)) { return Result.Fail("SNAPSHOT_NOT_FOUND", "快照不存在。"); } var snapshotJson = await File.ReadAllTextAsync(snapshotFile, cancellationToken); var snapshot = JsonSerializer.Deserialize(snapshotJson); if (snapshot == null) { return Result.Fail("INVALID_SNAPSHOT", "快照数据无效。"); } var detailDto = new RuntimeDeploymentSnapshotDetailDto { Id = snapshot.Id, Name = snapshot.Name, Description = snapshot.Description, ProductTypeId = snapshot.ProductTypeId, ProductTypeCode = snapshot.ProductTypeCode, RuleVersionId = snapshot.RuleVersionId, RuleVersionNo = snapshot.RuleVersionNo, ModelPackageVersionId = snapshot.ModelPackageVersionId, ModelPackageVersionNo = snapshot.ModelPackageVersionNo, RuntimeParameterVersionId = snapshot.RuntimeParameterVersionId, RuntimeParameterVersionNo = snapshot.RuntimeParameterVersionNo, Status = snapshot.Status, CreatedAtUtc = snapshot.CreatedAtUtc, CreatedBy = snapshot.CreatedBy, ActivatedAtUtc = snapshot.ActivatedAtUtc, ActivatedBy = snapshot.ActivatedBy, RolledBackAtUtc = snapshot.RolledBackAtUtc, RolledBackBy = snapshot.RolledBackBy, MetadataJson = snapshot.MetadataJson }; return Result.Success(detailDto); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取运行时部署快照详情失败。TraceId: {TraceId}", traceId); return Result.FailWithTrace( "GET_SNAPSHOT_FAILED", "获取运行时部署快照详情失败。", traceId, ex.Message); } } /// /// 获取运行时部署快照列表。 /// public async Task>> GetSnapshotsAsync( Guid? productTypeId = null, RuntimeDeploymentSnapshotStatus? status = null, CancellationToken cancellationToken = default) { try { var snapshots = new List(); foreach (var snapshotDir in Directory.GetDirectories(_basePath)) { var snapshotFile = Path.Combine(snapshotDir, "snapshot.json"); if (!File.Exists(snapshotFile)) continue; try { var snapshotJson = await File.ReadAllTextAsync(snapshotFile, cancellationToken); var snapshot = JsonSerializer.Deserialize(snapshotJson); if (snapshot == null) continue; // 应用筛选条件 if (productTypeId.HasValue && snapshot.ProductTypeId != productTypeId.Value) continue; if (status.HasValue && snapshot.Status != status.Value) continue; var summaryDto = new RuntimeDeploymentSnapshotSummaryDto { Id = snapshot.Id, Name = snapshot.Name, Description = snapshot.Description, ProductTypeCode = snapshot.ProductTypeCode, RuleVersionNo = snapshot.RuleVersionNo, ModelPackageVersionNo = snapshot.ModelPackageVersionNo, RuntimeParameterVersionNo = snapshot.RuntimeParameterVersionNo, Status = snapshot.Status, CreatedAtUtc = snapshot.CreatedAtUtc, CreatedBy = snapshot.CreatedBy, ActivatedAtUtc = snapshot.ActivatedAtUtc, ActivatedBy = snapshot.ActivatedBy }; snapshots.Add(summaryDto); } catch (Exception ex) { _logger.LogWarning(ex, "读取快照文件失败: {SnapshotFile}", snapshotFile); } } // 按创建时间降序排序 snapshots = snapshots.OrderByDescending(s => s.CreatedAtUtc).ToList(); return Result>.Success(snapshots); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取运行时部署快照列表失败。TraceId: {TraceId}", traceId); return Result>.FailWithTrace( "GET_SNAPSHOTS_FAILED", "获取运行时部署快照列表失败。", traceId, ex.Message); } } /// /// 激活运行时部署快照。 /// public async Task> ActivateSnapshotAsync( ActivateRuntimeDeploymentSnapshotCommand command, CancellationToken cancellationToken = default) { try { _logger.LogInformation("开始激活运行时部署快照: {SnapshotId}", command.SnapshotId); // 获取目标快照 var snapshotResult = await GetSnapshotAsync(command.SnapshotId, cancellationToken); if (!snapshotResult.Succeeded) { return Result.Fail(snapshotResult.Code, snapshotResult.Message); } var snapshot = snapshotResult.Data!; // 停用当前激活的快照(同一机种) var previousSnapshotId = await DeactivateCurrentSnapshotAsync(snapshot.ProductTypeId, cancellationToken); // 创建新的快照对象用于更新 var updatedSnapshot = new RuntimeDeploymentSnapshot { Id = snapshot.Id, Name = snapshot.Name, Description = snapshot.Description, ProductTypeId = snapshot.ProductTypeId, ProductTypeCode = snapshot.ProductTypeCode, RuleVersionId = snapshot.RuleVersionId, RuleVersionNo = snapshot.RuleVersionNo, ModelPackageVersionId = snapshot.ModelPackageVersionId, ModelPackageVersionNo = snapshot.ModelPackageVersionNo, RuntimeParameterVersionId = snapshot.RuntimeParameterVersionId, RuntimeParameterVersionNo = snapshot.RuntimeParameterVersionNo, Status = RuntimeDeploymentSnapshotStatus.Activated, CreatedAtUtc = snapshot.CreatedAtUtc, CreatedBy = snapshot.CreatedBy, ActivatedAtUtc = DateTime.UtcNow, ActivatedBy = command.ActivatedBy, RolledBackAtUtc = snapshot.RolledBackAtUtc, RolledBackBy = snapshot.RolledBackBy, MetadataJson = snapshot.MetadataJson }; // 保存更新后的快照 await SaveSnapshotAsync(updatedSnapshot, cancellationToken); var result = new ActivateRuntimeDeploymentSnapshotResultDto { SnapshotId = command.SnapshotId, Status = RuntimeDeploymentSnapshotOperationStatus.Succeeded, Message = "快照激活成功", PreviousSnapshotId = previousSnapshotId, ActivatedAtUtc = DateTime.UtcNow, ActivatedBy = command.ActivatedBy }; _logger.LogInformation("成功激活运行时部署快照: {SnapshotId}", command.SnapshotId); return Result.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "激活运行时部署快照失败。TraceId: {TraceId}", traceId); return Result.FailWithTrace( "ACTIVATE_SNAPSHOT_FAILED", "激活运行时部署快照失败。", traceId, ex.Message); } } /// /// 回滚到运行时部署快照。 /// public async Task> RollbackToSnapshotAsync( RollbackToRuntimeDeploymentSnapshotCommand command, CancellationToken cancellationToken = default) { try { _logger.LogInformation("开始回滚到运行时部署快照: {TargetSnapshotId}", command.TargetSnapshotId); // 获取目标快照 var targetSnapshotResult = await GetSnapshotAsync(command.TargetSnapshotId, cancellationToken); if (!targetSnapshotResult.Succeeded) { return Result.Fail(targetSnapshotResult.Code, targetSnapshotResult.Message); } var targetSnapshot = targetSnapshotResult.Data!; // 获取当前激活的快照 var currentSnapshotId = await GetCurrentActivatedSnapshotIdAsync(targetSnapshot.ProductTypeId, cancellationToken); // 停用当前激活的快照 if (currentSnapshotId.HasValue) { await DeactivateSnapshotAsync(currentSnapshotId.Value, cancellationToken); } // 激活目标快照 var activateCommand = new ActivateRuntimeDeploymentSnapshotCommand { SnapshotId = command.TargetSnapshotId, ActivatedBy = command.RolledBackBy, Reason = command.Reason }; var activateResult = await ActivateSnapshotAsync(activateCommand, cancellationToken); if (!activateResult.Succeeded) { return Result.Fail(activateResult.Code, activateResult.Message); } var result = new RollbackToRuntimeDeploymentSnapshotResultDto { TargetSnapshotId = command.TargetSnapshotId, PreviousSnapshotId = currentSnapshotId, Status = RuntimeDeploymentSnapshotOperationStatus.Succeeded, Message = "回滚成功", RolledBackAtUtc = DateTime.UtcNow, RolledBackBy = command.RolledBackBy }; _logger.LogInformation("成功回滚到运行时部署快照: {TargetSnapshotId}", command.TargetSnapshotId); return Result.Success(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "回滚到运行时部署快照失败。TraceId: {TraceId}", traceId); return Result.FailWithTrace( "ROLLBACK_SNAPSHOT_FAILED", "回滚到运行时部署快照失败。", traceId, ex.Message); } } /// /// 获取当前激活的快照。 /// public async Task> GetCurrentActivatedSnapshotAsync( Guid productTypeId, CancellationToken cancellationToken = default) { try { var snapshotsResult = await GetSnapshotsAsync(productTypeId, RuntimeDeploymentSnapshotStatus.Activated, cancellationToken); if (!snapshotsResult.Succeeded) { return Result.Fail(snapshotsResult.Code, snapshotsResult.Message); } var activatedSnapshots = snapshotsResult.Data!; if (!activatedSnapshots.Any()) { return Result.Fail("NO_ACTIVATED_SNAPSHOT", "没有找到激活的快照。"); } // 获取最新的激活快照 var latestSnapshot = activatedSnapshots.OrderByDescending(s => s.ActivatedAtUtc).First(); var snapshotDetailResult = await GetSnapshotAsync(latestSnapshot.Id, cancellationToken); return snapshotDetailResult; } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取当前激活的快照失败。TraceId: {TraceId}", traceId); return Result.FailWithTrace( "GET_CURRENT_SNAPSHOT_FAILED", "获取当前激活的快照失败。", traceId, ex.Message); } } /// /// 获取快照历史记录。 /// public async Task>> GetSnapshotHistoryAsync( Guid productTypeId, CancellationToken cancellationToken = default) { return await GetSnapshotsAsync(productTypeId, null, cancellationToken); } #region 辅助方法 /// /// 停用当前激活的快照。 /// private async Task DeactivateCurrentSnapshotAsync(Guid productTypeId, CancellationToken cancellationToken) { var currentSnapshotResult = await GetCurrentActivatedSnapshotAsync(productTypeId, cancellationToken); if (currentSnapshotResult.Succeeded) { var currentSnapshotId = currentSnapshotResult.Data!.Id; await DeactivateSnapshotAsync(currentSnapshotId, cancellationToken); return currentSnapshotId; } return null; } /// /// 停用指定快照。 /// private async Task DeactivateSnapshotAsync(Guid snapshotId, CancellationToken cancellationToken) { var snapshotDir = Path.Combine(_basePath, snapshotId.ToString("N")); var snapshotFile = Path.Combine(snapshotDir, "snapshot.json"); if (File.Exists(snapshotFile)) { var snapshotJson = await File.ReadAllTextAsync(snapshotFile, cancellationToken); var snapshot = JsonSerializer.Deserialize(snapshotJson); if (snapshot != null) { snapshot.Status = RuntimeDeploymentSnapshotStatus.RolledBack; snapshot.RolledBackAtUtc = DateTime.UtcNow; snapshot.RolledBackBy = "System"; // 系统自动回滚 await SaveSnapshotAsync(snapshot, cancellationToken); } } } /// /// 获取当前激活的快照ID。 /// private async Task GetCurrentActivatedSnapshotIdAsync(Guid productTypeId, CancellationToken cancellationToken) { var snapshotsResult = await GetSnapshotsAsync(productTypeId, RuntimeDeploymentSnapshotStatus.Activated, cancellationToken); if (snapshotsResult.Succeeded) { var activatedSnapshots = snapshotsResult.Data!; if (activatedSnapshots.Any()) { return activatedSnapshots.OrderByDescending(s => s.ActivatedAtUtc).First().Id; } } return null; } /// /// 保存快照。 /// private async Task SaveSnapshotAsync(RuntimeDeploymentSnapshot snapshot, CancellationToken cancellationToken) { var snapshotDir = Path.Combine(_basePath, snapshot.Id.ToString("N")); var snapshotFile = Path.Combine(snapshotDir, "snapshot.json"); var snapshotJson = JsonSerializer.Serialize(snapshot, new JsonSerializerOptions { WriteIndented = true }); await File.WriteAllTextAsync(snapshotFile, snapshotJson, cancellationToken); } #endregion }