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)
{
// 创建新的快照对象用于更新
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.RolledBack,
CreatedAtUtc = snapshot.CreatedAtUtc,
CreatedBy = snapshot.CreatedBy,
ActivatedAtUtc = snapshot.ActivatedAtUtc,
ActivatedBy = snapshot.ActivatedBy,
RolledBackAtUtc = DateTime.UtcNow,
RolledBackBy = "System", // 系统自动回滚
MetadataJson = snapshot.MetadataJson
};
await SaveSnapshotAsync(updatedSnapshot, 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
}