Node-RED:消息对象(Message)的奥秘

头图

Node-RED:消息对象(Message)的奥秘


关键字: Node-RED消息对象 msg详解msg消息传递规则调试技巧 JSONata表达式物联网数据处理

摘要

有次我帮同事排查一个奇怪的问题:他的流程明明连对了,Debug 节点却什么也不显示。
我们一起看了半小时,最后发现——他在 Function 节点里写了 return { payload: "ok" },但忘了这是个全新的对象,丢失了原始 msg.topic,导致下游 Switch 节点无法匹配条件。

那一刻我意识到:Node-RED 的灵魂,不在节点,而在消息

在前三篇文章中,我们安装了环境、熟悉了界面、跑通了第一个流程。但如果你只把 msg.payload 当成“数据容器”,那你只用了 Node-RED 30% 的能力。今天,我们就来揭开 msg 对象的全部秘密——它到底包含什么?字段如何传递?为什么有时“明明有数据却收不到”?

这篇文章,就是你的“消息解剖课”。


一、msg 不是“变量”,而是一封“信”

想象一下:Node-RED 的每个节点,都是一个邮局。当你点击 Inject 节点,相当于寄出一封信。这封信(msg)里可以装任何东西:正文(payload)、收件人(topic)、优先级(priority)、附件(自定义字段)……

下游节点收到信后,可以:

  • 只读正文
  • 修改正文
  • 加盖邮戳(新增字段)
  • 甚至把信撕了(return null)

关键在于:这封信在整个流程中是同一个对象(引用传递),除非你主动创建新对象。

默认 msg 长什么样?

一个最简单的 Inject 节点(类型为 string,内容为 “hello”)发出的 msg 是:

{
  "payload": "hello",
  "topic": ""
}

如果你用的是 timestamp 类型:

{
  "payload": 1729845600123,  // 毫秒时间戳
  "topic": ""
}

注意:topic 默认为空字符串,不是 undefined。这点在条件判断时很重要。


二、msg 的核心字段:不止 payload

虽然 payload 最常用,但 Node-RED 的强大之处在于利用其他字段实现上下文传递

1. msg.payload:主数据载体

  • 可以是任意类型:string、number、boolean、object、array、Buffer
  • 大多数节点默认读写 payload
  • 示例:HTTP in 节点把请求体放 payload,MQTT out 节点从 payload 读取要发布的消息

2. msg.topic:主题/分类标识

  • 常用于 MQTT(作为 topic)
  • 也可用于 Switch 节点做路由
  • 示例:多个传感器共用一个 MQTT in 节点,通过 topic 区分设备
msg.topic = 'sensor/temp'
msg.topic = 'sensor/humi'
temp
humi
MQTT in
Switch
处理温度
处理湿度

3. msg.headers:HTTP 请求/响应头

  • HTTP in 节点会把请求头放入 msg.headers
  • HTTP request 节点可设置 msg.headers 发送自定义头
  • 示例:传递认证 token
msg.headers = { "Authorization": "Bearer xxx" };
return msg;

4. msg.statusCode:HTTP 状态码

  • HTTP response 节点根据此字段返回状态(默认 200)
  • 可用于返回 404、500 等错误

5. 自定义字段:你的“私有信封”

你可以随意添加字段,比如:

msg.deviceId = "sensor_001";
msg.location = "warehouse_A";
msg.timestamp = Date.now();

这些字段会一直传递到流程末尾,除非被覆盖或删除。

💡 最佳实践:用自定义字段传递元数据,避免把所有信息塞进 payload。这样下游节点逻辑更清晰。


三、消息传递的三大规则(新手必看)

规则1:节点必须 return msg,否则流程中断

Function 节点中,如果你只写:

msg.payload = "done";
// 忘记 return

那么消息流在此终止,后续节点收不到任何东西。

✅ 正确写法:

msg.payload = "done";
return msg;  // 继续传递

或主动终止:

if (msg.payload < 0) {
    return null; // 丢弃无效消息
}
return msg;

规则2:修改 msg 是“原地操作”,不是复制

// 错误认知:以为创建了新对象
let newMsg = msg;
newMsg.payload = "modified";

// 实际上 msg 和 newMsg 指向同一个对象!

如果你真的需要副本(比如并行处理不同分支),要用 RED.util.cloneMessage()

let copy1 = RED.util.cloneMessage(msg);
let copy2 = RED.util.cloneMessage(msg);
copy1.payload += "_branch1";
copy2.payload += "_branch2";
return [copy1, copy2]; // 多输出

规则3:连线顺序 = 执行顺序(单线程)

Node-RED 是单线程事件循环,消息按连线顺序依次处理。
即使你画了“并行”分支,实际也是串行执行(除非用 Link 节点或子流程隔离)。


四、调试 msg 的四种高效方法

方法1:Debug 节点“完整对象”模式

双击 Debug 节点,将 “Output”msg.payload 改为 complete msg object
这样就能看到所有字段,包括你自定义的。

方法2:Function 节点中打印

node.warn("当前 msg: " + JSON.stringify(msg));
// 注意:不要用 console.log,它不会显示在 Debug 面板

node.warn() 会在 Debug 面板显示黄色警告,适合临时调试。

方法3:使用 Context 查看中间状态

在 Function 节点中临时保存:

flow.set("last_msg", msg);

然后在右侧侧边栏切换到 Context 面板,展开 flow 就能看到完整结构。

方法4:用 Change 节点“窥探”而不修改

添加一个 Change 节点,规则设为:

  • Set debug_snapshot
  • To $string($) (JSONata 表达式,转为字符串)

这样既保留原 msg,又能在 Debug 看到快照。


五、实战案例:用 msg 字段实现智能路由

假设你有三个设备上报数据,格式如下:

// 设备A
{ "type": "temperature", "value": 25.3 }

// 设备B
{ "type": "humidity", "value": 60 }

// 设备C
{ "type": "pressure", "value": 1013 }

目标:根据 type 字段,分别存入不同数据库表。

传统做法:写一个大 Function 节点,用 if-else 判断。

Node-RED 推荐做法

  1. Change 节点 提取 typemsg.topic
    • Rule: Set msg.topic to msg.payload.type
  2. Switch 节点topic 路由:
    • Case 1: temperature → 温度处理流
    • Case 2: humidity → 湿度处理流
    • Case 3: pressure → 气压处理流
  3. 各分支用 Change 节点value 提到 payload
    • Set msg.payload to msg.payload.value

这样,每个分支只处理单一职责,逻辑清晰,易于维护。


六、常见陷阱与避坑指南

❌ 陷阱1:在 Function 中覆盖整个 msg

// 危险!丢失所有原始字段
msg = { payload: "new" };

✅ 正确做法:只修改需要的字段

msg.payload = "new";
// 保留 msg.topic, msg.headers 等

❌ 陷阱2:误以为 msg 在子流程间自动共享

子流程(Subflow)有独立的 msg 上下文。如果你在主流程设置 msg.token,进入子流程后依然存在;但子流程内部修改不会影响主流程(除非 return 出来)。

❌ 陷阱3:Buffer 数据未正确处理

串口或 TCP 节点返回的 payload 可能是 Buffer。直接 msg.payload.toString() 可能乱码。

✅ 正确做法:指定编码

let str = msg.payload.toString('utf8');

七、进阶:用 JSONata 表达式优雅操作 msg

Node-RED 内置 JSONata 引擎,可在 Change 节点、Switch 条件中直接操作 msg。

例如,把嵌套对象扁平化:

// 原始 msg.payload
{ "sensor": { "temp": 25, "unit": "C" } }

// JSONata 表达式
{ "temperature": payload.sensor.temp, "unit": payload.sensor.unit }

结果:

{ "temperature": 25, "unit": "C" }

这比写 Function 节点更简洁、安全。


写在最后:msg 是你的“上下文记忆”

Node-RED 的设计哲学是:让数据自己说话
你不需要在全局变量里记“这是哪个设备的数据”,只需要在 msg.deviceId 里带上;
你不需要写复杂状态机,只需要在 msg.stage 里标记当前步骤。

理解了 msg,你就理解了 Node-RED 的“思维方式”。

动手练习
创建一个流程:

  1. Inject 节点发送 { "user": "alice", "action": "login" }
  2. 用 Change 节点把 user 提取到 msg.topic
  3. 用 Debug 节点显示完整 msg
    观察 topic 是否正确设置。

博客签名2021
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DreamLife.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值