You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node-RED流路径排查:C#代码无法识别指定起止节点路径问题

Node-RED流路径识别代码排查与修复

问题背景

我们部署了Node-RED服务器,需要基于现有flows.json文件生成流路径报告,要求:

  1. 路径起点为指定节点类型:kafka-consumer、kafkajs-consumer、kafka-producer、kafkajs-producer、mqtt in、mqtt out、http request
  2. 路径终点同样为上述指定节点类型之一
  3. 仅输出起始节点和结束节点的属性,无需中间节点

但编写的C#代码无法识别任何符合条件的路径,实际存在大量此类路径。示例路径:
Kafka-Flow路径示例

原代码

using Newtonsoft.Json.Linq;
 static void Main(string[] args)
    {
        string jsonFilePath = "C:\\temp\\Nodered\\flows.json";
        string activeFlowsFile = "C:\\temp\\Nodered\\activeflows.txt";
        string outputDirectory = "C:\\temp\\Nodered";

        // Parse the JSON file
        var json = File.ReadAllText(jsonFilePath);
        var nodes = JArray.Parse(json);

        // Filter active flows and write their names to activeflows.txt
        var activeFlows = new List<string>();
        foreach (var node in nodes)
        {
            if (node["type"]?.ToString() == "tab" && node["disabled"]?.ToObject<bool>() != true)
            {
                activeFlows.Add(node["label"]?.ToString());
            }
        }

        Directory.CreateDirectory(outputDirectory);
        File.WriteAllLines(activeFlowsFile, activeFlows);

        // Process each active flow
        foreach (var flowName in activeFlows)
        {
            ProcessFlow(flowName, nodes, outputDirectory);
        }

        Console.WriteLine($"Processing complete. Results saved to {outputDirectory}");
    }

    static void ProcessFlow(string flowName, JArray nodes, string outputDirectory)
    {
        // Filter nodes belonging to the flow
        var flowNodes = new Dictionary<string, JToken>();
        foreach (var node in nodes)
        {
            if (node["z"]?.ToString() == flowName)
            {
                flowNodes[node["id"]?.ToString()] = node;
            }
        }

        // Define start and end node types
        var startNodeTypes = new HashSet<string>
    {
        "kafka-consumer", "kafkajs-consumer", "kafka-producer",
        "kafkajs-producer", "mqtt in", "mqtt out", "http request"
    };

        var endNodeTypes = new HashSet<string>
    {
        "kafka-consumer", "kafkajs-consumer", "kafka-producer",
        "kafkajs-producer", "mqtt in", "mqtt out", "http request"
    };

        // Find starting and endpoint matches
        var results = new List<string>();
        foreach (var node in flowNodes.Values)
        {
            if (startNodeTypes.Contains(node["type"]?.ToString()))
            {
                var startNodeId = node["id"]?.ToString();
                var endpoints = FindEndpoints(flowNodes, startNodeId, endNodeTypes);

                foreach (var endpoint in endpoints)
                {
                    results.Add($"Starting Node: {node}\nMatched Endpoint: {endpoint}\n");
                }
            }
        }

        // Write results to flow-specific file
        string outputFilePath = Path.Combine(outputDirectory, $"{flowName}.txt");
        File.WriteAllLines(outputFilePath, results);
    }

    static List<JToken> FindEndpoints(Dictionary<string, JToken> flowNodes, string startNodeId, HashSet<string> endNodeTypes)
    {
        var visited = new HashSet<string>();
        var endpoints = new List<JToken>();

        void Traverse(string nodeId)
        {
            if (visited.Contains(nodeId) || !flowNodes.ContainsKey(nodeId))
                return;

            visited.Add(nodeId);
            var node = flowNodes[nodeId];
            if (endNodeTypes.Contains(node["type"]?.ToString()))
            {
                endpoints.Add(node);
                return;
            }

            var wires = node["wires"] as JArray;
            if (wires != null)
            {
                foreach (var wireGroup in wires)
                {
                    foreach (var connectedId in wireGroup)
                    {
                        Traverse(connectedId.ToString());
                    }
                }
            }
        }

        Traverse(startNodeId);
        return endpoints;

    }

核心问题与修复方案

1. 流程节点匹配逻辑错误

Node-RED中,节点的z字段存储的是tab节点的ID,而非tab的名称(label)。原代码用tab名称匹配z字段,导致无法筛选出对应流程的节点。

修复:

  • 收集active flows时同时存储tab的ID和名称:
var activeFlows = new Dictionary<string, string>(); // key: tab ID, value: tab名称
foreach (var node in nodes)
{
    if (node["type"]?.ToString() == "tab" && node["disabled"]?.ToObject<bool>() != true)
    {
        string tabId = node["id"]?.ToString();
        string tabLabel = node["label"]?.ToString();
        activeFlows.Add(tabId, tabLabel);
    }
}
  • 修改遍历逻辑,用tab ID筛选节点,用tab名称命名输出文件:
foreach (var flow in activeFlows)
{
    string tabId = flow.Key;
    string flowName = flow.Value;
    ProcessFlow(tabId, flowName, nodes, outputDirectory);
}
  • 更新ProcessFlow方法的参数与筛选逻辑:
static void ProcessFlow(string tabId, string flowName, JArray nodes, string outputDirectory)
{
    var flowNodes = new Dictionary<string, JToken>();
    foreach (var node in nodes)
    {
        if (node["z"]?.ToString() == tabId)
        {
            flowNodes[node["id"]?.ToString()] = node;
        }
    }
    // 后续逻辑不变
}

2. 路径遍历终止逻辑错误

原代码遇到终点节点时直接return,会中断后续分支的遍历,导致无法识别同一起点下的多个终点。

修复:
移除return语句,仅将节点加入结果集,继续遍历后续分支:

if (endNodeTypes.Contains(node["type"]?.ToString()))
{
    endpoints.Add(node);
    // 移除return,保留后续遍历
}

3. 起点节点被误判为终点

如果起点节点本身属于终点类型,原逻辑会将其误判为路径终点,不符合业务需求。

修复:
在遍历方法中标记初始节点,跳过初始节点的终点判断:

static List<JToken> FindEndpoints(Dictionary<string, JToken> flowNodes, string startNodeId, HashSet<string> endNodeTypes)
{
    var visited = new HashSet<string>();
    var endpoints = new List<JToken>();

    void Traverse(string nodeId, bool isInitial = false)
    {
        if (visited.Contains(nodeId) || !flowNodes.ContainsKey(nodeId))
            return;

        visited.Add(nodeId);
        var node = flowNodes[nodeId];
        // 仅非初始节点才判断是否为终点
        if (!isInitial && endNodeTypes.Contains(node["type"]?.ToString()))
        {
            endpoints.Add(node);
        }

        var wires = node["wires"] as JArray;
        if (wires != null)
        {
            foreach (var wireGroup in wires)
            {
                foreach (var connectedId in wireGroup)
                {
                    Traverse(connectedId.ToString());
                }
            }
        }
    }

    Traverse(startNodeId, isInitial: true);
    return endpoints;
}

4. 节点输出格式优化

原代码直接输出JToken对象,可读性差,建议提取关键属性输出:

// 替换结果添加逻辑
string startInfo = $"类型: {node["type"]}, ID: {node["id"]}, 名称: {node["name"] ?? "无名称"}";
string endInfo = $"类型: {endpoint["type"]}, ID: {endpoint["id"]}, 名称: {endpoint["name"] ?? "无名称"}";
results.Add($"起始节点: {startInfo}\n匹配终点: {endInfo}\n");

内容的提问来源于stack exchange,提问作者Mdarende

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 23:49:59