511 lines
22 KiB
C#
511 lines
22 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// 运行时部署快照配置选项。
|
|
/// </summary>
|
|
public class RuntimeDeploymentSnapshotOptions
|
|
{
|
|
/// <summary>
|
|
/// 快照基础路径。
|
|
/// </summary>
|
|
public string BasePath { get; set; } = "RuntimeDeploymentSnapshots";
|
|
}
|
|
|
|
/// <summary>
|
|
/// 运行时部署快照应用服务实现。
|
|
/// </summary>
|
|
public class RuntimeDeploymentSnapshotAppService : IRuntimeDeploymentSnapshotAppService
|
|
{
|
|
private readonly ILogger<RuntimeDeploymentSnapshotAppService> _logger;
|
|
private readonly RuntimeDeploymentSnapshotOptions _options;
|
|
private readonly string _basePath;
|
|
|
|
/// <summary>
|
|
/// 初始化运行时部署快照应用服务。
|
|
/// </summary>
|
|
/// <param name="logger">日志记录器。</param>
|
|
/// <param name="options">配置选项。</param>
|
|
public RuntimeDeploymentSnapshotAppService(
|
|
ILogger<RuntimeDeploymentSnapshotAppService> logger,
|
|
IOptions<RuntimeDeploymentSnapshotOptions> options)
|
|
{
|
|
_logger = logger;
|
|
_options = options.Value;
|
|
_basePath = _options.BasePath ?? "RuntimeDeploymentSnapshots";
|
|
|
|
// 确保基础目录存在
|
|
if (!Directory.Exists(_basePath))
|
|
{
|
|
Directory.CreateDirectory(_basePath);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建运行时部署快照。
|
|
/// </summary>
|
|
public async Task<Result<CreateRuntimeDeploymentSnapshotResultDto>> 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<CreateRuntimeDeploymentSnapshotResultDto>.Success(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var traceId = Guid.NewGuid().ToString("N");
|
|
_logger.LogError(ex, "创建运行时部署快照失败。TraceId: {TraceId}", traceId);
|
|
return Result<CreateRuntimeDeploymentSnapshotResultDto>.FailWithTrace(
|
|
"CREATE_SNAPSHOT_FAILED", "创建运行时部署快照失败。", traceId, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取运行时部署快照详情。
|
|
/// </summary>
|
|
public async Task<Result<RuntimeDeploymentSnapshotDetailDto>> 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<RuntimeDeploymentSnapshotDetailDto>.Fail("SNAPSHOT_NOT_FOUND", "快照不存在。");
|
|
}
|
|
|
|
var snapshotJson = await File.ReadAllTextAsync(snapshotFile, cancellationToken);
|
|
var snapshot = JsonSerializer.Deserialize<RuntimeDeploymentSnapshot>(snapshotJson);
|
|
|
|
if (snapshot == null)
|
|
{
|
|
return Result<RuntimeDeploymentSnapshotDetailDto>.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<RuntimeDeploymentSnapshotDetailDto>.Success(detailDto);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var traceId = Guid.NewGuid().ToString("N");
|
|
_logger.LogError(ex, "获取运行时部署快照详情失败。TraceId: {TraceId}", traceId);
|
|
return Result<RuntimeDeploymentSnapshotDetailDto>.FailWithTrace(
|
|
"GET_SNAPSHOT_FAILED", "获取运行时部署快照详情失败。", traceId, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取运行时部署快照列表。
|
|
/// </summary>
|
|
public async Task<Result<IReadOnlyList<RuntimeDeploymentSnapshotSummaryDto>>> GetSnapshotsAsync(
|
|
Guid? productTypeId = null,
|
|
RuntimeDeploymentSnapshotStatus? status = null,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var snapshots = new List<RuntimeDeploymentSnapshotSummaryDto>();
|
|
|
|
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<RuntimeDeploymentSnapshot>(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<IReadOnlyList<RuntimeDeploymentSnapshotSummaryDto>>.Success(snapshots);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var traceId = Guid.NewGuid().ToString("N");
|
|
_logger.LogError(ex, "获取运行时部署快照列表失败。TraceId: {TraceId}", traceId);
|
|
return Result<IReadOnlyList<RuntimeDeploymentSnapshotSummaryDto>>.FailWithTrace(
|
|
"GET_SNAPSHOTS_FAILED", "获取运行时部署快照列表失败。", traceId, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 激活运行时部署快照。
|
|
/// </summary>
|
|
public async Task<Result<ActivateRuntimeDeploymentSnapshotResultDto>> ActivateSnapshotAsync(
|
|
ActivateRuntimeDeploymentSnapshotCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("开始激活运行时部署快照: {SnapshotId}", command.SnapshotId);
|
|
|
|
// 获取目标快照
|
|
var snapshotResult = await GetSnapshotAsync(command.SnapshotId, cancellationToken);
|
|
if (!snapshotResult.Succeeded)
|
|
{
|
|
return Result<ActivateRuntimeDeploymentSnapshotResultDto>.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<ActivateRuntimeDeploymentSnapshotResultDto>.Success(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var traceId = Guid.NewGuid().ToString("N");
|
|
_logger.LogError(ex, "激活运行时部署快照失败。TraceId: {TraceId}", traceId);
|
|
return Result<ActivateRuntimeDeploymentSnapshotResultDto>.FailWithTrace(
|
|
"ACTIVATE_SNAPSHOT_FAILED", "激活运行时部署快照失败。", traceId, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 回滚到运行时部署快照。
|
|
/// </summary>
|
|
public async Task<Result<RollbackToRuntimeDeploymentSnapshotResultDto>> RollbackToSnapshotAsync(
|
|
RollbackToRuntimeDeploymentSnapshotCommand command,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
_logger.LogInformation("开始回滚到运行时部署快照: {TargetSnapshotId}", command.TargetSnapshotId);
|
|
|
|
// 获取目标快照
|
|
var targetSnapshotResult = await GetSnapshotAsync(command.TargetSnapshotId, cancellationToken);
|
|
if (!targetSnapshotResult.Succeeded)
|
|
{
|
|
return Result<RollbackToRuntimeDeploymentSnapshotResultDto>.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<RollbackToRuntimeDeploymentSnapshotResultDto>.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<RollbackToRuntimeDeploymentSnapshotResultDto>.Success(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
var traceId = Guid.NewGuid().ToString("N");
|
|
_logger.LogError(ex, "回滚到运行时部署快照失败。TraceId: {TraceId}", traceId);
|
|
return Result<RollbackToRuntimeDeploymentSnapshotResultDto>.FailWithTrace(
|
|
"ROLLBACK_SNAPSHOT_FAILED", "回滚到运行时部署快照失败。", traceId, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取当前激活的快照。
|
|
/// </summary>
|
|
public async Task<Result<RuntimeDeploymentSnapshotDetailDto>> GetCurrentActivatedSnapshotAsync(
|
|
Guid productTypeId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
try
|
|
{
|
|
var snapshotsResult = await GetSnapshotsAsync(productTypeId, RuntimeDeploymentSnapshotStatus.Activated, cancellationToken);
|
|
if (!snapshotsResult.Succeeded)
|
|
{
|
|
return Result<RuntimeDeploymentSnapshotDetailDto>.Fail(snapshotsResult.Code, snapshotsResult.Message);
|
|
}
|
|
|
|
var activatedSnapshots = snapshotsResult.Data!;
|
|
if (!activatedSnapshots.Any())
|
|
{
|
|
return Result<RuntimeDeploymentSnapshotDetailDto>.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<RuntimeDeploymentSnapshotDetailDto>.FailWithTrace(
|
|
"GET_CURRENT_SNAPSHOT_FAILED", "获取当前激活的快照失败。", traceId, ex.Message);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取快照历史记录。
|
|
/// </summary>
|
|
public async Task<Result<IReadOnlyList<RuntimeDeploymentSnapshotSummaryDto>>> GetSnapshotHistoryAsync(
|
|
Guid productTypeId,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
return await GetSnapshotsAsync(productTypeId, null, cancellationToken);
|
|
}
|
|
|
|
#region 辅助方法
|
|
|
|
/// <summary>
|
|
/// 停用当前激活的快照。
|
|
/// </summary>
|
|
private async Task<Guid?> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 停用指定快照。
|
|
/// </summary>
|
|
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<RuntimeDeploymentSnapshot>(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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取当前激活的快照ID。
|
|
/// </summary>
|
|
private async Task<Guid?> 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 保存快照。
|
|
/// </summary>
|
|
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
|
|
}
|