Node-RED 自定义节点开发
Node-RED 自定义节点开发完全指南
基于项目实战经验总结,Node-RED 5.x + amqplib 2.x
目录
1. Node-RED 架构概览
Node-RED 是基于 Node.js 的可视化编程工具,核心架构:
┌─────────────────────────────────────────────┐
│ Browser (Editor) │
│ HTML templates + JS (client-side) │
└──────────────────┬──────────────────────────┘
│ HTTP/WebSocket
┌──────────────────▼──────────────────────────┐
│ Node-RED Runtime │
│ ┌─────────┐ ┌──────────┐ ┌──────────────┐ │
│ │ Flows │ │ Nodes │ │ Settings │ │
│ │ (JSON) │ │ (JS+HTML)│ │ (settings.js)│ │
│ └─────────┘ └──────────┘ └──────────────┘ │
│ ┌──────────────────────────────────────┐ │
│ │ HTTP Admin API (httpAdminRoot) │ │
│ │ HTTP Node API (httpNodeRoot) │ │
│ │ Static Files (editor-client) │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
关键概念:
- Node(节点):一个功能单元,由 JS(运行时逻辑)+ HTML(编辑器 UI)组成
- Flow(流程):节点的连接关系,存储为
flows.json - Config Node(配置节点):共享配置(如 MQTT broker 连接),被多个节点引用
- User Directory:Node-RED 存储用户数据的目录(flows.json、settings.js、node_modules 等)
2. 项目目录结构
Node-RED/ ← 项目根目录
├── red.js ← Node-RED 入口文件(启动脚本)
├── lib/ ← Node-RED 核心库
├── settings.js ← 用户配置文件
├── node_modules/ ← 全局依赖
│ ├── @node-red/ ← Node-RED 核心包
│ │ ├── runtime/ ← 运行时引擎
│ │ ├── editor-api/ ← 编辑器后端 API
│ │ └── editor-client/ ← 编辑器前端(HTML/JS/CSS)
│ │ ├── public/ ← 静态资源
│ │ │ ├── red/ ← 编辑器核心 JS
│ │ │ │ ├── red.js ← 编辑器主入口
│ │ │ │ └── main.js ← 编辑器主逻辑
│ │ │ ├── vendor/ ← 第三方库
│ │ │ └── favicon.ico
│ │ └── templates/
│ │ └── index.mst ← 编辑器 HTML 模板
│ ├── amqplib/ ← AMQP 客户端库
│ ├── express/ ← HTTP 框架
│ └── node-red-contrib-hmes-amqp/ ← 自定义节点包
│ ├── package.json ← 包定义
│ ├── amqp-hmes.js ← 节点运行时逻辑
│ └── amqp-hmes.html ← 节点编辑器 UI
└── nodered/ ← User Directory
├── flows.json ← 流程定义(核心)
├── flows_cred.json ← 凭据(加密)
├── .config.nodes.json ← 已安装节点注册表
├── .config.runtime.json ← 运行时配置缓存
├── settings.js ← 备用配置
└── node_modules/ ← 项目级依赖
├── node-red-contrib-hmes-amqp/
└── amqplib/
关键路径说明:
| 文件 | 作用 | 能否手动编辑 |
|---|---|---|
red.js |
Node-RED 入口,初始化 HTTP 服务、加载 settings | 不建议 |
settings.js |
配置端口、流文件路径、认证等 | ✅ |
flows.json |
流程拓扑+节点配置 | ✅(或通过编辑器) |
.config.nodes.json |
节点类型注册表(自动生成) | ❌ |
node_modules/ |
npm 安装的包 | 通过 npm 管理 |
3. 自定义节点开发
3.1 包结构
创建一个 Node-RED 自定义节点 = 创建一个 npm 包。最小结构:
node-red-contrib-my-node/
├── package.json ← 必须包含 node-red 字段
├── my-node.js ← 运行时逻辑(Node.js 端)
└── my-node.html ← 编辑器 UI(Browser 端)
3.2 package.json
{
"name": "node-red-contrib-my-node",
"version": "1.0.0",
"description": "My custom Node-RED node",
"main": "my-node.js",
"node-red": {
"nodes": {
"my-node": "my-node.js"
}
},
"dependencies": {
"amqplib": "^2.0.1"
}
}
关键字段:
node-red.nodes:注册节点类型,格式为"节点类型名": "JS文件路径"- 一个 JS 文件可以注册多个节点类型(如
hmes-amqp-in和hmes-amqp-out共用一个 JS 文件) main字段对 Node-RED 不重要,node-red.nodes才是关键
3.3 节点 JS 文件(运行时)
// my-node.js
module.exports = function(RED) {
// 注册节点类型
function MyNode(config) {
RED.nodes.createNode(this, config);
var node = this;
// 读取配置(来自 HTML 表单或 flows.json)
var topic = config.topic;
var interval = config.interval;
// 节点启动逻辑
node.status({ fill: "green", shape: "dot", text: "connected" });
// 处理输入消息
node.on('input', function(msg) {
// msg.payload = 输入数据
// msg.topic = 消息主题
node.send(msg); // 转发到下一个节点
});
// 节点关闭时清理
node.on('close', function() {
// 关闭连接、清除定时器等
});
}
RED.nodes.registerType("my-node", MyNode);
// 可选:注册配置节点
function MyConfigNode(config) {
RED.nodes.createNode(this, config);
this.host = config.host;
this.port = config.port;
}
RED.nodes.registerType("my-config-node", MyConfigNode);
};
关键 API:
| API | 说明 |
|---|---|
RED.nodes.createNode(this, config) |
必须调用,初始化节点 |
config.xxx |
读取 HTML 表单中 id="node-input-xxx" 的值 |
node.send(msg) |
向下游节点发送消息 |
node.error(msg) |
报错(红色状态) |
node.warn(msg) |
警告 |
node.log(msg) |
日志 |
node.status({fill, shape, text}) |
设置节点状态指示器 |
node.on('input', fn) |
监听输入消息 |
node.on('close', fn) |
节点关闭时清理资源 |
状态指示器:
node.status({ fill: "green", shape: "dot", text: "connected" });
// fill: "red" | "green" | "yellow" | "blue" | "grey"
// shape: "ring" | "dot" | "dot"(实心)
// text: 显示的文字
3.4 节点 HTML 文件(编辑器 UI)
HTML 文件包含两部分:JavaScript 注册 + HTML 模板。
<!-- 客户端 JS:注册节点类型到编辑器 -->
<script type="text/javascript">
RED.nodes.registerType('my-node', {
category: 'network', // 调色板分类
color: '#E9967A', // 节点颜色
defaults: { // 默认值和验证
name: { value: '' },
topic: { value: '', required: true },
interval: { value: 1000, validate: RED.validators.number() }
},
inputs: 1, // 输入端口数
outputs: 1, // 输出端口数
icon: 'feed.png', // 图标
label: function() { // 节点上显示的文字
return this.name || this.topic || 'my-node';
},
paletteLabel: 'my node' // 调色板中显示的文字
});
</script>
<!-- HTML 模板:编辑器中的属性面板 -->
<script type="text/html" data-template-name="my-node">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-topic"><i class="fa fa-envelope"></i> Topic</label>
<input type="text" id="node-input-topic" placeholder="my/topic">
</div>
<div class="form-row">
<label for="node-input-interval"><i class="fa fa-clock-o"></i> Interval</label>
<input type="text" id="node-input-interval" placeholder="1000">
</div>
</script>
HTML 规则:
| 规则 | 说明 |
|---|---|
<script type="text/html" data-template-name="xxx"> |
模板名必须与 registerType 的类型名一致 |
id="node-input-xxx" |
必须匹配 defaults 中的属性名(前缀 node-input-) |
id="node-config-input-xxx" |
配置节点的输入框前缀是 node-config-input- |
<div class="form-row"> |
标准表单行样式 |
<i class="fa fa-xxx"> |
Font Awesome 图标(Node-RED 内置) |
配置节点的 HTML:
<!-- 配置节点模板前缀是 node-config-input -->
<script type="text/html" data-template-name="my-config-node">
<div class="form-row">
<label for="node-config-input-host"><i class="fa fa-server"></i> Host</label>
<input type="text" id="node-config-input-host" placeholder="127.0.0.1">
</div>
</script>
3.5 属性名匹配(最容易出错!)
核心原则:HTML 中的 id、JS 中的 config.xxx、flows.json 中的属性名必须三者一致。
// HTML 中
<input id="node-input-hostname" ...>
// JS 中读取
config.hostname // ← 自动从 flows.json 读取同名属性
// flows.json 中
{ "hostname": "127.0.0.1" }
错误示例:
// ❌ HTML 用 "host",flows.json 用 "hostname" → 找不到值
<input id="node-input-host">
// flows.json: { "hostname": "127.0.0.1" }
config.host // undefined!
4. flows.json 流程定义
4.1 结构
[
{
"id": "tab.main",
"type": "tab",
"label": "主流程",
"disabled": false,
"info": ""
},
{
"id": "node.1",
"type": "mqtt-broker",
"z": "tab.main",
"name": "MQTT Broker",
"broker": "127.0.0.1",
"port": "1883"
},
{
"id": "node.2",
"type": "mqtt in",
"z": "tab.main",
"name": "MQTT Subscribe",
"topic": "test/topic",
"qos": "0",
"datatype": "json",
"broker": "node.1",
"wires": [["node.3"]]
},
{
"id": "node.3",
"type": "debug",
"z": "tab.main",
"name": "Debug",
"active": true,
"wires": []
}
]
字段说明:
| 字段 | 说明 |
|---|---|
id |
节点唯一 ID(字符串) |
type |
节点类型(对应 registerType 的名称) |
z |
所属流程 tab 的 ID |
wires |
连接关系:[["下游节点ID"]],二维数组对应输出端口 |
| 其他属性 | 节点自定义配置(与 defaults 对应) |
4.2 wires 连接规则
// wires 是二维数组:[输出端口0的连接, 输出端口1的连接, ...]
"wires": [["node.b"]] // 端口0 连到 node.b
"wires": [["node.b"], ["node.c"]] // 端口0→node.b, 端口1→node.c
"wires": [["node.b", "node.c"]] // 端口0 同时连到 node.b 和 node.c
"wires": [[]] // 端口0 不连任何节点
4.3 配置节点在 flows.json 中
配置节点也是 flows.json 的一个条目,被其他节点通过 ID 引用:
{
"id": "amqp-conn.1",
"type": "hmes-amqp-connection",
"host": "127.0.0.1",
"port": 5672,
"user": "guest",
"password": "guest",
"vhost": "/"
},
{
"id": "amqp-in.1",
"type": "hmes-amqp-in",
"connection": "amqp-conn.1", ← 引用配置节点 ID
"queue": "my.queue",
"wires": [["next.node"]]
}
注意:如果你的节点不在 HTML 中定义 config node,而是直接在节点上放连接属性(如我们的 AMQP 节点),则不需要单独的配置节点条目。
5. settings.js 配置
module.exports = {
// 流程文件名
flowFile: 'flows.json',
// 格式化 JSON(便于手动编辑)
flowFilePretty: true,
// 凭据加密密钥(false = 使用默认密钥)
credentialSecret: false,
// HTTP 端口
uiPort: 1880,
// HTTP 根路径(默认 "/")
httpAdminRoot: "/",
// 用户目录(默认 ~/.node-red,可覆盖)
// userDir: "/path/to/userDir",
// MQTT 重连时间(毫秒)
mqttReconnectTime: 15000,
// 编辑器认证(可选)
// httpAdminAuth: { user: "admin", pass: "password" }
};
6. 启动与部署
6.1 启动命令
# 标准启动
node red.js
# 指定端口
node red.js --port 1881
# 指定 settings 文件
node red.js --settings /path/to/settings.js
# 指定用户目录
node red.js --userDir /path/to/nodered
# 后台启动(Windows)
Start-Process -FilePath 'node' -ArgumentList 'red.js' -WindowStyle Hidden
# 后台启动(Linux)
nohup node red.js &
6.2 启动流程
red.js 启动
├── 1. 解析命令行参数
├── 2. 加载 settings.js
├── 3. 设置 userDir(默认 __dirname/nodered)
├── 4. 创建 HTTP 服务器(Express)
├── 5. 初始化 Node-RED Runtime
│ ├── 加载 node_modules 中的节点
│ ├── 读取 .config.nodes.json(节点注册表)
│ └── 加载 flows.json
├── 6. 挂载路由
│ ├── httpAdminRoot → 编辑器 UI
│ ├── httpNodeRoot → 用户 HTTP 节点
│ └── 静态文件 → editor-client/public
├── 7. 启动 HTTP 监听
└── 8. 执行流程(节点开始工作)
6.3 热重载
Node-RED 支持部分热重载:
- flows.json 修改:编辑器中点 Deploy 即可,不需要重启
- 节点代码修改(JS/HTML):必须重启 Node-RED
- settings.js 修改:必须重启
- npm 安装新包:必须重启
7. 调试与排错
7.1 常见错误
7.1.1 JSON 解析错误
SyntaxError: Unexpected token '', "{..." is not valid JSON
原因:文件有 UTF-8 BOM(字节序标记 0xEF 0xBB 0xBF)。
解决:
# 检查是否有 BOM
$b = [System.IO.File]::ReadAllBytes('flows.json')
$b[0..2] -join ' ' # 如果是 239 187 191 就是 BOM
# 移除 BOM
$nb = New-Object byte[]($b.Length - 3)
[Array]::Copy($b, 3, $nb, 0, $nb.Length)
[System.IO.File]::WriteAllBytes('flows.json', $nb)
避免方法:写文件时用 UTF8Encoding(false, false)(无 BOM)。
7.1.2 MIME 类型错误
Refused to execute script because its MIME type ('text/html') is not executable
原因:Node-RED 找不到静态文件(如 red/red.js),返回了 HTML 错误页面。
解决:检查 @node-red/editor-client/public/ 目录是否完整。
# 检查关键文件是否存在
Test-Path "node_modules/@node-red/editor-client/public/red/red.js"
Test-Path "node_modules/@node-red/editor-client/public/vendor/vendor.js"
Test-Path "node_modules/@node-red/editor-client/templates/index.mst"
7.1.3 节点属性面板打不开
原因:HTML 模板中的 data-template-name 与 registerType 的类型名不匹配。
排查:
// JS 注册的类型名
RED.nodes.registerType('hmes-amqp-in', ...) // ← 类型名
// HTML 模板名
<script type="text/html" data-template-name="hmes-amqp-in"> // ← 必须一致
7.1.4 节点属性值为 undefined
原因:HTML id、JS config.xxx、flows.json 属性名三者不一致。
// HTML
<input id="node-input-hostname">
// JS 读取
config.hostname // 必须与 HTML id 的 node-input- 后面部分一致
// flows.json
{ "hostname": "127.0.0.1" } // 必须与 HTML id 一致
7.2 调试技巧
// 在节点 JS 中输出调试信息
node.log("Debug: " + JSON.stringify(config));
node.warn("Warning: " + msg.payload);
node.error("Error: " + err.message, msg); // 第二个参数会附加到错误信息
// 在编辑器 JS 中调试
console.log("Node config:", this); // this 包含节点所有属性
// 查看 Node-RED 内部状态
// 浏览器控制台输入:
RED.nodes.depot // 所有已部署的节点
RED.nodes.workspace // 工作空间信息
7.3 查看 Node-RED 日志
Node-RED 日志输出到 stdout。启动时重定向到文件:
node red.js > nodered.log 2>&1
日志级别控制:
# 详细日志
node red.js --verbose
# 或在 settings.js 中
settings.verbose = true;
8. 实战案例:AMQP 节点
8.1 需求
创建 hmes-amqp-in 和 hmes-amqp-out 两个节点,用于 RabbitMQ 消息收发。
8.2 完整代码
package.json:
{
"name": "node-red-contrib-hmes-amqp",
"version": "1.0.1",
"description": "HMES AMQP nodes for RabbitMQ",
"main": "amqp-hmes.js",
"node-red": {
"nodes": {
"hmes-amqp-in": "amqp-hmes.js",
"hmes-amqp-out": "amqp-hmes.js"
}
},
"dependencies": {
"amqplib": "^2.0.1"
}
}
amqp-hmes.js(核心逻辑):
const amqplib = require('amqplib');
module.exports = function(RED) {
// 从配置中提取连接参数(兼容 config node 和直接属性两种方式)
function getConnOpts(config) {
if (config.hostname) {
return {
host: config.hostname,
port: parseInt(config.port) || 5672,
user: config.username || 'guest',
password: config.password || 'guest',
vhost: config.vhost || '/'
};
}
return null;
}
// ========== AMQP In 节点 ==========
function AmqpInNode(config) {
RED.nodes.createNode(this, config);
var node = this;
var queue = config.queue || '';
var pfCount = parseInt(config.prefetch) || 10;
var opts = getConnOpts(config);
var conn = null, ch = null, closing = false, retryTimer = null;
function scheduleRetry() {
if (!closing && !retryTimer) {
retryTimer = setTimeout(function() {
retryTimer = null;
doConnect();
}, 5000);
}
}
async function doConnect() {
if (closing || !opts) return;
try {
var url = 'amqp://' + opts.user + ':' + opts.password +
'@' + opts.host + ':' + opts.port + '/' + opts.vhost;
conn = await amqplib.connect(url);
ch = await conn.createChannel();
await ch.prefetch(pfCount);
await ch.assertQueue(queue, { durable: true });
node.status({ fill: 'green', shape: 'dot', text: 'connected' });
ch.consume(queue, function(msg) {
if (!msg) return;
var payload = msg.content;
try { payload = JSON.parse(payload.toString()); } catch(e) {}
node.send({
payload: payload,
_amqpMsg: msg, // 保留原始消息用于 ack
_channel: ch // 保留通道引用
});
}, { noAck: false });
conn.on('error', function(err) {
node.error('AMQP error: ' + err.message);
});
conn.on('close', function() {
ch = null; conn = null;
if (!closing) {
node.status({ fill: 'yellow', shape: 'ring', text: 'reconnecting' });
scheduleRetry();
}
});
} catch(err) {
node.error('AMQP connect failed: ' + err.message);
node.status({ fill: 'red', shape: 'ring', text: 'retrying' });
if (conn) { try { await conn.close(); } catch(e) {} conn = null; }
scheduleRetry();
}
}
// 收到下游处理完的消息时 ack
node.on('input', function(msg) {
if (msg._amqpMsg && msg._channel) {
try { msg._channel.ack(msg._amqpMsg); } catch(e) {}
delete msg._amqpMsg;
delete msg._channel;
}
});
node.on('close', function() {
closing = true;
if (retryTimer) { clearTimeout(retryTimer); retryTimer = null; }
try { if (ch) ch.close(); } catch(e) {}
try { if (conn) conn.close(); } catch(e) {}
conn = null; ch = null;
});
doConnect();
}
RED.nodes.registerType('hmes-amqp-in', AmqpInNode);
// ========== AMQP Out 节点 ==========
function AmqpOutNode(config) {
RED.nodes.createNode(this, config);
var node = this;
var queue = config.queue || '';
var exchange = config.exchange || '';
var routingKey = config.routingKey || '';
var opts = getConnOpts(config);
var conn = null, ch = null, closing = false, retryTimer = null;
function scheduleRetry() { /* 同上 */ }
async function doConnect() {
if (closing || !opts) return;
try {
var url = 'amqp://' + opts.user + ':' + opts.password +
'@' + opts.host + ':' + opts.port + '/' + opts.vhost;
conn = await amqplib.connect(url);
ch = await conn.createChannel();
if (queue) await ch.assertQueue(queue, { durable: true });
node.status({ fill: 'green', shape: 'dot', text: 'connected' });
conn.on('close', function() {
ch = null; conn = null;
if (!closing) scheduleRetry();
});
} catch(err) {
node.error('AMQP connect failed: ' + err.message);
scheduleRetry();
}
}
node.on('input', async function(msg) {
if (!ch) { node.warn('channel not ready'); return; }
try {
var content = typeof msg.payload === 'string'
? msg.payload
: JSON.stringify(msg.payload);
var buf = Buffer.from(content);
var rk = msg.topic || routingKey || queue;
if (exchange) ch.publish(exchange, rk, buf);
else if (queue) ch.sendToQueue(queue, buf);
} catch(err) {
node.error('AMQP publish error: ' + err.message);
}
});
node.on('close', function() { /* 同上 */ });
doConnect();
}
RED.nodes.registerType('hmes-amqp-out', AmqpOutNode);
};
amqp-hmes.html:
<script type="text/javascript">
RED.nodes.registerType('hmes-amqp-in', {
category: 'HMES',
color: '#88C999',
defaults: {
name: { value: '' },
hostname: { value: '127.0.0.1', required: true },
port: { value: 5672, required: true, validate: RED.validators.number() },
username: { value: 'guest' },
password: { value: 'guest' },
vhost: { value: '/' },
queue: { value: '', required: true },
prefetch: { value: 5000, validate: RED.validators.number() }
},
inputs: 1,
outputs: 1,
icon: 'bridge.svg',
label: function() { return this.name || this.queue || 'amqp-in'; },
paletteLabel: 'amqp in'
});
</script>
<script type="text/html" data-template-name="hmes-amqp-in">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Name">
</div>
<div class="form-row">
<label for="node-input-hostname"><i class="fa fa-server"></i> Host</label>
<input type="text" id="node-input-hostname" placeholder="127.0.0.1">
</div>
<div class="form-row">
<label for="node-input-port"><i class="fa fa-plug"></i> Port</label>
<input type="text" id="node-input-port" placeholder="5672">
</div>
<div class="form-row">
<label for="node-input-username"><i class="fa fa-user"></i> User</label>
<input type="text" id="node-input-username" placeholder="guest">
</div>
<div class="form-row">
<label for="node-input-password"><i class="fa fa-lock"></i> Password</label>
<input type="password" id="node-input-password" placeholder="guest">
</div>
<div class="form-row">
<label for="node-input-vhost"><i class="fa fa-folder"></i> VHost</label>
<input type="text" id="node-input-vhost" placeholder="/">
</div>
<div class="form-row">
<label for="node-input-queue"><i class="fa fa-inbox"></i> Queue</label>
<input type="text" id="node-input-queue" placeholder="telemetry.nodered.input">
</div>
<div class="form-row">
<label for="node-input-prefetch"><i class="fa fa-sort-amount-down"></i> Prefetch</label>
<input type="text" id="node-input-prefetch" placeholder="5000">
</div>
</script>
<!-- hmes-amqp-out 类似,省略 -->
8.3 关键设计决策
| 决策 | 选择 | 原因 |
|---|---|---|
| 连接方式 | 直接属性 vs config node | flows.json 已有直接属性,保持兼容 |
| Prefetch 类型 | parseInt() 强转 |
flows.json 中属性可能是字符串 |
| 重连机制 | setTimeout + closing 标志 |
避免并发重连 |
| 消息确认 | 保留 _amqpMsg + _channel |
下游处理完再 ack,不丢失消息 |
| BOM 处理 | 写文件时 UTF8Encoding(false) |
避免 Node-RED JSON 解析失败 |
9. 常见坑与最佳实践
9.1 坑:npm install 后自定义节点消失
# ❌ npm install 会清理 node_modules,自定义节点被删除
npm install amqplib
# ✅ 先保存到 package.json,再安装
npm install amqplib --save
恢复方法:重新创建节点文件(建议用 git 管理自定义节点代码)。
9.2 坑:BOM 导致 JSON 解析失败
# PowerShell 5.1 的 Set-Content -Encoding UTF8 会加 BOM
Set-Content -Encoding UTF8 file.json "..." # ❌ 有 BOM
# ✅ 用 .NET 方法写无 BOM 的 UTF-8
$enc = New-Object System.Text.UTF8Encoding($false, $true)
[System.IO.File]::WriteAllText('file.json', $content, $enc)
9.3 坑:amqplib 2.x API 变化
// amqplib 0.x(旧)
var conn = amqplib.connect('amqp://localhost');
conn.then(function(conn) { ... });
// amqplib 2.x(新)
var conn = await amqplib.connect('amqp://localhost');
var ch = await conn.createChannel();
9.4 坑:prefetch 参数类型
// ❌ flows.json 中 "prefetch": "5000"(字符串)会导致错误
await ch.prefetch(config.prefetch);
// ✅ 强制转为数字
await ch.prefetch(parseInt(config.prefetch) || 10);
9.5 坑:旧进程残留
# 杀掉所有旧的 node 进程
Get-Process -Name 'node' | Stop-Process -Force
Start-Sleep -Seconds 2
# 再启动新的
9.6 最佳实践
- 自定义节点用 git 管理,不要依赖 node_modules 中的自定义文件
- 写文件始终无 BOM:
new UTF8Encoding(false, true) - 配置值强转类型:
parseInt()、Number()、Boolean() - 异步操作加 try-catch:AMQP 连接、数据库操作等
- 重连机制用标志位:避免
close和error事件并发触发多次重连 - 清理资源在
on('close')中:关闭连接、清除定时器 - 编辑器 UI 属性名与 flows.json 严格一致
- 部署前验证 JSON:
ConvertFrom-Json或在线工具
附录:Node-RED 内置节点分类
| 分类 | 常用节点 |
|---|---|
| function | function, change, switch, template |
| network | http in/out/request, mqtt in/broker |
| input | inject, serial, tcp |
| output | debug, mqtt out, tcp out |
| storage | file in/out, csv |
| social | email, websocket |
| 列表 | comment, link, junction |
最后更新:2026-08-24
适用版本:Node-RED 5.x + amqplib 2.x

浙公网安备 33010602011771号