112 lines
3.9 KiB
C#
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}");
|
|
}
|
|
}
|
|
}
|