版本260406
This commit is contained in:
@@ -0,0 +1,490 @@
|
||||
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)
|
||||
{
|
||||
snapshot.Status = RuntimeDeploymentSnapshotStatus.RolledBack;
|
||||
snapshot.RolledBackAtUtc = DateTime.UtcNow;
|
||||
snapshot.RolledBackBy = "System"; // 系统自动回滚
|
||||
|
||||
await SaveSnapshotAsync(snapshot, 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
|
||||
}
|
||||
Reference in New Issue
Block a user