Files
XplorePlane/XplorePlane/Services/Pipeline/PipelinePersistenceService.cs
T
2026-04-23 16:28:53 +08:00

112 lines
3.9 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using XplorePlane.Models;
namespace XplorePlane.Services
{
public class PipelinePersistenceService : IPipelinePersistenceService
{
private readonly IImageProcessingService _imageProcessingService;
private readonly string _baseDirectory;
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
Converters = { new JsonStringEnumConverter() }
};
public PipelinePersistenceService(IImageProcessingService imageProcessingService, string baseDirectory = null)
{
_imageProcessingService = imageProcessingService ?? throw new ArgumentNullException(nameof(imageProcessingService));
_baseDirectory = baseDirectory ?? Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"XplorePlane", "Pipelines");
}
public async Task SaveAsync(PipelineModel pipeline, string filePath)
{
if (pipeline == null) throw new ArgumentNullException(nameof(pipeline));
ValidatePath(filePath);
pipeline.UpdatedAt = DateTime.UtcNow;
var json = JsonSerializer.Serialize(pipeline, JsonOptions);
await File.WriteAllTextAsync(filePath, json);
}
public async Task<PipelineModel> LoadAsync(string filePath)
{
ValidatePath(filePath);
if (!File.Exists(filePath))
throw new FileNotFoundException("流水线文件不存在", filePath);
var json = await File.ReadAllTextAsync(filePath);
var model = JsonSerializer.Deserialize<PipelineModel>(json, JsonOptions)
?? throw new InvalidDataException("流水线文件格式无效");
// 白名单验证:过滤未注册的算子键
var available = _imageProcessingService.GetAvailableProcessors().ToHashSet(StringComparer.OrdinalIgnoreCase);
model.Nodes = model.Nodes
.Where(n => available.Contains(n.OperatorKey))
.OrderBy(n => n.Order)
.ToList();
// 重新编号确保 Order 连续
for (int i = 0; i < model.Nodes.Count; i++)
model.Nodes[i].Order = i;
return model;
}
public async Task<IReadOnlyList<PipelineModel>> LoadAllAsync(string directory)
{
if (string.IsNullOrWhiteSpace(directory))
directory = _baseDirectory;
ValidateDirectory(directory);
if (!Directory.Exists(directory))
return Array.Empty<PipelineModel>();
var files = Directory.GetFiles(directory, "*.xpm");
var results = new List<PipelineModel>();
foreach (var file in files)
{
try
{
var model = await LoadAsync(file);
results.Add(model);
}
catch
{
// 跳过无效文件
}
}
return results;
}
private void ValidatePath(string filePath)
{
if (string.IsNullOrWhiteSpace(filePath))
throw new ArgumentException("文件路径不能为空", nameof(filePath));
if (filePath.Contains(".."))
throw new UnauthorizedAccessException($"不允许路径遍历:{filePath}");
}
private void ValidateDirectory(string directory)
{
if (directory.Contains(".."))
throw new UnauthorizedAccessException($"不允许路径遍历:{directory}");
}
}
}