XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEWorkflow

【WinUI】XFE基于windows的工作流

公开
关注 0 Fork 0 Star 0
UTF-8
using System.IO.Compression;
using System.Text;
using System.Text.Json;

namespace XFEWorkflow.Abstractions;

/// <summary>Portable definitions only. Inspection never loads code or starts triggers.</summary>
public static class WorkflowPackage
{
    public const long MaximumBytes = 32L * 1024 * 1024;
    public static WorkflowDefinition Inspect(string path)
    {
        if (new FileInfo(path).Length is <= 0 or > MaximumBytes) throw new InvalidDataException("工作流文件大小无效(最多 32 MiB)");
        using var file = File.OpenRead(path);
        WorkflowDefinition? workflow;
        if (Path.GetExtension(path).Equals(".json", StringComparison.OrdinalIgnoreCase)) workflow = JsonSerializer.Deserialize<WorkflowDefinition>(file, WorkflowJson.Options);
        else
        {
            using var archive = new ZipArchive(file, ZipArchiveMode.Read);
            if (archive.Entries.Count != 1 || archive.Entries[0].FullName != "workflow.json" || archive.Entries[0].Length > MaximumBytes)
                throw new InvalidDataException("工作流包必须只包含 workflow.json,且解压后不超过 32 MiB");
            using var definition = archive.Entries[0].Open();
            workflow = JsonSerializer.Deserialize<WorkflowDefinition>(definition, WorkflowJson.Options);
        }
        Validate(workflow); return workflow!;
    }
    public static void Validate(WorkflowDefinition? workflow)
    {
        if (workflow is null || workflow.SchemaVersion != 1 || !Enum.IsDefined(workflow.Kind)) throw new InvalidDataException("工作流格式不兼容");
        if (workflow.Id is null || workflow.Id.Length is < 1 or > 100 || workflow.Id.Any(c => !char.IsAsciiLetterOrDigit(c) && c is not '-' and not '_')) throw new InvalidDataException("工作流 ID 无效");
        if (string.IsNullOrWhiteSpace(workflow.Name) || workflow.Name.Length > 150 || workflow.Description is null || workflow.Description.Length > 10000)
            throw new InvalidDataException("工作流名称或描述无效");
        if (workflow.PluginVersions is null || workflow.Inputs is null || workflow.Outputs is null || workflow.InputDefaults is null || workflow.Variables is null || workflow.Code is null || workflow.Triggers is null)
            throw new InvalidDataException("工作流字段缺失");
        void Ports(List<PortDefinition> ports)
        {
            HashSet<string> names = [];
            foreach (var port in ports)
                if (port is null || string.IsNullOrWhiteSpace(port.Name) || port.Name.Length > 200 || !names.Add(port.Name) || !Enum.IsDefined(port.Type)
                    || port.Label is null || port.Description is null || port.TypeId is null || port.Aliases is null || port.Choices is null)
                    throw new InvalidDataException("工作流输入或输出端口无效");
        }
        Ports(workflow.Inputs); Ports(workflow.Outputs);
        foreach (var (id, version) in workflow.PluginVersions)
            if (!PluginPackage.IsIdentifier(id) || !SemanticVersion.TryParse(version, out _)) throw new InvalidDataException("工作流插件版本锁定无效");
        HashSet<string> ids = []; var count = 0;
        void Graph(GraphDefinition? graph, int depth)
        {
            if (depth > 32 || graph?.Nodes is null || graph.Edges is null) throw new InvalidDataException("工作流子图缺失或嵌套过深");
            HashSet<string> local = [];
            foreach (var node in graph.Nodes)
            {
                if (node is null || string.IsNullOrWhiteSpace(node.Id) || node.Id.Length > 200 || !ids.Add(node.Id) || ++count > 10000
                    || string.IsNullOrWhiteSpace(node.CapabilityId) || node.CapabilityId.Length > 200 || node.Parameters is null || node.Children is null || node.Name is null || node.Code is null)
                    throw new InvalidDataException("工作流节点无效、重复或超过 10000 个");
                local.Add(node.Id); foreach (var child in node.Children.Values) Graph(child, depth + 1);
            }
            foreach (var edge in graph.Edges)
                if (edge is null || !local.Contains(edge.FromNode) || !local.Contains(edge.ToNode) || string.IsNullOrWhiteSpace(edge.FromPort) || string.IsNullOrWhiteSpace(edge.ToPort) || !Enum.IsDefined(edge.Kind))
                    throw new InvalidDataException("工作流连线无效");
        }
        Graph(workflow.Graph, 0);
    }
    public static void Create(WorkflowDefinition workflow, string path)
    {
        Validate(workflow); var copy = WorkflowJson.Clone(workflow); copy.Enabled = false;
        var bytes = Encoding.UTF8.GetBytes(WorkflowJson.SerializeDisplay(copy));
        if (bytes.LongLength > MaximumBytes) throw new InvalidDataException("工作流定义超过 32 MiB");
        using var file = new FileStream(path, FileMode.CreateNew, FileAccess.Write);
        using var archive = new ZipArchive(file, ZipArchiveMode.Create);
        using var entry = archive.CreateEntry("workflow.json", CompressionLevel.Optimal).Open(); entry.Write(bytes);
    }
}