物联网中的 Node-RED(9·下壹):AI 协作与多工程拆分方案 · 第二阶段多工程拆分
本篇是《AI + Node-RED 协作开发与多工程拆分统一管理方案》拆分后的第 3 篇(共 5 篇)。
- (上)背景、核心概念与总体架构
- (中)第一阶段:Flow 拆分与 AI 协作
- (下壹)第二阶段:多工程拆分(分组 / 目录 / settings.js / 注册表 / 配置节点 / 脚本改造) ← 本篇
- (下贰)第二阶段:统一面板升级、风险清单与实施计划
- (终)协作边界、与传统方式对比、FAQ 与检查清单
原文标题:AI + Node-RED 协作开发与多工程拆分统一管理方案
五、第二阶段:多工程拆分与统一面板管理
本阶段是第一阶段的演进扩展,不是替代。三条铁律全部继承。新增的是:① 按业务域把 Tab 拆成多个独立 Node-RED 工程;② 每个工程独立端口 / 独立 userDir / 自带 config;③ 统一面板升级为「双层」管理这些工程。原脚本被改造为「按工程选端口 + 目录」。
5.1 工程分组方法
原则:把工作流按业务域(而非按技术层)分成若干组。同一时间段你只关心其中一组,就把那一组拆成一个工程。
分组依据(通用):某几个 Tab 是否共享同一套设备 / 数据源 / 对外接口?是 → 归同一工程。彼此无依赖 → 可拆开。
示例分组表(占位,落地时替换为你的业务域):
| 序号 | 工程名(目录) | 端口 | tab 数 | 包含 Tab(示例) |
|---|---|---|---|---|
| ① | <业务域A> proj-a |
<端口1> |
N | <Tab A1>、<Tab A2>、<Tab A3> |
| ② | <业务域B> proj-b |
<端口2> |
N | <Tab B1>、<Tab B2> |
| ③ | <业务域C> proj-c |
<端口3> |
N | <Tab C1> |
| ④ | <业务域D> proj-d |
<端口4> |
N | <Tab D1> |
| ⑤ | <业务域E> proj-e |
<端口5> |
N | <Tab E1> |
| ⑥ | <业务域F> proj-f |
<端口6> |
N | <Tab F1>、<Tab F2> |
| ⑦ | <业务域G> proj-g |
<端口7> |
N | <Tab G1> |
| ⑧ | <其他> others |
<端口8> |
N | 启动/模拟采集/实时监听/ETL/转发平台 等通用 Tab |
端口段选择:建议从一个空闲段递增分配(示例
1891-1898,主实例保留原端口如1880)。落地前务必用netstat确认该段全部空闲,且避开被其他进程 / 业务连接占用的端口。端口分配写入注册表(见 5.4),脚本自动读取,不手算。清理:拆分前先删除重复 / 无效 / 空 Tab(导入残留、同 id 重复副本等),保持分组映射为单一事实来源。
5.2 目标目录结构
<项目根>/
├── doc/ ← 全局工程文档(本文即其中之一)
│ ├── 01.方案总览.md ← 索引,说明「工程 = 业务域分组」
│ ├── 02.Flow拆分与AI协作.md ← 第一阶段细节
│ ├── 03.多工程拆分与统一面板.md ← 第二阶段细节(本文)
│ └── <业务域A>/ <业务域B>/ ... ← 分项目文档(只描述各自业务)
├── projects/ ← 【新增】多工程根目录
│ ├── _registry.json ← 【核心】工程注册表(分组+端口+路径)
│ ├── proj-a/ ← 工程①
│ │ ├── settings.js ← uiPort + userDir 指向本目录
│ │ ├── flows.json ← 运行时存储(由 sync 生成/回写)
│ │ ├── .config.runtime.json ← Node-RED 自动生成
│ │ ├── .flows.json.backup ← Node-RED 自动生成
│ │ └── src/tabs/*.yaml ← 仅本工程的 tab(从原 src/tabs 迁入)
│ ├── proj-b/ proj-c/ ... others/
│ └── ...
├── src/tabs/ ← 原全量 tab(拆分完成后保留作备份/对照,不删除)
├── sync-panel/ ← 按钮化面板(升级为双层,见 5.7)
└── tmp/ ← 工具脚本与临时文件
关键决策:
src/tabs/先保留不删,作为对照与回滚源;确认每个工程独立跑通后再考虑归档。- 每个工程自带 config 节点副本(见 5.5),不跨工程共享。
- 每个工程
settings.js全量保留原functionGlobalContext(按需,不按工程裁剪)。
5.3 每个工程 settings.js 模板(关键,避免多实例起不来)
基于现有根 settings.js,每个工程目录放一份,差异点只有三处(其余原样复制):
// projects/<key>/settings.js
module.exports = {
// …… 原 settings.js 全部内容复制 ……
uiPort: <端口1>, // ← 改为本工程端口(写死,不靠 env)
// flowFile / flowFilePretty 保持 'flows.json' / true(相对 userDir)
functionGlobalContext: { // ← 全量原样保留,不裁剪(按需增删 Node 内置模块)
fs: require('fs'),
path: require('path'),
// 例:net: require('net'), crypto: require('crypto'),
// 项目特有的第三方模块(如 iconv-lite / canvas 等)按工程实际需要保留
},
// …… 其余(如 multer patch / apiMaxLength / editorTheme 等)原样 ……
}
启动方式(与现有启动脚本同款形式,优先用项目本地 Node-RED):
cd projects/<key>
..\..\node_modules\.bin\node-red.cmd -s settings.js "flows.json" --port <端口>
不采用
--userDir方式(行为不同、易与习惯不符)。functionGlobalContext里的__dirname指向各自工程目录的 settings.js 位置,相对路径随工程解析(用不到的工程只是指向空目录,不报错)。总目录与切换策略:所有拆分产物落在根
projects/下,原src/tabs/、flows.json、settings.js一律不动。验证全部工程独立跑通后,再决定切换(届时停掉老实例、改用各工程启动)。拆分阶段不触碰线上环境。
5.4 工程注册表 _registry.json(核心事实来源)
所有脚本、面板只读这个文件决定「工程名 ↔ 端口 ↔ 目录 ↔ tab 列表」的映射:
{
"basePort": <端口1>,
"projects": [
{
"key": "proj-a",
"name": "<业务域A>",
"port": <端口1>,
"dir": "projects/proj-a",
"tabs": ["<Tab A1>", "<Tab A2>", "<Tab A3>"]
},
{ "key": "proj-b", "name": "<业务域B>", "port": <端口2>, "dir": "projects/proj-b",
"tabs": ["<Tab B1>", "<Tab B2>"] },
{ "key": "proj-c", "name": "<业务域C>", "port": <端口3>, "dir": "projects/proj-c",
"tabs": ["<Tab C1>"] },
{ "key": "others", "name": "<其他>", "port": <端口8>, "dir": "projects/others",
"tabs": ["<启动>", "<模拟采集>", "<实时监听>", "<ETL>", "<转发平台>"] }
]
}
tabs存的是 tab 的 label(与 yaml 文件名一致,无.yaml后缀),脚本据此从src/tabs/<label>.yaml取文件。- 新增工程:在
projects数组追加一项,port = basePort + 序号。脚本自动分配,不手动算。
5.5 配置节点(config)策略 —— 每个工程自带、内联天然满足
决策:
- config 节点不共享,每个工程自带自己的副本。
- 各工程连各自的后端(数据库 / MQTT 等),不互通;将来若有互通需求,用其他方式实现,不在本方案内处理。
- 各工程
settings.js全量保留原functionGlobalContext,不按工程裁剪。
现状核实(重要,避免过度设计):
- 若项目里 config 节点直接内联在各 tab 的 yaml 文件内(flow 节点与 config 节点同文件,无独立
config-nodes/抽取),则拆分时无需「扫描引用再复制 config」的复杂逻辑:每个工程复制其 tab yaml 时,yaml 里自带的 config 节点自然随 tab 一起迁入,config 自带是天然满足的。 - 若项目采用「config 抽到独立
config-nodes/」的组织方式,则拆分脚本需先解析每个 tab 引用了哪些 config,再把对应 config 文件一并复制。
拆分方法(内联场景·简化):
- 按
_registry.json的tabs列表,把对应<label>.yaml整文件复制到projects/<key>/src/tabs/; - yaml 内已含的 config 节点(无
z字段的节点)随之带入,实例内 id 唯一即可,跨实例无关,不重新生成 id; - 拆分脚本只需做「文件级复制 + tab id 去重校验」(见 5.8 R4),不做 config 依赖分析。
风险(已降级):
- ✅ 连接数暴涨:按决策各工程连各自后端、不互通,不存在共享连接竞争,不列为风险;
- ⚠ 端口冲突:Node-RED Admin UI 端口必须空闲(拆分前已用
netstat确认);设备侧端口(TCP server 监听、MQTT 连远端等)由 flow 内部决定,与 UI 端口无关,不冲突; - ⚠ config 漏带:因 config 内联随 tab 走,正常不会漏;但需确认被多个 tab 共用的 config 节点复制后 id 一致、单实例内不冲突即可。
5.6 脚本改造(继承第一阶段,按工程选端口 + 目录)
第一阶段三个脚本的核心改造点:去掉硬编码单端口 / 单 src/,改为读 _registry.json 按工程路由。
| 脚本 | 改造点 |
|---|---|
split_to_projects.js(由 split_flows.js 演进,一次性) |
读 _registry.json,按每个工程的 tabs 列表把对应 <label>.yaml 整文件复制到 projects/<key>/src/tabs/(config 内联随之带入)。只跑一次。复制前做 tab id 全局唯一性校验。 |
sync_flows.js(改造) |
入参 --project <key>(或 --all 全工程)。只对该工程的 tab/config 做增删,严禁动其他工程的节点。保留原 mergeFlows「移除 YAML 已有 id 及其子节点、保留其余」逻辑——单实例内安全。 |
_reverse_sync.js(改造) |
入参 --project <key>。从 port 对应实例拉 GET /flows,只提取该工程 tab 下的节点回写 projects/<key>/src/tabs/。防止跨工程污染(原脚本会全量回写 src/tabs/,多工程下必须改)。 |
端口参数化约定(所有脚本统一):
const reg = require('../projects/_registry.json');
const proj = reg.projects.find(p => p.key === key);
const PORT = proj.port; // 替代原硬编码单端口
const USERDIR = proj.dir; // 替代原单 userDir

浙公网安备 33010602011771号