大家好,我是翔宇。在“翔宇工作流”的实践中,我们经常会遇到这样的情况:n8n 提供的标准节点功能已经非常强大和丰富了,但在某些特定的、个性化的需求面前,似乎总是差那么一点点火候。比如,我们需要对获取到的数据进行一种非常规的格式转换,或者要执行一个标准节点并未提供的复杂计算逻辑,再或者,我们希望把多个逻辑上紧密相连的处理步骤合并到一个节点里完成,以此来让整个工作流显得更加简洁明了。
你是否也曾有过类似的经历?想要对数据进行某种特殊处理,翻遍了节点列表却找不到一个完全合适的?或者看着画布上拖得越来越长的工作流,心里琢磨着能不能把那些零碎的小步骤整合一下?这时候,很多没有编程基础的朋友可能会下意识地认为:“这肯定需要写代码才能实现吧?编程太难了,我肯定搞不定。”
其实,完全不必为此感到焦虑!n8n 平台非常贴心地提供了一个极其强大的节点——“代码 (Code)”。你可以把它想象成一把功能繁多的“瑞士军刀”。它允许我们通过编写一些简单的“指令”(是的,我们暂时不称它为“代码”,用“指令”这个词或许更容易被零基础的你所接受),就能轻松实现那些标准节点无法完成的特殊任务 。这个节点赋予了我们极高的灵活性和自由度,是 n8n 从一个“能用”的工具,蜕变为一个“好用”乃至“神器”级别平台的关键一步。请不要害怕,翔宇将会一步步引导你深入了解它。你会惊奇地发现,即使没有任何编程背景,也完全能够掌握它的核心用法,从而让你的自动化能力得到质的飞跃!
这篇教程,正是翔宇专门为像你一样零基础、但渴望进阶的 n8n 用户精心准备的。我们会从最基础的概念开始讲起,用最通俗易懂的语言解释清楚 Code 节点究竟是做什么的、它的各个配置项如何设置、以及它如何处理输入和输出数据。同时,翔宇也会毫无保留地分享自己在实战中总结出的常用场景、实用技巧以及曾经踩过的“坑”。我们的目标是让你不仅“知道”有 Code 这个节点,更能真正地“用起来”,用它去解决你在实际工作和生活中遇到的自动化难题。
教程内容简介
本教程将主要聚焦于 n8n 中两个具有强大扩展能力的节点:“代码 (Code)”节点和“执行命令 (Execute Command)”节点。我们将分章节对它们进行详细的讲解。
在本章关于“代码 (Code)”节点的部分,我们将深入探讨以下几个方面:
- 它是什么,能帮你做什么?(节点概览):明确 Code 节点的功能定位和核心价值所在。
- 节点界面上的每个选项都是什么意思,翔宇是怎么用的?(参数与配置):逐一解析节点的各项配置,并结合翔宇的实战经验给出理解和建议。
- 如何让 Code 节点读懂前面的数据,又如何让它输出你想要的结果?(数据映射与表达式):讲解如何在 Code 节点中访问和操作数据,以及输出数据的正确姿势和常见误区。
- 翔宇在哪些场景下最常用它?(应用场景):分享一些翔宇在实际工作中高频使用 Code 节点的具体场景。
- 遇到报错怎么办?翔宇教你如何快速找到问题并解决。(常见报错及解决方案):分析常见的错误类型,提供排错思路和调试技巧。
- 使用时有哪些需要特别注意的地方?(注意事项):提醒一些使用 Code 节点时需要留意的细节和最佳实践。
请跟随翔宇的思路,一步一个脚印地来学习。学习过程中,请重点理解每个概念背后的原理,不要急于复制代码。我们会尽量减少展示大段的代码,而是聚焦于讲解思路、关键点和方法。如果遇到暂时不理解的地方,可以多看几遍翔宇的比喻和解释,或者结合自己的 n8n 环境动手尝试一下。
第一章:代码 (Code) 节点详解
1. 节点概览
节点功能定位与核心价值
功能定位:
代码 (Code) 节点是 n8n 提供的一个核心功能节点,它允许用户在工作流中直接编写和执行自定义的 JavaScript 或 Python 代码片段 。它的主要作用是处理数据、实现那些标准节点无法覆盖的复杂逻辑,或者对现有功能进行个性化的扩展。值得一提的是,这个 Code 节点是 n8n 发展过程中的一个重要里程碑,它整合并取代了在早期版本(0.198.0 之前)中存在的 Function 和 Function Item 这两个节点 。如果你接触 n8n 较早,或者看到一些旧的教程,可能会遇到后两者,但现在,Code 节点是官方推荐的、功能更全面的选择。
核心价值 (翔宇解读):
翔宇认为,Code 节点的核心价值主要体现在以下几个方面:
- 无与伦比的灵活性: 这是 Code 节点最吸引人的地方。如果说 n8n 的标准节点是一个个功能明确的“专用工具”(比如锤子、螺丝刀),那么 Code 节点就好比一个“万能工具箱”,里面装满了各种基础零件,你可以根据自己的需求,随时“制造”出当前最趁手的工具。需要一种特殊的数据格式化方式?没问题。需要执行一个独特的计算公式?小菜一碟。需要根据非常复杂的条件进行判断和分支?它也能轻松应对。这种灵活性使得 n8n 能够突破预设功能的限制,满足千变万化的个性化需求。
- 强大的扩展性: 虽然 n8n 社区活跃,官方也在不断增加新的集成节点,但总有一些特定的内部系统、非主流的 API 或服务是标准节点库暂时没有覆盖的。在这种情况下,Code 节点(尤其是在可以自由配置环境的自托管 n8n 实例上,结合引入外部代码库的能力 )就成了打通这些连接的关键。它让用户有能力自行编写代码,去对接几乎任何可以通过编程方式交互的服务,极大地扩展了 n8n 的应用边界。
- 提升工作流效率: 有时候,要完成一个相对简单的任务,可能需要按顺序拖拽好几个标准节点才能实现(比如:先提取数据A,再提取数据B,然后合并A和B,最后格式化输出)。如果这些步骤之间的逻辑并不复杂,完全可以用一个 Code 节点来替代这一连串的标准节点。这样做不仅能让工作流的画布变得更加简洁、清爽,减少节点数量,还可能因为减少了节点间的切换和数据传递开销,从而提升整个工作流的执行效率。
- 平滑的学习阶梯: 对于那些希望从纯粹的“拖拉拽”式操作,向更深入理解自动化逻辑、甚至学习一点编程知识的用户来说,Code 节点提供了一个非常好的过渡桥梁。虽然本教程面向的是零基础用户,翔宇也会尽可能用简单的方式来讲解,但哪怕只是掌握 Code 节点最基础的用法,也足以让你解决问题的能力上限得到显著提升。它是在不要求你成为专业程序员的前提下,体验“用代码解决问题”的魅力的最佳途径。
输入(Input)与输出(Output)数据结构
要用好 Code 节点,首先必须理解它如何接收数据以及如何返回数据。这是使用该节点的基础,也是新手最容易出错的地方。
输入数据:
Code 节点像工作流中的其他节点一样,接收来自其上游节点传递过来的数据。这些数据通常是以一个“数组 (Array)”的形式存在的,数组中的每一个元素被称为一个“项目 (Item)”。你可以把这个输入想象成一叠文件,每一份文件就是一个 Item。
每个 Item 内部,主要包含两部分信息 :
json
对象: 这是最常用的部分,包含了结构化的数据,通常是键值对的形式。比如,上一个节点如果是读取客户信息,那么每个 Item 的json
对象里可能就包含name
,email
,city
等字段和对应的值。binary
对象: 这部分用于存放二进制数据,比如图片、PDF 文件、音频视频等。如果上游节点处理的是文件,那么文件内容就会存放在这里。
翔宇的比喻: 想象一下,上一个节点处理完一批订单后,把结果交给你。它给你的不是一堆散乱的信息,而是一个整理好的“档案袋”(输入数组)。档案袋里装着一份份独立的“订单详情单”(Items)。每张详情单上,既有文字记录的“订单信息”(json
数据),也可能附带着“商品图片”或“发票扫描件”(binary
数据)。Code 节点的工作,就是要打开这个档案袋,拿出每一份订单详情单,然后按照你的特定要求(你写的代码)来处理上面的信息或附件。
输出数据:
这是 极其关键、重中之重 的一点,翔宇必须反复强调!很多新手在使用 Code 节点时遇到的第一个“拦路虎”,就是不理解或搞错了输出数据的格式要求。Code 节点在执行完你的代码逻辑后,必须 返回一个符合 n8n 规范的数据结构,后续的节点才能正确地接收和处理。
这个规范的格式是什么呢?它同样是一个数组 (Array)。并且,这个数组中的每一个元素 (Item),都必须是一个包含 json
键的对象 (Object)。最基础、最标准的输出结构看起来是这样的:
[
{
"json": {
// 你处理后得到的数据,放在这里
// 比如: "processedField": "someValue", "anotherField": 123
}
}
// 如果你的代码生成了多个输出 Item,就继续在这里添加更多类似的对象
// , { "json": {... } }
]
翔宇再次强调: 请务必牢牢记住这个结构:[ { "json": {... } } ]
。就像你按照要求处理完了订单详情单,不能随手一扔,而是需要按照公司的规定,把处理好的信息填在一张新的、标准格式的“处理结果单”上,这张结果单必须明确标有 json
这个标签区域,然后把你处理好的数据填在这个区域里。最后,把所有处理完的结果单(可能是一张,也可能是多张)一起放进一个新的“发出档案袋”(输出数组)里,交给工作流中的下一个人(下一个节点)。如果你的输出格式不符合这个规定(比如直接返回了一个对象、或者 json
标签打错了、或者 json
后面跟的不是一个对象),那么下游节点就会“拒收”,导致工作流中断或出现错误 。许多常见的 Code 节点报错,如 “Code doesn’t return items properly” 或 “A ‘json’ property isn’t an object”,其根源都在于没有遵循这个输出格式 。理解并严格遵守这个输出数据结构,是成功使用 Code 节点的第一步,也是最重要的一步。这不仅仅是一个语法规则,它关系到 n8n 内部数据如何在节点间顺畅流动。
二进制数据 (Binary Data):
除了处理 json
数据,Code 节点也具备处理二进制数据的能力 。当上游节点传递过来包含二进制数据(如文件)的 Item 时,可以通过访问 item.binary
来获取这些数据。同样地,如果你的 Code 节点需要生成或传递二进制数据给下游节点,也需要在返回的 Item 对象中按照特定的结构来包含 binary
数据。
翔宇提示: 对于零基础的用户来说,翔宇建议初期可以先专注于掌握 json
数据的处理。先把如何正确地读取、修改、创建 json
数据以及如何保证输出格式正确这部分彻底搞明白。等熟练之后,如果遇到了需要处理文件上传下载、图片处理等涉及二进制数据的场景,再回过头来深入研究 binary
数据的处理方式也不迟。
数据结构与 Item Linking 的关系:
还有一个与数据结构相关的进阶概念叫做“项目链接 (Item Linking)”。简单来说,n8n 会尝试记录每个节点输出的 Item 是由哪个输入 Item 产生的。这种链接关系使得在下游节点中可以通过类似 $("上游节点名").item
的表达式来回溯查找原始数据 。
通常情况下,如果你的 Code 节点只是对每个输入 Item 进行一对一的转换(输入 N 个 Item,输出 N 个 Item,且顺序和对应关系不变),n8n 会自动处理好这个链接。但是,如果你的代码逻辑改变了 Item 的数量(比如,合并多个输入 Item 为一个输出 Item,或者根据一个输入 Item 生成了多个输出 Item),或者你想手动精确控制输出 Item 与输入 Item 的对应关系时,你就需要在返回的 Item 对象中,除了 json
之外,再添加一个名为 pairedItem
的属性 。这个属性的值应该设置为与该输出 Item 相关联的那个输入 Item 在输入数组中的索引(位置,从 0 开始计数)。
例如,如果你的代码基于输入的第一个 Item (索引为 0) 生成了一个新的输出 Item,那么返回时应该是:
[
{
"json": {... },
"pairedItem": 0 // 明确指出这个输出 Item 与输入的第 0 个 Item 相关联
}
]
翔宇解读:pairedItem
的概念可能对新手来说有点抽象,但它对于保证复杂工作流中数据能够正确关联非常重要。如果你发现下游节点在使用 $("你的Code节点名").item
表达式时获取不到预期的数据,很有可能就是因为你的 Code 节点改变了 Item 数量或对应关系,却没有正确设置 pairedItem
。当然,对于初学者,可以先从简单的、不改变 Item 数量和对应关系的场景入手,暂时忽略 pairedItem
。当你需要实现更复杂的聚合、拆分逻辑时,再来关注和学习如何使用它。理解 pairedItem
是从仅仅处理单个节点的数据,到开始管理跨节点数据关系的一个重要进阶。
2. 参数与配置
了解了 Code 节点的基本功能和数据结构后,我们来看看它的配置界面。打开一个 Code 节点,你会看到以下几个主要的配置选项:
Language (语言)
- 含义: 这个选项用来选择你希望在节点内编写和执行的代码所使用的编程语言。目前,n8n 的 Code 节点主要支持两种广泛使用的语言:JavaScript 和 Python 。
- 翔宇实战理解: 对于没有任何编程基础的朋友,翔宇强烈推荐从 JavaScript 开始入手。原因有几点:
- 与 n8n 表达式统一: n8n 自身的表达式系统(就是你在其他节点的参数里经常看到的
${{...}}
写法)本质上就是基于 JavaScript 的一个子集。很多 n8n 内置的变量和辅助方法(我们稍后会详细讲到)也是遵循 JavaScript 的语法和风格 。选择 JavaScript 可以让你在学习 Code 节点的同时,也加深对 n8n 表达式的理解,知识可以触类旁通,学习曲线更为平缓。 - 前端关联性: JavaScript 是网页开发的核心语言,网络上相关的入门教程、代码示例和社区资源非常丰富,遇到问题更容易找到答案。
- 内置功能: n8n 对 JavaScript 的支持更为原生,例如可以直接使用
console.log
将调试信息输出到浏览器的开发者控制台 ,这对于调试非常有帮助。 Python 当然也是一门非常强大且流行的语言,特别是在数据科学、人工智能等领域应用广泛。如果你已经具备 Python 基础,或者你需要用到某个特定的 Python 库(并且你使用的是可以安装外部库的自托管 n8n 环境),那么选择 Python 也是完全可以的。但对于初学者,翔宇还是建议先集中精力掌握一种语言,JavaScript 是更贴合 n8n 生态的选择。
- 与 n8n 表达式统一: n8n 自身的表达式系统(就是你在其他节点的参数里经常看到的
- 默认值: JavaScript
- 可选值: JavaScript, Python
- 场景推荐:
- 绝大多数场景和新手: 推荐使用 JavaScript。
- 已有 Python 基础或特定库需求 (自托管): 可以选择 Python。
Mode (模式)
- 含义: 这个设置决定了 Code 节点中的代码将如何执行,特别是当它接收到多个输入 Item 时 。它控制着代码的执行频率和处理数据的范围。
- 可选值:
Run Once for All Items
(为所有项目运行一次 – 默认模式): 这是节点的默认行为。无论上游节点传递过来多少个 Item(比如 10 个、100 个),Code 节点里的代码从头到尾只会完整地执行一次。在这次执行中,你的代码可以访问到所有传入的 Items(通常是以一个名为items
的数组的形式)。你需要自己编写逻辑(比如使用for
循环)来遍历和处理这个items
数组中的每一个或部分 Item。Run Once for Each Item
(为每个项目运行一次): 如果你选择了这个模式,那么上游节点传递过来多少个 Item,Code 节点里的代码就会独立地执行多少次。每次执行时,代码只会处理当前那一个 Item 的数据。n8n 会自动帮你处理遍历的过程。
- 翔宇实战理解: 这两种模式的选择,直接影响了你在代码中访问数据的方式以及代码的编写逻辑,适用于不同的场景。
Run Once for All Items
(默认模式):- 比喻: 想象一下,邮递员一次性给了你一大堆信件(所有输入 Items)。你需要打开这个大邮包,把所有信件都看一遍(或者按某种规则筛选、统计),然后给出一个汇总报告(比如总共有多少封信,或者计算所有信件的总邮资),或者根据所有信件的内容,重新整理生成一批新的信件发出。
- 适用场景: 这种模式非常适合需要进行 数据聚合(如计算总和、平均值、最大/最小值)、整体分析(如基于所有输入数据生成一份报告)、或者需要 根据所有输入的组合来决定最终输出 的情况。
- 数据访问: 在这种模式下,你的代码通常会与一个名为
items
的数组打交道,你需要使用类似items
,items
… 或者for (let item of items)
这样的循环语句来访问和处理每一个 Item 的数据。
Run Once for Each Item
:- 比喻: 想象一条工厂流水线,传送带上源源不断地送来一个个零件(输入 Items)。你的工位(Code 节点)负责对每一个到达你面前的零件,执行一套完全相同的标准加工步骤(你的代码逻辑),加工完成后,再把这个零件传递给流水线的下一道工序(下一个节点)。
- 适用场景: 这种模式最适合于需要 对每个 Item 进行独立、相同或相似的转换、处理或计算 的场景。比如,将每个用户的姓名都转换为大写,为每个产品价格计算税费,或者根据每个订单的状态发送不同的通知。
- 数据访问: 在这种模式下,事情变得简单多了。n8n 会自动帮你处理循环。在你的代码里,可以直接使用一个特殊的变量(通常是
item
,或者更方便的简写$json
和$binary
)来访问当前正在被处理的那个 Item 的数据。这使得代码通常更简洁,逻辑更清晰,尤其对新手更友好。
- 场景推荐:
- 新手入门: 翔宇强烈建议新手从
Run Once for Each Item
模式开始。因为它隐藏了处理数据集合的复杂性,让你能更专注于对单个 Item 的处理逻辑。 - 数据转换/格式化: 当你需要对每个输入记录应用相同的格式化规则、提取字段、或者进行简单计算时,
Run Once for Each Item
是最佳选择。 - 数据汇总/聚合/报告: 当你需要计算所有输入的总和、平均值,或者需要基于所有数据生成一个最终结果时,必须使用
Run Once for All Items
模式。 - 性能考量: 对于需要处理海量 Item 的情况,
Run Once for All Items
可能在某些方面更有效率,因为它只初始化一次代码执行环境。不过,n8n 的内部机制也可能对Run Once for Each Item
模式下的并行处理有所优化。对于大多数常见场景,优先考虑哪种模式更符合你的逻辑和易于编写。
- 新手入门: 翔宇强烈建议新手从
JavaScript/Python Code (代码编辑区)
- 含义: 这是 Code 节点的核心区域,你将在这里编写实际的 JavaScript 或 Python 代码,来实现你想要的功能。
- 翔宇实战理解: 这就是你的“魔法棒”真正施展威力的地方。无论你选择的是 JavaScript 还是 Python,都需要在这里输入相应的指令。n8n 为这个编辑区提供了一些基础的辅助功能,比如 语法高亮(不同的代码元素会用不同颜色显示,方便阅读)和一些简单的 自动补全 提示。 对于 JavaScript 代码,n8n 的执行环境支持一些现代 JavaScript 的特性,包括 Promises。这意味着如果你的代码需要执行一些异步操作(比如等待一个网络请求返回),你可以返回一个 Promise 对象,n8n 会正确地等待它完成 。 此外,一个非常实用的功能是,你可以在 JavaScript 代码中使用
console.log()
语句 。当工作流执行到这个 Code 节点时,console.log()
输出的内容会显示在你浏览器的开发者工具的 Console (控制台) 面板中。这对于 调试代码、检查变量的值、理解代码执行流程 非常非常有帮助,是翔宇在排查 Code 节点问题时最常用的手段之一。我们后面会在“常见报错及解决方案”部分详细介绍如何利用它。 - AI 辅助编码 (n8n Cloud 用户专享): 如果你使用的是 n8n 的 Cloud 版本,Code 节点还提供了一个“Ask AI”的功能 。你可以在这里用自然语言描述你想要实现的功能(目前仅支持 JavaScript),然后点击“Generate Code”,n8n 会尝试调用类似 ChatGPT 的 AI 模型来为你生成相应的代码。
- 翔宇提示: 这个 AI 辅助功能可以作为一个不错的 起点,特别是当你对某个功能不知道如何下手时,它可以帮你快速生成一个基础的代码框架。但是,需要注意几点:
- 覆盖代码: AI 生成的代码会 直接覆盖掉你当前在代码编辑区里写的所有内容。所以,最好在一个空的代码框中使用它,或者在生成前务必备份好你已有的代码。
- 并非完美: AI 生成的代码不一定是完全正确或最优的。你 必须 具备一定的阅读和理解代码的能力,去审查、测试,并根据你的具体需求进行修改和完善。
- 把它当作助手: 不要完全依赖 AI。把它看作一个能提供建议和草稿的助手,最终的代码质量和逻辑正确性还是需要由你来把关。
- 翔宇提示: 这个 AI 辅助功能可以作为一个不错的 起点,特别是当你对某个功能不知道如何下手时,它可以帮你快速生成一个基础的代码框架。但是,需要注意几点:
使用外部库 (External Libraries)
- 含义: 在某些情况下,你可能希望在 Code 节点中使用一些不是 n8n 内置的、由第三方开发的 JavaScript (npm modules) 或 Python 库,来完成更复杂的任务(比如操作 Excel 文件、调用特定的 SDK 等)。
- 翔宇实战理解: 这是 Code 节点的一个强大但有条件限制的功能。
- 仅限自托管: 使用外部库的功能 仅限于自托管 (Self-hosted) 的 n8n 实例 。如果你使用的是 n8n Cloud 服务,出于安全和环境管理的考虑,是无法导入和使用外部 npm 模块或 Python 包的。
- 需要配置: 对于自托管用户,想要使用外部库,还需要进行额外的配置:
- 安装库: 你需要先在你的 n8n 服务器环境(或者你的 Docker 容器内)安装好你想要使用的库(比如通过
npm install <package-name>
或修改 Dockerfile)。 - 设置环境变量: 你需要设置特定的 n8n 环境变量来显式允许 Code 节点加载这些库。相关的环境变量通常是
NODE_FUNCTION_ALLOW_BUILTIN
(允许加载 Node.js 内置模块) 和NODE_FUNCTION_ALLOW_EXTERNAL
(允许加载你安装的外部模块) 。具体的设置方法取决于你的 n8n 部署方式(npm, Docker 等)。
- 安装库: 你需要先在你的 n8n 服务器环境(或者你的 Docker 容器内)安装好你想要使用的库(比如通过
- 使用方式: 在配置完成后,你可以在 JavaScript 代码中使用
require('package-name')
的方式来引入已安装的 npm 模块 。注意,n8n 的 Code 节点环境不支持 ES6 的import... from...
语法,即使你的 Node.js 版本支持也不行,必须使用require()
。对于 Python,则是使用标准的import package_name
。 - 翔宇建议: 对于零基础用户,使用外部库属于比较进阶的操作,因为它涉及到服务器环境的管理和配置。初期可以先专注于利用 n8n 内置的功能和变量来解决问题。当你对 n8n 和 Code 节点都比较熟悉,并且确实遇到了必须使用外部库才能解决的问题时(且你是自托管用户),再去研究如何配置和使用它们。
Node Settings (节点设置 – 通用)
在 Code 节点的设置面板底部,还有一些通用的节点设置选项,这些选项在很多其他 n8n 节点中也能看到 。了解它们对于精细控制节点行为很有帮助:
- Always Output Data (始终输出数据):
- 含义: 默认情况下,如果 Code 节点的代码执行出错,或者代码逻辑没有
return
任何数据,那么这个节点就不会有任何输出传递给下游。如果勾选了这个选项,那么即使在上述情况下,该节点也会强制输出一个空的 Item (通常是[{ "json": {} }]
)。 - 翔宇理解: 这个选项的作用是确保无论节点内部发生什么,下游节点总能收到一个 Item,从而可能避免工作流在某个分支因为缺少输入而中断。但是,需要 非常小心 使用它。官方文档特别提醒,如果在 IF 节点之后使用这个选项,可能会导致无限循环 。翔宇建议新手 一般不要开启 这个选项,除非你非常清楚为什么需要它,并且已经考虑了它可能带来的副作用。优先通过完善代码逻辑和错误处理来确保正常的数据流。
- 含义: 默认情况下,如果 Code 节点的代码执行出错,或者代码逻辑没有
- Execute Once (执行一次):
- 含义: 这个选项与前面提到的
Mode
设置中的Run Once for All Items
有点相似,但更“极端”。如果勾选了这个选项,那么无论上游传递过来多少个 Item,Code 节点 只会执行一次,并且 只会使用第一个输入 Item 的数据 来执行。所有其他的输入 Item 都会被完全忽略。 - 翔宇理解: 它和
Mode
的区别在于:Mode: Run Once for All Items
是执行一次,但代码 可以访问到所有的 输入 Items (items
数组);而勾选Execute Once
是执行一次,且代码 只能访问到第一个 输入 Item。这个选项通常用在以下场景:- 触发型逻辑:你只需要这个节点被触发执行一次即可,不关心具体是哪个 Item 触发的,也不需要处理后续 Item。
- 仅需首项数据:你的逻辑只需要基于第一个输入 Item 的数据来完成。
- 避免重复执行:在某些特殊流程设计中,用来确保某个代码块只运行一次。
- 默认不勾选。 除非你有明确的理由,否则保持不勾选,让节点的行为由
Mode
设置来控制。
- 含义: 这个选项与前面提到的
- Retry On Fail (失败时重试):
- 含义: 如果勾选此项,当 Code 节点在执行过程中发生错误(比如代码抛出异常,或者访问外部资源失败)导致执行失败时,n8n 会自动尝试重新执行这个节点。你可以配置重试的次数和间隔时间。
- 翔宇理解: 这个选项对于提高工作流的健壮性很有帮助,特别是当你的 Code 节点内部逻辑涉及到一些可能 临时性失败 的操作时(例如:调用一个偶尔会超时的外部 API,或者访问一个网络不稳定的资源)。通过自动重试,可以增加工作流最终成功完成的概率。但是,如果失败的原因是你的 代码本身存在逻辑错误(比如语法错误、引用了不存在的变量等),那么重试多少次都是徒劳的,反而会浪费时间和资源。所以,开启它之前,要分析一下可能的失败原因。
- Notes (注释) & Display note in flow (在流程中显示注释):
- 含义: 这里提供了一个文本框,让你为这个 Code 节点添加注释或说明 。你可以写下这个节点的用途、代码的关键逻辑、注意事项等等。如果勾选了
Display note in flow
,那么你写的注释会作为节点的副标题直接显示在 n8n 的工作流画布上。 - 翔宇强烈建议:一定要养成写注释的好习惯! 特别是对于 Code 节点,里面的代码逻辑可能不像标准节点那样一目了然。几个月后,甚至几周后,当你回过头来看这个工作流时,可能连你自己都忘了当初写这段代码是为了解决什么问题,或者它是如何工作的。清晰的注释可以帮助你(以及可能接手你工作的其他人)快速理解节点的意图。勾选
Display note in flow
可以让注释在画布上一目了然,极大地提高了工作流的可读性。别小看这个功能,它对于维护复杂工作流至关重要。
- 含义: 这里提供了一个文本框,让你为这个 Code 节点添加注释或说明 。你可以写下这个节点的用途、代码的关键逻辑、注意事项等等。如果勾选了
3. 数据映射与表达式
现在我们进入 Code 节点最核心的部分:如何在代码中获取数据,以及如何构造返回的数据。这部分内容直接关系到你的代码能否正确地与 n8n 的数据流进行交互。
表达式写法 (访问数据)
在 Code 节点的代码编辑区内,你需要一种方式来“拿到”传递给它的输入数据,有时还需要获取工作流中其他节点产生的数据。n8n 为此提供了一套内置的变量和方法,让你可以在 JavaScript 或 Python 代码中方便地访问这些信息 。
访问当前节点的输入数据:
访问输入数据的方式,主要取决于你在 Mode
设置中选择的是哪种模式:
- 当
Mode
设置为Run Once for All Items
(默认模式) 时:items
: 这是最重要的变量。它是一个 JavaScript 数组 (Array),包含了所有从上游节点传递过来的 Items。你需要通过索引(如items
,items
…)或者使用循环(如for (let i = 0; i < items.length; i++)
或for (let item of items)
)来访问和处理数组中的每一个 Item。- 例如,访问第一个输入 Item 的
json
数据:items.json
- 访问第一个输入 Item 的
json
数据中名为fieldName
的字段值:items.json.fieldName
- 例如,访问第一个输入 Item 的
$input.all()
: 这个方法的作用与items
变量基本相同,也是返回包含所有输入 Items 的数组 。在某些旧版本或特定上下文中可能会看到它,但直接使用items
通常更常见和直观。$input.first()
: 获取输入数组中的第一个 Item 。相当于items
。$input.last()
: 获取输入数组中的最后一个 Item 。相当于items[items.length - 1]
。
- 当
Mode
设置为Run Once for Each Item
时:item
: 在这个模式下,n8n 会自动为你遍历输入 Items。item
这个变量就代表 当前正在被处理的那一个 Item 对象。你可以直接通过item
来访问它的json
或binary
属性。- 例如,访问当前 Item 的
json
数据:item.json
- 访问当前 Item 的
json
数据中名为fieldName
的字段值:item.json.fieldName
- 例如,访问当前 Item 的
$json
: 这是item.json
的一个 简写形式 。使用$json
可以让代码更简洁。- 例如,访问当前 Item 的
json
数据中名为fieldName
的字段值:$json.fieldName
- 例如,访问当前 Item 的
$binary
: 这是item.binary
的一个 简写形式 。用于访问当前 Item 的二进制数据。$input.item
: 这个变量与item
的作用完全相同,也是指向当前正在处理的 Item 对象 。
- 通用访问方式 (两种模式下通常都可用):
$input.params
: 获取 上一个节点 的参数设置信息 。这有时对于理解数据来源或上游节点的操作很有用。它返回的是一个对象,包含了上游节点执行时的配置参数,比如操作类型、限制数量等。$runIndex
: 代表当前工作流执行的索引。如果是手动执行或通过 Webhook 触发,通常是 0。如果是通过 Schedule 触发,每次执行会递增。$resumeIndex
: 在处理分页数据或需要暂停和恢复的工作流中可能会用到,表示恢复执行时的索引。对于初学者可以暂时忽略。
翔宇解读:
- 选择哪种访问方式,完全取决于你的
Mode
设置。 - 在
Run Once for Each Item
模式下,使用$json
来访问当前 Item 的 JSON 数据是最方便、最常用的方式。 - 在
Run Once for All Items
模式下,你打交道的主要对象是items
这个数组,你需要自己编写循环来处理它。 - 理解
item
(或$json
) 代表单个对象,而items
代表包含多个对象的数组,是区分这两种模式的关键。
访问工作流中其他节点的输出数据:
除了访问直接输入给当前 Code 节点的数据,有时你还需要获取工作流中 更早之前 某个节点产生的数据。例如,你的工作流可能是:Webhook 触发 -> 获取用户信息 -> 处理订单 -> Code 节点
。在 Code 节点处理订单时,你可能需要用到前面“获取用户信息”节点得到的用户邮箱。n8n 提供了强大的 Item Linking (项目链接) 机制和相应的表达式来实现这一点 。
$("节点名称").item
: 这是实现数据关联回溯的 核心方法 。它的作用是:找到当前 Code 节点正在处理的这个 Item,然后沿着 n8n 内部维护的链接链条,一直回溯到名为"节点名称"
的那个节点,并返回当时与当前 Item 相关联的那个 Item 对象。- 使用场景: 假设你有一个名为 “Get Customer” 的节点获取了客户信息,后面有一个 Code 节点处理该客户的订单。在 Code 节点(假设是
Run Once for Each Item
模式)中,你可以这样获取该订单对应客户的邮箱:$("Get Customer").item.json.email
。 - 前提: 这个方法依赖于 n8n 的 Item Linking 机制能够正确工作。如果中间有节点(比如另一个 Code 节点)破坏了链接(例如,没有正确设置
pairedItem
),那么这个表达式可能就无法返回预期的数据。 - 节点名称:
"节点名称"
必须是你想要获取数据的那个节点在 n8n 画布上显示的 确切名称。如果名称包含空格或特殊字符,也要照原样写。建议给节点起有意义且唯一的名称。
- 使用场景: 假设你有一个名为 “Get Customer” 的节点获取了客户信息,后面有一个 Code 节点处理该客户的订单。在 Code 节点(假设是
$("节点名称").all(branchIndex?, runIndex?)
: 获取名为"节点名称"
的那个节点 输出的所有 Items 。它返回一个包含该节点所有输出 Items 的数组。这在你需要获取某个节点的全部结果,而不是仅仅与当前 Item 关联的那一个时很有用。branchIndex
和runIndex
是可选参数,用于处理有分支或循环的复杂工作流,初学者可以暂时忽略。$("节点名称").first(branchIndex?, runIndex?)
: 获取名为"节点名称"
的节点输出的 第一个 Item 。$("节点名称").last(branchIndex?, runIndex?)
: 获取名为"节点名称"
的节点输出的 最后一个 Item 。$("节点名称").params
: 获取名为"节点名称"
的那个节点的 参数设置 ,类似于$input.params
,但作用于指定的历史节点。$("节点名称").context
: 主要用于和 Loop Over Items 节点配合,获取循环相关的一些上下文信息 。$("节点名称").itemMatching(currentNodeInputIndex)
: 这是在 Code 节点内部 使用$("节点名称").item
的一个替代方法,特别是当你在Run Once for All Items
模式下,需要根据当前正在处理的输入 Item 的索引 (currentNodeInputIndex
) 来精确地回溯查找关联项时 。例如,在循环处理items
数组时,可以用$("Get Customer").itemMatching(i)
来获取与items[i]
相关联的客户信息。
翔宇解读:
$("节点名称").item
是跨节点获取关联数据的 最常用、最重要 的方法。务必理解它的作用和依赖的 Item Linking 机制。- 给工作流中的关键节点起清晰、唯一的名称,对于使用这些表达式至关重要。
- 当你需要获取某个历史节点的全部输出,而不是与当前项关联的数据时,使用
$("节点名称").all()
。
访问其他内置方法和变量:
除了上述访问输入数据和历史节点数据的方法外,n8n 还提供了一些其他的内置变量和方法,可以在 Code 节点(以及表达式编辑器)中使用 :
$workflow
: 一个包含当前工作流元信息的对象,例如$workflow.id
(工作流 ID),$workflow.name
(工作流名称)。$execution
: 一个包含当前执行信息的对象,例如$execution.id
(本次执行的 ID),$execution.mode
(执行模式,如 ‘manual’, ‘webhook’, ‘scheduled’)。$now
: 提供了一个用于处理日期和时间的 Luxon DateTime 对象。你可以用它来获取当前时间 ($now.toISO()
),或者进行日期计算和格式化。$env
: 允许你访问在 n8n 环境中设置的环境变量。例如$env.MY_API_KEY
。$vars
: 用于访问 n8n 中设置的全局变量 (Variables)。$credentials
: 允许你访问已配置的凭证信息。例如$credentials.myApiCredential.apiKey
。$helpers
: 提供一些辅助方法,例如$helpers.sleep(milliseconds)
(让工作流暂停指定时间)。getWorkflowStaticData('node')
: 获取当前工作流的静态数据,包括节点信息 。
翔宇提示: 这些内置变量和方法极大地增强了 Code 节点的能力,让你可以访问执行上下文、环境变量、凭证等关键信息,并能方便地处理时间和执行流程控制。建议查阅 n8n 官方文档中关于 “Built-in methods and variables” 的部分 ,了解它们的详细用法。
映射技巧与常见易错点
掌握了如何访问数据后,下一步就是如何在代码中处理这些数据,并按照 n8n 要求的数据结构返回结果。这部分我们来看一些实用的技巧和需要避免的常见错误。
技巧 1: 时刻牢记返回结构!
这一点怎么强调都不为过。你的 Code 节点代码的最后一步,几乎总是 return
语句。并且,return
的 必须 是一个数组,数组中的每个元素都必须是一个包含 json
键的对象 。
最简单的例子,如果你处理完数据,得到一个名为 resultData
的对象,你需要这样返回:
JavaScript
return [{ json: resultData }];
即使你的代码逻辑上只产生一个输出 Item,也必须把它放在一个数组里。
技巧 2: 修改现有数据 (常用在 Run Once for Each Item
模式)
这是最常见的场景之一:你希望在保留大部分输入字段的同时,修改某些字段的值,或者添加一些新的字段。
JavaScript
// 假设输入的 $json 是 { "name": "Alice", "age": 30, "city": "New York" }
// 目标:将 age 加 1,并添加一个新的 status 字段
// 直接在 $json 对象上修改和添加
$json.age = $json.age + 1;
$json.status = 'active';
// $json 现在变成了 { "name": "Alice", "age": 31, "city": "New York", "status": "active" }
// 将修改后的 $json 对象包裹在标准结构中返回
return [{ json: $json }];
翔宇解读: 在 Run Once for Each Item
模式下,可以直接修改 $json
对象,然后将其返回。这是最简洁高效的方式。
技巧 3: 创建全新的数据 (常用在 Run Once for Each Item
模式)
有时,你可能不关心输入的具体结构,而是想根据输入数据,创建一个结构完全不同的新 Item。
JavaScript
// 假设输入的 $json 是 { "firstName": "Bob", "lastName": "Smith", "userId": "user123" }
// 目标:创建一个新的 Item,只包含一个 fullName 字段
// 创建一个新的 JavaScript 对象
let newItem = {
fullName: $json.firstName + ' ' + $json.lastName
};
// 将这个新创建的对象包裹在标准结构中返回
return [{ json: newItem }];
翔宇解读: 先创建一个新的、符合你要求的 JavaScript 对象,然后把它放在 [{ json:... }]
结构里返回。输入 Item 中的其他字段会被丢弃。
技巧 4: 处理多个输入 (用于 Run Once for All Items
模式)
当你的模式是 Run Once for All Items
时,你需要处理 items
数组。
JavaScript
// 假设输入的 items 是 [ { json: { value: 10 } }, { json: { value: 20 } }, { json: { value: 30 } } ]
// 目标:计算所有输入 Item 的 value 字段的总和
let sum = 0;
// 使用 for...of 循环遍历 items 数组
for (let item of items) {
// 检查 item.json 和 item.json.value 是否存在且为数字,避免出错
if (item.json && typeof item.json.value === 'number') {
sum += item.json.value;
}
}
// 创建一个包含总和的新 Item
let resultItem = {
totalSum: sum
};
// 将结果 Item 包裹在标准结构中返回
return [{ json: resultItem }];
翔宇解读: 在 Run Once for All Items
模式下,核心是使用循环(for
, forEach
, map
, reduce
等)来处理 items
数组。处理完后,根据你的需求构造一个或多个包含结果的 Item,并以标准格式返回。
技巧 5: 处理 Item Linking 与 pairedItem
如前所述,当你需要手动控制输出 Item 与输入 Item 的关联关系时(通常发生在 Item 数量改变或需要精确匹配时),就需要使用 pairedItem
属性 。
JavaScript
// 假设在 Run Once for All Items 模式下
// 输入 items =
// 目标:为每个输入 Item 生成两个相关的输出 Item
let outputItems =;
for (let i = 0; i < items.length; i++) {
let currentInputItem = items[i];
// 第一个输出 Item,关联到当前输入 Item
outputItems.push({
json: {
message: "First part related to input " + currentInputItem.json.id
},
pairedItem: i // 关联到输入数组中索引为 i 的 Item
});
// 第二个输出 Item,也关联到当前输入 Item
outputItems.push({
json: {
message: "Second part related to input " + currentInputItem.json.id
},
pairedItem: i // 关联到输入数组中索引为 i 的 Item
});
}
// 返回包含所有新生成 Items 的数组
return outputItems;
翔宇解读:pairedItem
的值是输入 Item 在 items
数组中的 索引 (index),从 0 开始。正确设置 pairedItem
可以确保下游节点在使用 $("Code Node Name").item
时能够找到正确的源数据。如果你的节点逻辑简单,输入输出一一对应,n8n 通常能自动处理好链接,你可以暂时忽略 pairedItem
。但只要涉及 Item 的合并、拆分或重新排序,就必须考虑它 。理解 pairedItem
是从基础的数据转换迈向管理复杂数据流关系的关键一步。
常见易错点:
- 返回格式错误: 这是 最高频 的错误!
- 忘记写
return
语句。 - 返回的不是一个数组
[...]
。 - 返回了数组,但数组元素不是
{ json: {...} }
格式的对象。 json
键对应的值不是一个对象{...}
,而是一个字符串、数字或其他类型 。- 翔宇忠告: 在
return
语句前,用console.log(JSON.stringify(yourReturnData))
打印一下你准备返回的数据,仔细检查其结构是否完全符合[ { json: {... } } ]
的规范!
- 忘记写
- 变量作用域问题: 特别是在
Run Once for All Items
模式下使用循环时,要注意在循环内部和外部定义的变量的作用范围(Scope),避免出现意想不到的结果。 - 异步操作处理不当: 如果你的代码中包含需要等待的操作(比如使用
fetch
或axios
调用外部 API),你需要正确地使用 JavaScript 的async/await
语法,或者确保你的函数返回一个 Promise 对象,否则 n8n 可能在异步操作完成前就继续执行了 。- 翔宇提示: 对于零基础用户,建议初期尽量避免在 Code 节点中执行复杂的异步操作。如果确实需要,务必学习并理解
async/await
的基本用法。
- 翔宇提示: 对于零基础用户,建议初期尽量避免在 Code 节点中执行复杂的异步操作。如果确实需要,务必学习并理解
- 引用不存在的属性或变量: 尝试访问一个不存在的字段,比如
$json.nonExistentField
,会导致运行时错误 (通常是TypeError: Cannot read property 'nonExistentField' of undefined
)。- 排错思路: 在访问前,先用
console.log($json)
确认数据结构,或者使用安全的方式访问,例如:if ($json && $json.nonExistentField)
或使用可选链操作符$json?.nonExistentField
(推荐)。
- 排错思路: 在访问前,先用
- 未处理
null
或undefined
值: 代码在处理可能为空的数据时,如果没有进行检查,直接对其进行操作(比如调用方法、访问属性),也容易引发错误。- 排错思路: 在使用变量前,增加判断
if (variable!== null && variable!== undefined)
或者利用 JavaScript 的空值合并运算符??
提供默认值。
- 排错思路: 在使用变量前,增加判断
- Item Linking 丢失或错误: 由于没有正确设置
pairedItem
(在需要设置的场景下),导致下游节点无法通过$("Node Name").item
正确关联到源数据 。- 排错思路: 仔细检查你的代码逻辑是否改变了 Item 的数量或对应关系。如果是,确保为每个输出 Item 都添加了正确的
pairedItem
索引。
- 排错思路: 仔细检查你的代码逻辑是否改变了 Item 的数量或对应关系。如果是,确保为每个输出 Item 都添加了正确的
- 语法错误: 代码中存在拼写错误、括号不匹配、缺少逗号、关键字使用不当等语法问题 。
- 排错思路: 仔细阅读 n8n 提供的错误信息,通常会指出错误发生的大致位置。利用代码编辑器的语法高亮功能检查。可以将代码片段复制到在线的 JavaScript 校验工具进行检查。
- 类型错误: 尝试对一个变量执行与其类型不符的操作,比如对一个字符串调用数组的方法 (
TypeError: variable.map is not a function
)。- 排错思路: 使用
console.log(typeof variableName)
检查变量的实际类型,确保操作与其类型匹配。
- 排错思路: 使用
调试的核心武器:console.log()
对于初学者来说,在没有复杂的集成开发环境 (IDE) 的情况下,console.log()
是你在 Code 节点中进行调试、理解代码执行、排查问题的 最重要、最实用的工具 。
- 用法: 在你的 JavaScript 代码中,任何你想要检查的地方,插入
console.log()
语句。 - 打印什么:
- 标记执行点:
console.log("代码执行到这里了 A");
- 检查输入数据:
console.log("收到的输入 Item:", JSON.stringify(item));
(使用JSON.stringify
可以更清晰地看到对象内部结构) - 检查变量值:
console.log("变量 myVariable 的值是:", myVariable);
- 检查中间计算结果:
console.log("计算出的折扣是:", discount);
- 检查最终返回的数据:
console.log("准备返回的数据:", JSON.stringify(outputItems));
(在return
语句前检查)
- 标记执行点:
- 查看位置: 打开你的浏览器,按 F12 (或右键检查) 打开开发者工具,切换到 “Console” (控制台) 面板。当你的 n8n 工作流执行到包含
console.log()
的 Code 节点时,你打印的信息就会显示在这里。
翔宇建议: 不要吝啬使用 console.log()
。在你感觉代码行为不符合预期时,多加几个打印语句,观察数据的变化和代码的执行路径,往往能快速定位问题所在。
4. 应用场景
理论讲了不少,现在我们来看看翔宇在实际工作中,通常会在哪些场景下祭出 Code 节点这个“法宝”。以下是一些常见的应用实例,希望能给你带来启发:
场景 1: 自定义数据格式化
- 需求:
- 某个 API 返回的日期格式是
YYYYMMDD
(例如20231026
),但你需要将其转换为更易读的YYYY-MM-DD
格式 (例如2023-10-26
) 以便存入数据库或展示给用户。 - 或者,你需要将用户的姓 (
lastName
) 和名 (firstName
) 两个字段合并成一个完整的姓名 (fullName
) 字段。 - 又或者,你需要将一段文本全部转换为小写或大写。
- 某个 API 返回的日期格式是
- 翔宇做法:
- 通常使用
Run Once for Each Item
模式。 - 利用 JavaScript 内置的字符串处理方法(如
substring()
,slice()
,toUpperCase()
,toLowerCase()
,+
运算符进行拼接)或日期处理方法(可以先将输入转为 Date 对象,再格式化输出,或者直接用字符串截取拼接)。 - 例如,处理日期
YYYYMMDD
转YYYY-MM-DD
: JavaScriptlet dateStr = $json.orderDate; // 假设输入字段名为 orderDate if (dateStr && dateStr.length === 8) { $json.formattedDate = dateStr.substring(0, 4) + '-' + dateStr.substring(4, 6) + '-' + dateStr.substring(6, 8); } else { $json.formattedDate = null; // 处理无效输入 } return [{ json: $json }];
- 例如,合并姓名: JavaScript
$json.fullName = $json.firstName + ' ' + $json.lastName; return [{ json: $json }];
- 通常使用
场景 2: 实现复杂的条件逻辑
- 需求: 根据用户的购买金额 (
amount
) 和会员等级 (level
) 来计算不同的折扣率 (discountRate
)。规则可能比较复杂:- 如果金额 > 1000 且等级是 ‘Gold’,折扣率为 0.15。
- 如果金额 > 500 且 (等级是 ‘Gold’ 或 ‘Silver’),折扣率为 0.10。
- 如果金额 > 100,折扣率为 0.05。
- 其他情况,折扣率为 0。 用 n8n 的标准 IF 或 Switch 节点来实现这种嵌套和组合的条件可能会变得非常臃肿和难以管理。
- 翔宇做法:
- 使用
Run Once for Each Item
模式。 - 在代码编辑区使用 JavaScript 的
if...else if...else
语句来清晰地表达这些条件判断。 JavaScriptlet amount = $json.amount; let level = $json.level; let discountRate = 0; if (amount > 1000 && level === 'Gold') { discountRate = 0.15; } else if (amount > 500 && (level === 'Gold' |
- 使用
| level === ‘Silver’)) {
discountRate = 0.10;
} else if (amount > 100) {
discountRate = 0.05;
}
$json.discountRate = discountRate;
$json.finalPrice = amount * (1 - discountRate); // 顺便计算最终价格
return [{ json: $json }];
```
场景 3: 进行简单的计算与聚合
- 需求:
- 计算一个订单中所有商品的总价。假设订单数据在一个 Item 中,其中包含一个
products
数组,每个元素有price
和quantity
字段。 - 或者,统计一批用户数据(多个 Items,每个 Item 代表一个用户)的平均年龄。
- 计算一个订单中所有商品的总价。假设订单数据在一个 Item 中,其中包含一个
- 翔宇做法:
- 计算订单总价(数据在单个 Item 内):使用
Run Once for Each Item
模式。 JavaScriptlet totalPrice = 0; if ($json.products && Array.isArray($json.products)) { for (let product of $json.products) { if (typeof product.price === 'number' && typeof product.quantity === 'number') { totalPrice += product.price * product.quantity; } } } $json.orderTotal = totalPrice; return [{ json: $json }];
- 计算平均年龄(数据分布在多个 Items 中):必须使用
Run Once for All Items
模式。 JavaScriptlet totalAge = 0; let validUserCount = 0; for (let item of items) { if (item.json && typeof item.json.age === 'number') { totalAge += item.json.age; validUserCount++; } } let averageAge = (validUserCount > 0)? (totalAge / validUserCount) : 0; // 返回一个包含聚合结果的新 Item return [{ json: { averageUserAge: averageAge, totalUsersProcessed: items.length } }];
- 计算订单总价(数据在单个 Item 内):使用
场景 4: 调用简单的外部服务或 API (自托管环境)
- 需求: 需要与一个没有 n8n 内置集成节点的、但提供了简单 HTTP API 的服务进行交互。或者,需要执行服务器上的一个简单脚本来辅助处理数据。
- 翔宇做法 (进阶):
- 前提: 必须是自托管的 n8n,并且已经按照前面讲的配置好了环境变量(如
NODE_FUNCTION_ALLOW_BUILTIN
或NODE_FUNCTION_ALLOW_EXTERNAL
) 。 - 调用 API: 可以使用 Node.js 内置的
https
模块,或者安装并require
一个流行的 HTTP 客户端库(如axios
)。这通常需要编写异步代码 (async/await
)。 JavaScript// 伪代码示例 (需要安装 axios: npm install axios) // const axios = require('axios'); // 需要配置环境变量允许外部模块 // async function fetchData() { // try { // const response = await axios.get('https://api.example.com/data?param=' + $json.someInput); // $json.apiResult = response.data; // } catch (error) { // $json.apiError = error.message; // } // return [{ json: $json }]; // } // return fetchData(); // 需要返回 Promise
- 调用脚本: 如果只是简单的脚本调用,有时可以通过后面要讲的
Execute Command
节点实现。但如果需要在脚本执行前后用 JavaScript 处理数据,或者脚本本身就是 Node.js 脚本,也可以在 Code 节点内通过 Node.js 的child_process
模块来执行(同样需要配置环境变量允许内置模块)。 - 翔宇提示: 这属于进阶用法,需要一定的 Node.js 基础知识。对于简单的 API 调用,优先考虑使用 n8n 功能强大的
HTTP Request
节点。
- 前提: 必须是自托管的 n8n,并且已经按照前面讲的配置好了环境变量(如
场景 5: 数据清洗与规范化
- 需求:
- 输入的电话号码格式五花八门,比如
+1-555-123-4567
,(555) 123-4567
,555.123.4567
,需要统一清洗成纯数字格式5551234567
。 - 去除用户输入文本字段两端或中间多余的空格。
- 将地址信息中的省份名称统一替换为标准缩写。
- 输入的电话号码格式五花八门,比如
- 翔宇做法:
- 通常使用
Run Once for Each Item
模式。 - 利用 JavaScript 的字符串方法,特别是
replace()
方法配合正则表达式 (RegExp),可以非常高效地完成各种复杂的清洗任务。 - 例如,清洗电话号码(去除所有非数字字符): JavaScript
let phoneNumber = $json.phone; if (typeof phoneNumber === 'string') { $json.cleanedPhone = phoneNumber.replace(/\D/g, ''); // \D 匹配任何非数字字符,g 表示全局替换 } else { $json.cleanedPhone = null; } return [{ json: $json }];
- 例如,去除首尾空格: JavaScript
let text = $json.description; if (typeof text === 'string') { $json.trimmedText = text.trim(); } else { $json.trimmedText = null; } return [{ json: $json }];
- 翔宇提示: 正则表达式是一个非常强大的文本处理工具,虽然初学有点门槛,但掌握一些基础用法对于数据清洗非常有帮助。网上有很多 JavaScript 正则表达式的教程。
- 通常使用
场景 6: 合并多个简单的处理步骤
- 需求: 工作流中有一连串的简单操作,比如:先从一个日期字符串中提取年份,然后根据年份判断是否是闰年,最后根据是否闰年给数据打上一个标记。这可能需要 2-3 个标准节点(如 Function Item, IF, Set)来完成。
- 翔宇做法:
- 如果这些步骤逻辑上紧密相关,且都不复杂,可以用一个
Run Once for Each Item
模式的 Code 节点将它们合并起来,让工作流更紧凑。 JavaScriptlet dateString = $json.eventDate; // "2024-02-15" let year = null; let isLeap = false; if (typeof dateString === 'string' && dateString.length >= 4) { year = parseInt(dateString.substring(0, 4)); if (!isNaN(year)) { // 判断闰年的简单逻辑 isLeap = (year % 4 === 0 && year % 100!== 0) |
- 如果这些步骤逻辑上紧密相关,且都不复杂,可以用一个
| (year % 400 === 0);
}
}
$json.eventYear = year;
$json.isLeapYear = isLeap;
return [{ json: $json }];
```
翔宇权衡: Code 节点虽然灵活,但也不要滥用。如果几个标准节点就能清晰地完成任务,并且逻辑不复杂,使用标准节点的可读性可能更高。只有当你觉得多个标准节点显得冗余,或者逻辑用代码表达更自然时,才考虑用 Code 节点进行合并。
以上只是翔宇常用的一些场景示例。Code 节点的潜力远不止于此,它的应用范围几乎只受限于你的想象力和 JavaScript/Python 语言本身的能力。
5. 常见报错及解决方案
在使用 Code 节点的过程中,遇到错误是在所难免的,尤其对于初学者。别担心,大部分错误都是有规律可循的。下面翔宇就列举一些最常见的报错信息,分析原因,并提供排错思路和调试技巧。
错误 1: Code doesn't return items properly
或 Code doesn't return an object
- 解析: 这是 新手遇到概率最高 的错误!它明确告诉你,你的 Code 节点执行完毕后,返回的数据格式不符合 n8n 的期望。n8n 期望收到的是一个数组
[...]
,并且数组中的每个元素都必须是{ json: {...} }
这种形式的对象。出现这个错误,通常是以下几种情况:- 你的代码根本没有
return
任何东西(比如代码有分支逻辑,某个分支忘记写return
了)。 - 你
return
了一个 单个的对象,而不是把它放在数组里。比如直接return { json: myData }
是错的,应该是return [{ json: myData }]
。 - 你
return
了一个数组,但数组里的元素 不是{ json:... }
结构。比如return [myDataObject]
是错的,应该是return [{ json: myDataObject }]
。 - 代码在执行过程中意外地返回了
undefined
。
- 你的代码根本没有
- 排错思路 (翔宇经验):
- 检查
return
语句: 确保你的代码在所有可能的执行路径上都有return
语句(除非你故意不返回任何东西)。 - 检查最外层结构: 确认
return
后面跟的是方括号[...]
,表示返回的是一个数组。 - 检查数组元素结构: 确认数组里的 每一个 元素都是花括号
{}
包裹的对象,并且这个对象 必须 有一个名为json
的键,这个键的值也是一个对象{...}
。 - 终极武器
console.log
: 在你的return
语句 之前,加上一行console.log("准备返回的数据:", JSON.stringify(yourReturnData));
,然后执行一次节点。去浏览器开发者工具的 Console 面板查看打印出来的内容,仔细核对它的结构是不是严格符合[ { json: {... } } ]
的规范。这是最直观的检查方法。 - 检查
undefined
: 确保你代码中引用的所有变量和对象属性都实际存在并且有有效值,避免因为引用了不存在的东西导致最终返回了undefined
。
- 检查
错误 2: A 'json' property isn't an object
- 解析: 这个错误是上一个错误的具体化。它说明你返回的数据结构里确实有
json
这个键了,但是这个键对应的值 不是一个对象{...}
,而是一个字符串、数字、布尔值、数组或者null
。例如,你可能错误地写成了return [{ json: "some string value" }]
或者return [{ json: 123 }]
。 - 排错思路: 检查你的
return
语句,确保json:
后面紧跟着的是用花括号{}
包裹起来的键值对数据。例如:return [{ json: { myField: "myValue" } }]
。
错误 3: 引用错误,例如 TypeError: Cannot read property 'fieldName' of undefined
或 TypeError: Cannot read properties of undefined (reading 'fieldName')
- 解析: 这个错误表示你尝试去读取一个
undefined
或null
的值的属性。通俗地说,就是你想访问某个东西(比如fieldName
)的“内部零件”,但那个东西本身就不存在,或者你找错地方了。最常见的情况是:- 你想访问
item.json.fieldName
,但item.json
本身就是undefined
(可能输入数据就没有json
字段)。 item.json
存在,但它里面并没有一个叫做fieldName
的字段。- 你尝试访问
items[i].json.fieldName
,但items[i]
超出了数组范围,或者items[i]
就没有json
属性。
- 你想访问
- 排错思路:
- 检查输入源: 在 Code 节点执行 之前,先查看上一个节点的输出结果。确认输入的数据结构和你代码中预期的一致。是不是真的有
json
字段?json
里面是不是真的有你想要的fieldName
? - 使用
console.log
确认: 在你访问那个可能出问题的属性 之前,打印一下它的父级对象。比如,如果你在访问$json.fieldName
时报错,就在前面加一句console.log("当前 $json 的值是:", JSON.stringify($json));
看看$json
到底是什么。 - 添加安全检查 (防御性编程): 这是最稳妥的方法。在访问属性前,先判断它的父对象是否存在。
- 使用
if
判断:if (item && item.json && item.json.fieldName) {... item.json.fieldName... }
- 使用 JavaScript 的 可选链 (Optional Chaining) 操作符
?.
(推荐,更简洁):let value = item?.json?.fieldName;
如果item
或item.json
是null
或undefined
,value
会被赋值为undefined
,而不会报错。 - 使用 空值合并 (Nullish Coalescing) 操作符
??
提供默认值:let value = item?.json?.fieldName?? '默认值';
如果前面的链条结果是null
或undefined
,value
会被赋值为'默认值'
。
- 使用
- 检查输入源: 在 Code 节点执行 之前,先查看上一个节点的输出结果。确认输入的数据结构和你代码中预期的一致。是不是真的有
错误 4: SyntaxError: Unexpected token...
或其他语法错误
- 解析: 这个错误意味着你的 JavaScript 或 Python 代码本身存在语法上的问题,比如括号没有配对、字符串引号没闭合、缺少逗号、关键字拼写错误、语句结尾缺少分号(虽然 JavaScript 有时会自动插入,但依赖它不是好习惯)等等。
- 排错思路:
- 仔细阅读错误信息:
SyntaxError
通常会比较明确地指出错误发生在 哪一行 或者哪个 意外的字符 (token) 附近。 - 检查代码: 根据错误提示的位置,仔细检查那一行的代码以及它附近的代码。核对括号、引号、逗号、分号等标点符号是否正确。检查关键字、变量名、函数名是否拼写无误。
- 利用编辑器高亮: n8n 的代码编辑器自带语法高亮功能,有时语法错误的地方会显示不同的颜色或波浪线提示。
- 借助外部工具: 如果自己实在看不出来,可以把代码片段复制到一些在线的 JavaScript/Python 语法检查器 (linter) 或代码格式化工具 (formatter) 中,它们通常能更精确地定位语法错误。
- 仔细阅读错误信息:
错误 5: TypeError:... is not a function
- 解析: 这个错误表示你尝试像调用函数一样去使用一个实际上不是函数的东西。比如,你可能以为变量
data
是一个数组,想用data.map(...)
方法,但实际上data
可能是一个对象、一个字符串或者undefined
,它们都没有map
这个方法。 - 排错思路:
- 确认类型: 在调用方法之前,使用
console.log(typeof variableName)
或者console.log(variableName)
来检查这个变量的实际类型和值。 - 检查拼写: 确认你调用的函数名称是否拼写正确。
- 确认来源: 确保这个变量确实被赋予了一个函数。
- 确认类型: 在调用方法之前,使用
错误 6: (仅限自托管) Error: Cannot find module '<module-name>'
- 解析: 这个错误只会在自托管的 n8n 环境下,当你尝试在 Code 节点中使用
require('<module-name>')
(JavaScript) 或import <module_name>
(Python) 来加载一个外部库时出现。它表示 n8n 在其运行环境中找不到你指定的这个模块。 - 排错思路:
- 确认环境: 再次确认你使用的是自托管的 n8n 实例,因为 n8n Cloud 不支持加载外部模块 。
- 确认安装: 确保你想要使用的模块已经正确地安装在了 n8n 的运行环境中。如果你使用 npm 运行 n8n,需要在同一个 Node.js 环境下
npm install <module-name>
。如果你使用 Docker,你可能需要基于官方 n8n 镜像创建一个自定义镜像 (Dockerfile),在里面添加安装模块的指令 。 - 确认环境变量: 检查你的 n8n 启动配置,确保已经设置了允许加载外部模块的环境变量,通常是
NODE_FUNCTION_ALLOW_EXTERNAL=true
(对于 npm 模块) 或类似的 Python 相关配置 。
错误 7: (仅限自托管) SyntaxError: 'import' and 'export' may only appear at the top level
或类似 import/export 语法错误
- 解析: 这个错误同样发生在自托管环境下尝试加载 JavaScript 模块时。它说明你使用了 ES6 的
import... from...
或export
语法,但 n8n 的 Code 节点执行沙箱(即使底层 Node.js 版本支持)目前并不支持这种模块导入/导出方式。 - 排错思路: 将你的
import
语句改为 Node.js 的 CommonJS 规范的require()
语法 。- 例如,将
import axios from 'axios';
修改为const axios = require('axios');
。
- 例如,将
调试方法与日志定位技巧
除了针对具体错误进行排查,掌握一些通用的调试方法和技巧,能让你更从容地应对 Code 节点中可能出现的各种问题。
console.log()
大法 (再次强调): 这是零基础用户的 核心调试武器 。- 怎么用: 在代码的关键节点、循环内部、条件分支、返回值之前等任何你怀疑或想了解的地方,插入
console.log()
。 - 打印什么: 打印变量的值、对象的结构 (
JSON.stringify
)、代码执行标记、函数的输入输出等。 - 看哪里: 浏览器开发者工具的 Console 面板。
- 翔宇心得: 不要怕打印得多,调试时多打 log 能帮你更快地理解代码的实际执行情况和数据的变化过程。调试结束后再清理掉不必要的 log 即可。
- 怎么用: 在代码的关键节点、循环内部、条件分支、返回值之前等任何你怀疑或想了解的地方,插入
- 简化问题: 如果你的代码比较长或逻辑复杂,而出错了,可以尝试 注释掉 (comment out) 一部分代码,只保留最核心或最简单的部分运行。如果这部分能正常工作,再逐步 取消注释 (uncomment),每次增加一小块逻辑,直到找到是哪部分代码引入了问题。这种“二分法”或“逐步排查法”能有效缩小问题范围。
- 利用节点测试功能: n8n 节点右上角通常有一个 “Test step” (测试步骤) 按钮(一个播放按钮图标) 。点击它可以只运行当前的 Code 节点(使用上一次执行时缓存的输入数据,或者你可以手动修改输入数据进行测试)。这比每次都运行整个工作流来测试代码修改的效果要快得多,非常适合快速迭代和验证。
- 查看 n8n 执行日志 (Executions): n8n 会记录每一次工作流的执行历史。在 n8n 界面的左侧导航栏找到 “Executions”。点击某一次具体的执行记录,你可以看到整个工作流的执行路径图,并且可以点击每一个节点,查看它在该次执行中的 输入数据 (Input) 和 输出数据 (Output),以及是否发生了 错误 (Error)。这是排查问题的非常重要的信息来源,特别是当你想了解节点接收到的实际数据和产生的实际结果时。如果 Code 节点报错了,错误信息通常也会显示在这里。
掌握这些常见的错误模式、排错思路和调试技巧,能让你在使用 Code 节点时更加自信。记住,遇到错误是正常的学习过程,关键在于学会如何定位和解决它们。
6. 注意事项
在使用 Code 节点为你带来强大灵活性的同时,翔宇也想提醒你一些需要注意的事项和最佳实践,以确保你的工作流稳定、高效且易于维护。
使用注意事项
- 性能考量:
- Code 节点中的代码执行是需要消耗服务器资源的。避免在节点内执行非常耗时(比如复杂的循环、大量的计算)或内存密集型(比如加载和处理非常大的数据对象)的操作,尤其是在
Run Once for Each Item
模式下处理成千上万的 Items 时。过于复杂的代码逻辑可能会显著拖慢整个工作流的执行速度。 - 如果需要进行大规模的数据处理或计算,考虑是否有更优化的方法,比如:是否可以在数据库层面完成?是否可以利用 n8n 的批处理设置 (Batching)?是否可以将计算任务分解到多个节点或子工作流中?
- Code 节点中的代码执行是需要消耗服务器资源的。避免在节点内执行非常耗时(比如复杂的循环、大量的计算)或内存密集型(比如加载和处理非常大的数据对象)的操作,尤其是在
- 安全性 (尤其在自托管环境):
- 如果你在自托管环境中使用 Code 节点,并且启用了加载外部模块 (
require
) 或与文件系统交互的功能,务必 高度关注安全风险。 - 避免执行不可信代码: 不要轻易运行从不明来源获取的代码片段。
- 谨慎处理输入: 如果代码逻辑依赖于外部输入(比如来自 Webhook 的数据),要对输入进行严格的校验和清理,防止潜在的代码注入攻击。
- 文件系统访问: 如果代码需要读写文件,确保它只在授权的、安全的目录下进行操作,避免意外修改或删除重要系统文件。
- 理解依赖: 如果使用了外部 npm 模块,确保了解该模块的功能、来源和潜在的安全隐患。
- 如果你在自托管环境中使用 Code 节点,并且启用了加载外部模块 (
- 代码可读性与维护性:
- 即使你的代码只有几行,也要注重 代码风格 和 可读性。使用有意义的变量名,保持适当的缩进和格式。
- 添加注释: 再次强调注释的重要性 。在代码中解释你的逻辑、目的和一些关键决策。利用节点设置里的 Notes 功能,从整体上说明这个 Code 节点的作用。良好的注释能让你(和他人)在未来更容易理解和维护这段代码。
- 健壮性与错误处理:
- 优秀的代码应该能够预料到并处理潜在的异常情况。比如,当预期的输入数据格式不正确、某个字段缺失、或者调用的外部服务失败时,你的代码应该如何应对?是提供一个默认值?还是跳过当前 Item 的处理?还是明确地抛出一个错误让工作流停止?
- 在代码中适当添加
try...catch
块(JavaScript)或类似的错误处理机制,可以捕获运行时错误,并进行更友好的处理或记录,而不是让整个节点直接失败。
- 避免过度使用:
- Code 节点非常灵活,但这并不意味着你应该用它来做所有事情。n8n 提供了大量功能丰富的标准节点,它们经过了良好的测试,通常更稳定、性能更好、也更容易被其他用户理解。
- 原则:优先使用标准节点。 只有当标准节点无法满足你的特定需求,或者使用多个标准节点来实现一个简单逻辑显得过于繁琐时,才考虑使用 Code 节点。不要为了用 Code 而用 Code。平衡好灵活性与标准化、可维护性之间的关系。
节点版本兼容性与历史演变
- Code 节点取代旧节点: 正如前面提到的,当前的 Code 节点是在 n8n 版本 0.198.0 之后引入的,它整合并取代了之前的 Function 和 Function Item 节点 。如果你在网上找到一些比较旧的教程或者工作流模板,可能会看到 Function 或 Function Item 节点。它们的核心思想(执行自定义代码)是类似的,但在界面、可用变量和某些功能细节上可能与现在的 Code 节点有所不同。翔宇建议,在新创建的工作流中,始终使用最新的 Code 节点。如果需要在旧工作流中维护 Function/Function Item 节点,可以查阅 n8n 官方文档中关于旧版本节点的存档说明。
- 功能持续迭代: n8n 是一个发展非常活跃的项目,它的功能在不断地更新和增强。Code 节点也不例外。未来可能会支持更多的编程语言、增加新的内置方法和变量、改进 AI 辅助功能,或者优化执行性能等。建议关注 n8n 的官方发布说明 (Release Notes),了解每个新版本可能带来的变化和新特性,以便充分利用 Code 节点的最新能力。
- JavaScript/Python 版本: Code 节点内部执行代码所使用的 JavaScript (Node.js) 或 Python 解释器的版本,通常会随着 n8n 的大版本更新而升级。虽然 n8n 会尽量保持向后兼容,但如果你使用了非常特定于某个旧版本语言特性的代码,或者依赖了某些在新版本中行为发生变化的库函数,理论上存在需要调整代码的可能性(尽管这种情况比较少见)。通常,使用稳定和标准的语言特性可以最大程度地保证兼容性。
遵循这些注意事项,可以帮助你更安全、更有效地利用 Code 节点,让它成为你 n8n 工具箱中真正得心应手的瑞士军刀。
第二章:执行命令 (Execute Command) 节点详解
在我们详细探讨了功能强大的代码 (Code) 节点之后,现在让我们将目光转向 n8n 中另一个堪称“大杀器”级别的节点:执行命令 (Execute Command)。
如果说 Code 节点是在 n8n 精心构建的“沙盒”环境中,通过编写 JavaScript 或 Python 代码来扩展工作流的能力,那么 Execute Command 节点则更为“硬核”——它允许你直接 “跳出沙盒”,在运行 n8n 程序的那台 宿主机器(你的服务器或者 Docker 容器)上,执行 系统级别的命令行指令 。
这意味着什么呢?这意味着你可以通过这个节点,去调用几乎所有你能在服务器的命令行界面 (CLI) 中运行的程序、脚本或者系统命令。比如说,你可以用它来:
- 操作服务器上的文件和目录(创建、移动、复制、删除等)。
- 调用特定的命令行工具(比如用
imagemagick
处理图片,用ffmpeg
转码视频,用git
进行版本控制)。 - 执行你自己编写的、用其他语言(如 Bash, PHP, Ruby, Perl 等)写成的脚本文件 。
- 与那些没有提供 API 但提供了 CLI 工具的系统进行交互。
能力看起来非常诱人,对吧?它几乎打通了 n8n 工作流与底层操作系统之间的壁垒。但是,翔宇必须在这里 郑重地提醒:这种强大的能力也伴随着 极高的风险!因为它不再受 n8n 沙盒的保护,而是直接对你的操作系统进行操作。
重要前提与警告:
- 仅限自托管: 这个节点 只能在自托管 (Self-hosted) 的 n8n 环境下使用。出于安全考虑,n8n 的官方 Cloud 版本是 不提供 Execute Command 节点的 。所以,如果你是 Cloud 用户,可以跳过这一章了。
- 需要环境知识: 使用这个节点,你不仅需要了解你要执行的命令本身,还需要对 n8n 运行所在的服务器环境(操作系统类型、安装了哪些工具、文件系统结构、权限配置等)或者 Docker 容器环境有一定的了解。否则,命令很可能无法按预期执行。
- 安全风险极高: 这是翔宇要反复强调的。Execute Command 节点是一把 双刃剑。用得好,它可以解决很多 Code 节点也难以处理的问题(比如调用一个编译好的二进制程序);但如果使用不当,比如执行了错误的命令,或者命令中嵌入了未经验证的用户输入,可能会轻易地破坏你的系统文件、造成数据丢失、甚至打开严重的安全漏洞。
- 谨慎使用: 翔宇强烈建议,务必谨慎使用 Execute Command 节点。在你没有完全理解你要执行的命令会产生什么效果、以及它可能带来的所有潜在风险之前,绝对不要 轻易使用它。对于零基础的用户,翔宇的建议是:优先考虑所有其他的解决方案(比如寻找是否有对应的 n8n 标准节点、社区节点,或者能否用 Code 节点实现)。只有在确认没有其他可行方法,并且你已经完全评估了风险之后,才考虑使用 Execute Command 节点。并且,一定要先在与生产环境隔离的测试环境中进行充分的验证!
总而言之,Execute Command 节点是一个面向高级用户、需要承担相应风险的工具。接下来的内容,翔宇会详细介绍它的用法和注意事项,但请你时刻牢记安全第一的原则。
1. 节点概览
节点功能定位与核心价值
功能定位:
执行命令 (Execute Command) 节点的核心功能非常直接:它允许用户在 n8n 工作流中,指定一条或多条 Shell 命令,并在运行 n8n 实例的宿主机操作系统(或者 n8n 所在的 Docker 容器内部)的默认 Shell 环境中执行这些命令 。
核心价值 :
尽管伴随着风险,但在特定场景下,Execute Command 节点的价值是无可替代的:
- 系统级交互能力: 它是打破 n8n 应用层与底层操作系统之间隔离的桥梁。当你需要工作流与操作系统本身进行交互,比如管理文件、监控系统资源、或者触发某些只有通过命令行才能完成的系统级任务时,这个节点就派上了用场。
- 利旧复用现有工具: 很多服务器上可能已经安装部署了非常强大的、经过长期验证的命令行工具(例如,图像处理库 ImageMagick 的
convert
命令,音视频处理库 FFmpeg 的ffmpeg
命令,版本控制系统 Git 的git
命令等)。通过 Execute Command 节点,你可以直接在 n8n 工作流中调用这些现成的工具,而无需寻找或开发相应的 n8n 节点,极大地复用了现有的技术资产。 - 执行非 JS/Python 脚本: n8n 的 Code 节点主要支持 JavaScript 和 Python。但如果你的团队或你个人已经积累了一些用其他脚本语言(如 Shell Script (.sh), PHP, Ruby, Perl 等)编写的、用于处理特定任务的脚本,Execute Command 节点提供了一个直接调用执行这些脚本的途径 。你只需要确保 n8n 的运行环境安装了对应脚本语言的解释器即可。
- 最后的“杀手锏”: 当你面临一个自动化需求,尝试了 n8n 所有的内置节点、搜索了社区节点、甚至觉得用 Code 节点实现起来也异常困难或不可能时(比如需要调用一个没有提供库接口的编译好的二进制程序),Execute Command 节点往往能作为最后的、几乎万能的(但务必记住是有风险的)解决方案出现。
输入(Input)与输出(Output)数据结构
输入数据:
与 Code 节点类似,Execute Command 节点也接收来自上游节点传递过来的 Items 数组。这一点很重要,因为它允许你在要执行的命令中,通过 n8n 的表达式语法,动态地引用上游传递过来的数据作为命令的参数或输入 。例如,你可以将上游节点获取到的文件名或用户 ID 作为参数传递给你调用的脚本。
输出数据:
当 Execute Command 节点执行完指定的命令后,它会产生输出 Item。其输出结构通常包含以下几个关键字段,这些字段被包裹在输出 Item 的 json
对象中:
stdout
(Standard Output – 标准输出): 这个字段包含了命令在 成功执行 过程中,打印到标准输出控制台的所有内容。通常,命令的正常执行结果会出现在这里。比如,如果你执行ls -l
命令,stdout
就会包含文件列表的文本。stderr
(Standard Error – 标准错误): 这个字段包含了命令在执行过程中,打印到标准错误输出的所有信息。通常,错误消息、警告信息 或调试信息会输出到这里。即使命令最终成功执行(exitCode
为 0),stderr
也可能包含一些警告信息。如果命令执行失败,stderr
往往是查找失败原因的关键线索。exitCode
(Exit Code – 退出码): 这是命令执行结束后返回的一个整数值。它表示命令的最终执行状态。按照惯例:exitCode
为0
通常表示命令 成功 执行完成。exitCode
为 非0
值通常表示命令在执行过程中遇到了 错误。具体的非零值含义可能因命令或脚本而异,有时可以根据退出码判断错误的类型。
输出 Item 结构示例:
假设你执行了一个命令 echo "Hello n8n"
,并且成功了,那么 Execute Command 节点可能会输出类似这样的 Item:
JSON
[
{
"json": {
"stdout": "Hello n8n\n", // 注意通常会包含换行符
"stderr": "", // 没有错误输出
"exitCode": 0 // 退出码为 0,表示成功
}
}
]
如果执行了一个不存在的命令 my_nonexistent_command
,输出可能类似:
JSON
[
{
"json": {
"stdout": "", // 没有标准输出
"stderr": "/bin/sh: my_nonexistent_command: not found\n", // 错误信息
"exitCode": 127 // 非零退出码,表示命令未找到
}
}
]
翔宇提示: 在使用 Execute Command 节点时,务必检查 exitCode
来判断命令是否真正成功执行。同时,stdout
包含了你需要的命令执行结果(如果是文本输出的话),而 stderr
则是排查问题的关键信息来源。你通常需要在 Execute Command 节点之后,连接一个 IF 节点,根据 exitCode
的值来决定工作流是继续正常处理 stdout
的结果,还是走向错误处理分支(比如记录 stderr
的错误信息)。
2. 参数与配置
Execute Command 节点的配置相对简单,主要就是指定要执行的命令。
Command (命令)
- 含义: 这是节点最核心的配置项。你需要在这个文本框中输入你想要在 n8n 宿主机(或 Docker 容器)上执行的 Shell 命令 。
- 翔宇实战理解:
- 单个简单命令: 可以直接输入基础的 Shell 命令,例如:
ls -la /data/files
(列出指定目录的文件详情)echo "Workflow started at $(date)"
(打印带有当前时间的文本)mkdir /tmp/my_workflow_output
(创建一个新目录)
- 带参数的命令: 许多命令需要接收参数来完成特定任务。
cp /data/source.txt /backup/
(复制文件)python /scripts/process_data.py --inputfile /data/input.csv --outputdir /output/
(执行 Python 脚本并传递参数)
- 引用 n8n 数据 (动态命令): 这是 Execute Command 节点与 n8n 工作流集成的关键。你可以使用 n8n 的表达式语法
${...}
或{{...}}
(旧版语法,建议用前者) 来将上游节点传递过来的数据动态地嵌入到命令字符串中 。echo "Processing user: ${{$json.userName}}"
(打印上游 Item 中的 userName 字段)wget -O /downloads/${{$json.fileName}} ${{$json.downloadUrl}}
(使用上游提供的 URL 和文件名下载文件)bash /opt/myscript.sh ${{$node["Previous Node Name"].item.json.argument}}
(调用脚本,并将上游某个节点的数据作为参数)- 安全警告: 在命令中嵌入数据时,必须极其小心 处理包含特殊字符(如空格、引号
'"
, 分号;
, 与号&
, 竖线|
, 反引号 “`, 美元符$
, 括号()
等)的输入,否则极易造成 命令注入 安全漏洞!我们稍后在“映射技巧与常见易错点”中会详细讨论如何更安全地处理。
- 执行多条命令: 如果你需要在一个节点内按顺序执行多条命令,有两种方式 :
- 使用
&&
连接: 将多条命令写在同一行,用&&
分隔。&&
的意思是“逻辑与”,表示只有前一条命令成功执行(exitCode
为 0),才会执行后一条命令。cd /data/my_project && git pull && npm run build
(先切换目录,成功后拉取代码,成功后再执行构建)
- 使用换行分隔: 直接将多条命令写在不同的行上。n8n 会将它们视为一个多行脚本来执行。 Bash
echo "Starting process..." cd /app/data process_data --input data.zip echo "Process finished."
这种方式下,即使中间某条命令失败(非 0exitCode
),后续的命令通常还是会继续执行(除非你在脚本内部做了错误检查和退出)。
- 使用
- 翔宇建议: 对于超过两三条的、或者逻辑稍微复杂一点的命令序列,翔宇强烈建议 不要 直接把所有命令都堆在这个节点的 Command 输入框里。更好的做法是:
- 将这些命令写到一个独立的 脚本文件 中(比如
my_task.sh
,my_task.py
)。 - 将这个脚本文件放置在 n8n 服务器上可以访问到的位置。
- 在 Execute Command 节点的 Command 输入框中,只写入 调用该脚本的命令,例如
bash /path/to/my_task.sh
。 - 如果需要传递动态参数给脚本,可以通过命令行参数(如
bash /path/to/my_task.sh ${{$json.param1}} ${{$json.param2}}
)或者更安全的环境变量方式传递。 这样做的好处是:命令逻辑更清晰、易于管理和维护;脚本可以在 n8n 之外独立测试;也更容易处理复杂的引用和参数传递。
- 将这些命令写到一个独立的 脚本文件 中(比如
- 关于 Shell 环境: 需要注意,你输入的命令是在 n8n 运行环境的 默认 Shell 中执行的 。在不同的操作系统上,默认 Shell 可能不同:
- Windows: 通常是
cmd.exe
(命令提示符)。 - macOS: 近期版本通常是
zsh
,旧版本可能是bash
。 - Linux: 通常是
bash
或sh
。 - n8n Docker 镜像: 官方基于 Alpine Linux 的镜像,默认 Shell 通常是
sh
(ash)。如果是基于 Debian/Ubuntu 的镜像,可能是bash
。 如果你编写的命令依赖于特定 Shell 的语法或特性(比如bash
的某些高级功能在sh
中可能不支持),你需要确保 n8n 的运行环境提供了你需要的 Shell,或者在命令中明确指定要使用的 Shell (例如bash -c "your command here"
)。 另外,像curl
这样的常用工具,在 n8n 官方的 Alpine 基础 Docker 镜像中可能 默认并未安装。如果你想在 Execute Command 节点中使用curl
,你需要像前面 Code 节点使用外部库一样,自己构建一个基于 n8n 官方镜像的 Docker 镜像,并在 Dockerfile 中添加安装curl
的指令 (例如RUN apk --update add curl
) 。
- Windows: 通常是
- PowerShell 用户注意: 如果你在 Windows 环境下运行 n8n,并且希望执行 PowerShell 命令而不是默认的
cmd
命令,Execute Command 节点本身可能无法直接满足你(因为它调用的是默认 Shell)。你可以考虑以下方法:- 在命令中尝试调用 PowerShell:
powershell -Command "& { Get-Process }"
(语法可能需要调整)。 - 寻找或使用社区提供的专门用于执行 PowerShell 的节点,例如
n8n-nodes-powershell
。这个社区节点就是为了解决 Execute Command 在 Windows 上默认使用cmd
的问题而创建的。
- 在命令中尝试调用 PowerShell:
- 单个简单命令: 可以直接输入基础的 Shell 命令,例如:
Execute Once (执行一次)
- 含义: 这个选项与 Code 节点中的同名选项作用完全相同 。
- 如果 勾选 (值为 true/on),那么无论上游节点传递过来多少个 Item,Execute Command 节点里的命令 只会执行一次。如果命令中引用了 n8n 数据,通常会使用 第一个 输入 Item 的数据。
- 如果 不勾选 (值为 false/off,默认状态),那么上游节点传递过来多少个 Item,Execute Command 节点里的命令就会 执行多少次,每次执行时,命令中引用的
${{$json...}}
会对应当前正在处理的那个 Item 的数据。
- 翔宇理解: 你需要根据你的命令的性质和目的来决定是否勾选。
- 勾选场景:
- 命令本身是全局性的操作,与单个 Item 无关(比如:检查服务器磁盘空间
df -h
,触发一次全局备份脚本)。 - 你只需要基于第一个 Item 的数据执行一次操作。
- 你想确保某个命令(比如创建目录)只执行一次,即使收到多个 Item。
- 命令本身是全局性的操作,与单个 Item 无关(比如:检查服务器磁盘空间
- 不勾选场景 (更常见):
- 你需要根据 每一个 输入 Item 的数据来执行命令(比如:为每个用户 ID 调用一个脚本
process_user.sh ${{$json.userId}}
)。 - 你需要为每个输入的文件执行转换命令。
- 你需要根据 每一个 输入 Item 的数据来执行命令(比如:为每个用户 ID 调用一个脚本
- 勾选场景:
Node Settings (节点设置 – 通用)
Execute Command 节点也包含一些通用的节点设置 ,与 Code 节点类似:
- Always Output Data: 行为同 Code 节点。即使命令执行失败(
exitCode
非 0),也强制输出一个包含stdout
,stderr
,exitCode
的 Item。同样需要谨慎使用。 - Retry On Fail: 这个选项对于 Execute Command 节点可能 更有实际意义。因为外部命令的执行可能会受到一些 临时性系统问题 的影响(比如网络抖动导致
wget
失败,或者磁盘瞬时 I/O 繁忙导致脚本出错)。在这种情况下,设置自动重试可以提高工作流的成功率。但同样地,如果失败是由于命令本身错误、权限不足或环境问题导致的,重试是无效的。 - Notes & Display note in flow:极其重要! 鉴于 Execute Command 节点的潜在风险和环境依赖性,翔宇强烈建议你必须在这里 详细记录 这个节点执行的命令是什么、它的作用、预期的输入输出、依赖的环境或工具、以及 最重要的——潜在的风险和注意事项。清晰的注释对于安全、有效地使用和维护包含 Execute Command 节点的工作流至关重要。
环境依赖性的重要性:
在使用 Execute Command 节点时,必须深刻理解其 对运行环境的强依赖性。这既是它力量的来源,也是它脆弱性的根源 。你的命令能否成功执行,完全取决于:
- 命令是否存在: 你要执行的命令或脚本,是否真的安装在了 n8n 运行的服务器或 Docker 容器里?是否在系统的
PATH
环境变量里,或者你是否使用了正确的绝对路径? - 依赖是否满足: 该命令或脚本自身是否有其他依赖(比如需要特定的库、配置文件、或者其他工具)?这些依赖在 n8n 环境中是否也满足了?
- 权限是否足够: 运行 n8n 进程的用户,是否有权限执行该命令?是否有权限读取/写入命令所涉及的文件或目录?
- 操作系统差异: 同一个命令在不同的操作系统(Linux, macOS, Windows)上,甚至在不同的 Linux 发行版上,其行为、参数、或者默认 Shell 都可能存在差异。
- Docker 环境: 如果 n8n 运行在 Docker 中,命令是在容器内部执行的,而不是在宿主机上。容器是一个隔离的环境,你需要确保容器镜像包含了所有必需的工具和依赖。
这意味着,包含 Execute Command 节点的工作流,其 可移植性通常较差。你不能轻易地将它分享给使用不同环境(尤其是 n8n Cloud)的用户。在将工作流从开发环境部署到生产环境时,也必须确保两个环境在命令、工具、权限等方面是兼容的。
3. 数据映射与表达式
Execute Command 节点的核心交互点在于如何在命令字符串中动态地使用来自 n8n 工作流的数据。
表达式写法 (在命令中引用数据)
如前所述,你可以在 Command
输入框中使用 n8n 的表达式语法 ${{...}}
来嵌入动态数据。
- 访问当前 Item 数据 (通常在 Execute Once 未勾选时):
${{$json.fieldName}}
: 获取当前 Item 的json
对象中名为fieldName
的字段值。${{item.json.fieldName}}
: 效果同上。
- 访问其他节点数据:
${{$node["节点名称"].item.json.fieldName}}
: 获取名为 “节点名称” 的节点输出的、与当前 Item 相关联的那个 Item 的json
数据中的fieldName
字段值。${{$node["节点名称"].first().json.fieldName}}
: 获取 “节点名称” 节点输出的第一个 Item 的数据。${{$node["节点名称"].all()}}
: 获取 “节点名称” 节点输出的所有 Items (这通常会返回一个对象数组的字符串表示,直接在命令行中使用可能需要后续处理)。
- 访问其他内置变量:
${{$now.toISODate()}}
: 获取当前日期,格式如2023-10-27
。${{$env.MY_VARIABLE}}
: 获取名为MY_VARIABLE
的环境变量的值。${{$workflow.id}}
: 获取当前工作流的 ID。
示例:
假设上游节点 “Get File Info” 输出的 Item 包含 { json: { filePath: "/data/input.txt", userId: "user-abc" } }
- 示例 1: 处理特定文件
process_file.sh ${{$json.filePath}}
(执行脚本 process_file.sh,并将 /data/input.txt 作为第一个参数传递给它) - 示例 2: 以用户 ID 创建目录
mkdir /output/${{$json.userId}}
(创建一个名为 /output/user-abc 的目录) - 示例 3: 结合其他节点数据和当前时间
log_event –user ${{$json.userId}} –file ${{$node[“Get File Info”].item.json.filePath}} –time ${{$now.toISO()}}
(调用 log_event 命令,并传递多个动态参数)
映射技巧与常见易错点
虽然在命令中使用表达式看起来很简单,但这里恰恰是 最容易出错、也是风险最高 的地方。务必注意以下技巧和易错点:
技巧 1: 处理特殊字符以防止命令注入 (安全第一!)
这是 使用 Execute Command 节点时最重要的安全考量。如果你的命令中嵌入的数据(特别是来自用户输入或外部系统的数据)包含 Shell 的特殊字符(如空格、;
, &
, |
, >
, <
, “`, $
, !
, \
, (
, )
, {
, }
等),并且你没有进行恰当的处理,攻击者可能通过构造恶意输入,让你执行非预期的、甚至是破坏性的命令。这就是 命令注入 (Command Injection) 漏洞。
翔宇强烈建议采取以下措施来降低风险:
- 永远不要信任外部输入: 对任何将要嵌入命令的数据,都要抱有怀疑态度。
- 优先使用环境变量传递: 将动态数据作为环境变量传递给脚本,通常比直接拼接到命令字符串中更安全。
- 示例:Bash
# 在 Command 输入框中: export USER_INPUT="${{$json.userInput}}"; export FILE_PATH="${{$json.filePath}}"; bash /path/to/my_safer_script.sh
然后在你的my_safer_script.sh
脚本内部,通过读取环境变量$USER_INPUT
和$FILE_PATH
来获取这些值。这种方式下,即使输入包含特殊字符,它们也只是作为环境变量的值存在,通常不会被 Shell 直接解释为命令的一部分。
- 示例:Bash
- 使用脚本语言内置的参数处理: 如果你调用的是 Python, Node.js 等脚本,利用这些语言提供的库来安全地处理命令行参数,而不是依赖 Shell 的解析。
- 严格的清理和转义 (下策,复杂且易错): 如果你 必须 将数据直接嵌入命令字符串,你需要对数据进行极其严格的清理和转义,移除或替换掉所有可能的 Shell 特殊字符。但这非常困难,很容易遗漏某些情况,对于零基础用户来说几乎是不可能完美做到的。翔宇强烈不推荐这种方式。
- 最小化使用动态数据: 尽可能地减少在命令中直接嵌入动态数据,特别是不可信来源的数据。
翔宇的底线:最安全的做法是,尽量避免将复杂的、或者来自外部不可信来源的数据,直接拼接到 Execute Command 节点的命令字符串中。 如果需要传递数据,优先考虑环境变量方式。
技巧 2: 获取和处理命令输出
- 标准输出 (
stdout
): 命令的正常结果通常在stdout
字段中。它是一个字符串。 - 处理 JSON 输出: 如果你调用的命令或脚本输出的是 JSON 格式的字符串,你可能需要在 Execute Command 节点之后连接一个 Code 节点,使用
JSON.parse(stdout)
将其转换回可以在 n8n 中直接使用的 JSON 对象。记得要处理JSON.parse
可能失败的情况(比如stdout
不是有效的 JSON 格式)。 JavaScript// 在后续的 Code 节点中 let outputJson = null; try { outputJson = JSON.parse($json.stdout); // $json 来自 Execute Command 节点的输出 } catch (e) { console.error("Failed to parse stdout as JSON:", e); // 可以设置错误标记或默认值 outputJson = { error: "Invalid JSON output from command" }; } // 使用 outputJson... return [{ json: outputJson }];
- 处理多行输出: 如果
stdout
包含多行文本,它会作为一个包含换行符 (\n
) 的单字符串出现。你可能需要使用 Code 节点和字符串的split('\n')
方法将其拆分成行数组进行处理。
技巧 3: 检查执行结果 (exitCode
和 stderr
)
- 必须检查
exitCode
: 不要假设命令总能成功执行。一定要检查输出 Item 中的exitCode
字段。exitCode === 0
才表示成功。 - 利用 IF 节点分流: 在 Execute Command 节点之后,通常应该连接一个 IF 节点。设置条件为
${{$json.exitCode}} === 0
。将成功路径连接到 True 输出,将失败处理逻辑(比如记录错误、发送通知)连接到 False 输出。 - 分析
stderr
: 当exitCode
非 0 时,stderr
字段通常包含了具体的错误信息,是诊断问题的关键。将stderr
的内容记录下来或发送给管理员。
常见易错点:
- 命令注入漏洞: (重复强调!) 没有正确处理包含特殊字符的输入数据,导致执行了恶意命令。这是 最严重 的错误。
- 命令未找到 (
command not found
或not found
):- 原因: 你要执行的命令在 n8n 的运行环境(服务器或 Docker 容器)中不存在,或者它所在的目录没有被包含在系统的
PATH
环境变量中。 - 解决:
- 确认命令已在该环境安装。
- 使用命令的绝对路径(例如
/usr/local/bin/mycommand
而不是mycommand
)。 - 如果是 Docker 环境,确认基础镜像包含该命令,或通过 Dockerfile 安装了它。可以通过
docker exec -it <container_id> which <command_name>
来检查命令是否存在及路径。
- 原因: 你要执行的命令在 n8n 的运行环境(服务器或 Docker 容器)中不存在,或者它所在的目录没有被包含在系统的
- 权限不足 (
Permission denied
):- 原因: 运行 n8n 进程的用户没有足够的权限来执行该命令文件,或者没有权限读取/写入命令所操作的文件或目录。
- 解决:
- 检查并修改文件/目录的权限 (使用
chmod
,chown
)。确保 n8n 用户有执行 (x
) 权限(对命令文件)和读/写权限(对相关数据文件/目录)。 - 确保 n8n 不是以权限过低的用户运行的 (但也 绝不 应该以 root 用户运行!)。
- 检查并修改文件/目录的权限 (使用
- 路径问题:
- 原因: 命令中使用了相对路径,但命令实际执行时的“当前工作目录”可能不是你预期的那样,导致找不到文件或目录。
- 解决:
- 在命令中尽量使用 绝对路径。
- 或者,在执行主要命令之前,先使用
cd /path/to/expected/directory
命令切换到正确的工作目录 。
- 输出缓冲区超限 (
Error: stdout maxBuffer length exceeded
):- 原因: 你执行的命令产生了大量的标准输出 (
stdout
) 内容,超过了 n8n 节点内部用于缓存输出的缓冲区大小限制。 - 解决:
- 修改命令以减少输出: 查看该命令是否有提供参数来限制输出内容(比如只输出摘要、减少冗余信息)。
- 使用管道过滤: 如果可能,将命令的输出通过管道 (
|
) 传递给其他命令(如grep
,head
,tail
,awk
,sed
)来过滤掉不需要的部分,只保留你需要的数据。 - 重定向到文件: 将命令的标准输出重定向到一个临时文件中 (
my_command > /tmp/output.txt
),然后在 Execute Command 节点之后,使用 n8n 的 “Read Binary File” 或 “Read File” 节点来读取这个文件的内容。这是处理大量输出的常用方法。
- 原因: 你执行的命令产生了大量的标准输出 (
- 表达式语法错误: 在
${{...}}
内部的 n8n 表达式本身写错了,导致无法正确解析和替换。- 解决: 仔细检查表达式的语法,确保字段名、节点名、函数调用都正确无误。可以在 n8n 的表达式编辑器中先测试表达式是否能正常工作。
- 引号问题: 在命令字符串中混合使用单引号 (
'
) 和双引号 ("
),以及需要嵌入包含引号的数据时,很容易出错。Shell 对不同引号的处理方式不同(双引号内会进行变量替换和转义,单引号内通常不会)。- 解决: 仔细规划引号的使用。如果传递给命令的参数可能包含空格或特殊字符,通常需要用引号将其包裹起来。如果参数本身就包含引号,情况会更复杂,这时使用环境变量传递通常是更简单的选择。
4. 应用场景 (翔宇常用场景)
虽然 Execute Command 节点风险较高,但在某些场景下,它的确是解决问题的利器。以下是翔宇在自托管环境中,经过审慎评估后,可能会使用到它的一些场景:
场景 1: 调用特定的、无 n8n 节点的命令行工具
- 需求:
- 工作流接收到用户上传的图片,需要将其统一转换为 WebP 格式,并调整尺寸。服务器上安装了强大的 ImageMagick 工具包。
- 工作流需要对一个视频文件进行转码,或者提取音频。服务器上安装了 FFmpeg。
- 需要对一个 PDF 文件进行加密或合并。服务器上有相应的 PDF 处理命令行工具。
- 翔宇做法:
- 前提: 确认 ImageMagick / FFmpeg / PDF 工具已在 n8n 运行环境(服务器或 Docker 容器)中正确安装,并且 n8n 进程有权限调用它们,以及读写相关文件。
- 命令示例 (ImageMagick):
convert ${{$json.inputImagePath}} -resize 800x600 -quality 80 ${{$json.outputWebPPath}}
(注意:${{$json.inputImagePath}}
和${{$json.outputWebPPath}}
需要是 n8n 可访问的文件系统路径。需要仔细处理路径和文件名,避免注入。) - 命令示例 (FFmpeg):
ffmpeg -i ${{$json.inputVideoPath}} -vn -acodec copy ${{$json.outputAudioPath}}
(提取视频中的音频流,不重新编码。同样要注意路径和注入风险。) - 关键: 仔细阅读所调用工具的文档,了解其参数用法。严格控制输入的文件路径和名称,防止被恶意利用。
场景 2: 执行自定义的、非 JS/Python 脚本
- 需求: 公司内部有一个使用 Perl 编写的脚本,用于根据特定规则生成报告。希望在 n8n 工作流中,当收到特定数据时,自动触发这个 Perl 脚本。
- 翔宇做法:
- 前提: 确认 n8n 运行环境安装了 Perl 解释器。脚本文件 (
generate_report.pl
) 放在服务器上 n8n 可访问的位置。 - 命令示例:
perl /opt/scripts/generate_report.pl --data "${{$json.payload}}" --output /reports/report_${{$workflow.executionId}}.txt
(调用 Perl 脚本,并通过命令行参数传递数据。注意这里使用了双引号包裹${{$json.payload}}
,假设 payload 可能包含空格。但如果 payload 本身包含双引号或 Shell 特殊字符,依然有风险。更安全的方式是在脚本内部接收标准输入,或者通过环境变量传递。)
- 前提: 确认 n8n 运行环境安装了 Perl 解释器。脚本文件 (
场景 3: 与 Git 进行交互 (例如工作流备份)
- 需求: 希望在每次手动保存 n8n 工作流后,或者定期地,自动将 n8n 工作流的 JSON 定义文件提交到 Git 仓库进行版本控制和备份 。
- 翔宇做法 (进阶):
- 步骤:
- (可能需要手动或通过 n8n API/CLI) 将需要备份的工作流导出为 JSON 文件,保存到服务器上 Git 仓库所在的目录(或其子目录)下。n8n CLI 提供了
export:workflow
命令 。 - 在 Execute Command 节点中执行一系列 Git 命令。
- (可能需要手动或通过 n8n API/CLI) 将需要备份的工作流导出为 JSON 文件,保存到服务器上 Git 仓库所在的目录(或其子目录)下。n8n CLI 提供了
- 命令示例:Bash
cd /path/to/n8n_workflows_git_repo && \ git pull origin main && \ git add workflows/*.json && \ git commit -m "Automated workflow backup triggered by n8n - ${{$now.toISO()}}" && \ git push origin main
- 前提:
- 服务器上安装了 Git。
- n8n 运行用户配置了访问 Git 仓库的权限(比如通过 SSH Key,并且 SSH Agent 正在运行,或者配置了 HTTPS Credential Helper)。
- Git 仓库已初始化并与远程仓库关联。
- 注意: 这套命令比较复杂,需要对 Git 有一定了解。错误处理也很重要(比如
git pull
失败怎么办?git commit
没有变更怎么办?git push
被拒绝怎么办?)。
- 步骤:
场景 4: 执行特定的文件系统操作
- 需求:
- 需要将工作流处理生成的文件,从临时目录移动到最终的归档目录。
- 需要根据输入数据动态地创建一系列目录结构。
- 需要删除一些过期的临时文件。
- n8n 内置的 “Move/Rename File or Folder”, “Create Folder”, “Delete File or Folder” 节点可能在某些特定场景下(如权限问题、需要更复杂的通配符操作等)无法满足需求。
- 翔宇做法:
- 使用基础的 Shell 文件操作命令:
cp
(复制),mv
(移动/重命名),mkdir
(创建目录),rm
(删除)。 - 命令示例:
mv ${{$json.tempFilePath}} /data/archive/${{$json.archiveFileName}}
mkdir -p /data/project/${{$json.projectName}}/raw_data
(-p
参数可以递归创建目录)rm /tmp/temp_file_${{$execution.id}}.dat
- 极度警告:使用
rm
命令时必须万分小心! 尤其是带有-r
(递归) 和-f
(强制) 参数时。一旦误删,数据可能无法恢复。务必确保路径和文件名是绝对正确的,并且在生产环境使用前经过严格测试。
- 使用基础的 Shell 文件操作命令:
场景 5: 与只提供 CLI 的旧系统或内部工具交互
- 需求: 公司有一个内部开发的旧系统,没有提供现代的 API 接口,但提供了一个命令行工具 (
legacy_tool
) 用于查询信息或执行操作。需要在 n8n 工作流中与这个系统交互。 - 翔宇做法:
- 通过 Execute Command 节点调用这个
legacy_tool
。 - 命令示例:
legacy_tool --query-user ${{$json.userId}} --output-format json
- 处理输出: 如果该工具能输出结构化数据(如 JSON),则在后续节点中解析
stdout
。如果输出是纯文本,可能需要用 Code 节点或配合正则表达式进行解析,提取所需信息。 - 处理输入: 如果需要向该工具提供复杂输入,可能需要先将输入数据写入临时文件,再将文件名作为参数传递给工具。
- 通过 Execute Command 节点调用这个
以上场景展示了 Execute Command 节点的潜在用途。但翔宇再次强调,在决定使用它之前,请务必:
- 确认没有更安全的替代方案 (内置节点? 社区节点? Code 节点? HTTP Request? SSH 节点? )。
- 充分理解命令的作用和风险。
- 严格控制和验证输入数据,防止命令注入。
- 在隔离的测试环境中充分测试。
5. 常见报错及解决方案
与 Code 节点类似,Execute Command 节点也可能因为各种原因执行失败。了解常见的错误及其原因,有助于快速定位和解决问题。
错误 1: Command failed: <command> /bin/sh: <command>: not found
或类似的 “command not found” 错误
- 解析: 这是 非常常见 的错误。它意味着 n8n 尝试执行你指定的命令时,操作系统(或 Shell)在其搜索路径 (PATH) 中找不到这个命令的可执行文件。
- 排错思路:
- 检查命令拼写: 确认你输入的命令名称是否完全正确,没有拼写错误。大小写在 Linux/macOS 上通常是敏感的。
- 确认命令是否已安装: 登录到 n8n 运行的服务器或进入 Docker 容器,手动尝试执行该命令,或者使用
which <command>
(Linux/macOS) 或where <command>
(Windows) 来查看命令是否存在以及它的完整路径。如果命令确实没有安装,你需要先安装它。 - 使用绝对路径: 如果命令安装了,但它所在的目录不在系统的
PATH
环境变量中,你需要使用命令的 完整绝对路径 来调用它,例如/usr/local/bin/my_command
而不是my_command
。 - 检查 Docker 环境: 如果 n8n 运行在 Docker 中,确认你使用的 n8n Docker 镜像包含了你需要的命令。官方 Alpine 镜像非常精简,可能缺少很多常用工具。你可能需要:
- 切换到基于 Debian/Ubuntu 的 n8n 镜像 (通常包含更多工具)。
- 或者,基于官方镜像创建一个自定义 Dockerfile,在里面使用包管理器(如
apk add...
for Alpine,apt-get install -y...
for Debian/Ubuntu)安装你需要的命令,然后构建并使用你自己的镜像 。 - 你可以使用
docker ps
找到 n8n 容器 ID,然后用docker exec -it <container_id> /bin/sh
(或/bin/bash
) 进入容器内部进行检查 。
错误 2: Error: stdout maxBuffer length exceeded
- 解析: 这个错误发生在你要执行的命令产生了 非常大量的标准输出 (stdout),其大小超过了 n8n 为 Execute Command 节点内部设置的用于存储输出内容的缓冲区限制。
- 排错思路: 核心思想是 减少命令的输出量。
- 命令自身选项: 查阅你所执行命令的文档,看它是否提供了可以 限制输出 或 过滤结果 的参数选项。比如,只输出摘要信息、只输出前 N 行、或者根据条件过滤输出等。
- 使用 Shell 管道 (|) 过滤: 将命令的输出通过管道传递给其他 Shell 工具进行处理,只保留你需要的部分。常用工具包括:
grep
: 过滤包含特定模式的行。head
: 只取输出的前 N 行。tail
: 只取输出的后 N 行。awk
/sed
: 进行更复杂的文本处理和提取。- 示例:
list_all_files | grep ".log"
(只保留包含 “.log” 的行);generate_report --verbose | head -n 20
(只取详细报告的前 20 行)。
- 重定向到文件 (推荐用于大量输出): 这是处理大量输出最稳妥的方法。将命令的标准输出重定向到一个临时文件中,而不是让 n8n 直接捕获。
- 示例:
my_command_with_huge_output > /tmp/command_output.txt
- 然后在 Execute Command 节点 之后,添加一个 n8n 的 “Read Binary File” 或 “Read File” 节点,去读取
/tmp/command_output.txt
文件的内容。这样就绕过了节点内部的缓冲区限制。记得在处理完文件后,可能需要再加一个 Execute Command 节点来删除这个临时文件 (rm /tmp/command_output.txt
)。
- 示例:
错误 3: 权限错误,例如 Permission denied
- 解析: 这个错误通常意味着运行 n8n 进程的用户 没有足够的权限 来执行你指定的命令,或者没有权限读取/写入该命令所要访问的文件或目录。
- 排错思路:
- 检查命令文件权限: 确认命令文件本身对于 n8n 用户是否具有 执行权限 (
x
)。可以使用ls -l /path/to/command
查看权限。 - 检查相关文件/目录权限: 确认 n8n 用户对命令需要读取或写入的文件和目录是否具有相应的 读 (
r
) / 写 (w
) / 执行 (x
for 目录) 权限。 - 修改权限 (需要管理员): 如果权限不足,你需要以管理员身份登录服务器,使用
chmod
(修改权限) 或chown
(修改所有者/组) 命令来调整权限。务必遵循 最小权限原则,只授予必要的权限。 - 检查 n8n 运行用户: 确认 n8n 是以哪个用户身份运行的。避免以 root 用户运行 n8n。如果需要执行需要较高权限的命令,考虑使用
sudo
(见下一点,但要极其谨慎)。 - SELinux/AppArmor: 在某些 Linux 发行版上,像 SELinux 或 AppArmor 这样的强制访问控制系统也可能阻止命令的执行,即使文件权限看起来是正确的。这属于比较高级的系统管理范畴。
- 使用
sudo
(极不推荐,风险高): 理论上,可以配置系统的sudoers
文件,允许 n8n 用户无密码执行 特定的、有限的 命令。但这会引入严重的安全风险,因为一旦 n8n 被攻破,攻击者就能以更高权限执行命令。翔宇强烈不建议这样做,除非你完全理解其风险并采取了严格的限制措施。
- 检查命令文件权限: 确认命令文件本身对于 n8n 用户是否具有 执行权限 (
错误 4: 命令因参数错误或逻辑错误而执行失败 (非 0 exitCode
)
- 解析: 命令本身被找到了,权限也足够,但命令在执行过程中出错了。这可能是因为你传递给命令的参数无效、命令需要的某个文件不存在、或者命令/脚本内部的逻辑执行失败。在这种情况下,
exitCode
通常是一个非零值,并且stderr
字段会包含具体的错误信息。 - 排错思路:
- 仔细阅读
stderr
: 这是 最重要的线索!stderr
中通常会告诉你命令失败的原因。仔细阅读错误消息。 - 手动执行命令: 复制 Execute Command 节点最终执行的 完整命令字符串(包括用 n8n 表达式替换后的参数),登录到 n8n 服务器或进入 Docker 容器,在命令行界面 手动执行 这个命令。观察是否能复现错误,以及命令行直接显示的报错信息是否更详细。
- 检查传递的参数: 仔细核对通过 n8n 表达式传递给命令的参数值是否符合命令的要求。格式对吗?值在有效范围内吗?路径存在吗?特别是要注意包含空格、引号或其他特殊字符的参数是否被正确传递和处理了。
- 阅读命令/脚本文档: 查阅你调用的那个命令或脚本的官方文档或帮助信息 (
man command
,command --help
等)。了解它的参数要求、用法示例以及可能的错误代码含义。 - 检查脚本内部逻辑: 如果你调用的是自己编写的脚本,那问题可能出在脚本内部的逻辑错误。你需要在脚本内部添加打印/日志语句,或者使用相应语言的调试工具来排查。
- 仔细阅读
通过理解这些常见的错误类型和排查方法,你应该能够更有效地解决在使用 Execute Command 节点时遇到的问题。记住,耐心和细致是排错的关键。
6. 注意事项
最后,翔宇再次不厌其烦地强调一些使用 Execute Command 节点时必须牢记的注意事项,这些关乎你的系统安全和工作流的稳定性。
使用注意事项
- 安全!安全!安全! (最高优先级) [Insight 8]
- 严防命令注入: 永远,永远不要将来自不可信来源(例如,公开的 Webhook、用户填写的表单等)的数据,未经严格验证和清理,就直接拼接到命令字符串中。这是导致安全灾难的最常见途径。优先使用环境变量传递数据。
- 遵循最小权限原则: 确保运行 n8n 服务的操作系统用户,只拥有执行其工作所必需的最小权限。它不应该能够执行任意命令,也不应该能够读写关键的系统文件或敏感数据目录。
- 避免高风险操作: 尽量避免使用 Execute Command 节点来执行那些可能对系统产生重大影响的操作,例如:修改系统配置文件、管理用户账户、格式化磁盘、执行
rm -rf /
(千万不要!) 等。对于这类操作,应该使用更安全的、专门的管理工具或流程。 - 审计与监控: 如果在生产环境中使用 Execute Command 节点执行关键任务,考虑增加额外的审计日志或监控措施,以便追踪命令的执行情况和潜在的异常行为。
- 深刻理解环境依赖性: [Insight 7]
- 始终记住,这个节点的行为 完全依赖于 n8n 运行所在的具体环境(操作系统、已安装的工具、库版本、Shell 类型、环境变量、文件系统结构、权限设置等)。
- 在开发、测试、生产环境之间迁移包含此节点的工作流时,必须确保目标环境满足命令执行所需的所有依赖条件,否则工作流必然失败。环境一致性管理是使用此节点的关键挑战。
- 可移植性极差:
- 由于其对特定环境的强依赖性,包含 Execute Command 节点的工作流几乎没有可移植性。你无法轻易地将其分享给使用 n8n Cloud 的用户,也很难保证它能在其他人的自托管环境中直接运行。在团队协作或分享工作流时,需要特别注意这一点。
- 性能开销:
- 每次执行 Execute Command 节点,都需要 n8n 启动一个新的子进程来运行 Shell 命令。这个过程相比于在 n8n 内部执行代码(如 Code 节点)会有额外的性能开销。如果在一个循环中或者以非常高的频率执行此节点,可能会对系统资源造成压力,影响工作流的整体性能。
- 必须进行错误处理:
- 绝对不能假设命令总能成功执行。你的工作流 必须 包含检查
exitCode
和处理stderr
的逻辑。使用 IF 节点根据exitCode
进行分流处理,记录或报告错误信息,是保证工作流健壮性的基本要求。
- 绝对不能假设命令总能成功执行。你的工作流 必须 包含检查
- 优先寻找替代方案: [Insight 9]
- 在使用 Execute Command 节点之前,请务必 再三确认,是否真的没有更安全、更标准、更符合 n8n 理念的替代方案?
- n8n 内置节点: 是否有现成的节点可以完成任务?(比如文件操作、HTTP 请求等)
- 社区节点: 在 n8n 社区市场 (n8n.io/integrations) 搜索一下,是否有其他人已经开发了针对你所需工具或服务的节点?(例如前面提到的 PowerShell 节点 )
- Code 节点: 如果任务主要是数据处理或逻辑运算,或者可以通过 JavaScript/Python 库实现与外部服务的交互(在自托管环境中),Code 节点通常是更安全、更可控的选择。
- HTTP Request 节点: 如果你需要与一个提供 HTTP API 的服务交互,功能强大的 HTTP Request 节点是首选,而不是试图用
curl
命令。 - SSH 节点: 如果你需要在 另一台远程服务器 上执行命令,应该使用 n8n 内置的 SSH 节点 ,而不是试图在 n8n 服务器本地执行
ssh
命令。SSH 节点提供了更安全的凭证管理和连接方式。
- 原则: 将 Execute Command 节点视为 最后的手段,而不是首选方案。
- 在使用 Execute Command 节点之前,请务必 再三确认,是否真的没有更安全、更标准、更符合 n8n 理念的替代方案?
节点版本兼容性与历史演变
- 核心功能相对稳定: Execute Command 节点的核心功能——执行 Shell 命令——自推出以来变化不大。其基本用法和参数保持了较好的稳定性。
- 主要变化来自环境: 对用户影响最大的变化,往往不是节点本身,而是 n8n 运行环境 的变化。例如:
- n8n 官方 Docker 镜像的基础操作系统更新(比如从 Alpine 3.18 更新到 3.19),可能会带来某些内置工具的版本变化,或者默认 Shell 行为的细微调整。
- Node.js 版本的更新(n8n 依赖 Node.js 运行)理论上也可能影响到子进程的创建和管理,但通常 n8n 会处理好兼容性。
- 始终牢记自托管限制: 这个节点仅适用于自托管 n8n 的事实 ,在可预见的未来不太可能改变,因为这涉及到核心的安全模型。
请务必将这些注意事项牢记在心。Execute Command 节点赋予了你直接操控底层系统的能力,善用它能解决难题,但滥用或疏忽则可能带来麻烦甚至灾难。安全、谨慎、负责任地使用它,才是正道。
总结
教程回顾, 节点用途总结, 使用技巧强调
好了,我们一起用了不少时间,深入探索了 n8n 中两个极具威力但也需要小心使用的节点:代码 (Code) 和 执行命令 (Execute Command)。希望通过翔宇的讲解,你对它们不再感到神秘和畏惧。
我们回顾一下:
- 我们了解了这两个节点各自的 定位:Code 节点是在 n8n 沙盒内用代码扩展逻辑和处理数据,而 Execute Command 节点是跳出沙盒直接与宿主系统交互。
- 我们详细解析了它们的 配置参数,特别是 Code 节点的两种执行模式 (
Mode
) 和 Execute Command 节点对环境的依赖。 - 我们重点学习了如何在节点内 访问和映射数据,尤其是 Code 节点严格的输出格式
[{ json: {...} }]
和pairedItem
的概念,以及 Execute Command 节点通过表达式嵌入数据和处理stdout/stderr/exitCode
。 - 翔宇也分享了自己在 实战中常用 这两个节点的场景,希望能给你一些启发。
- 我们还一起分析了各自 常见的报错信息,并掌握了重要的 调试技巧,特别是
console.log
和检查 n8n 执行日志。 - 最后,我们反复强调了使用这两个节点时必须注意的 事项,尤其是 Execute Command 节点的 安全风险。
现在,让我们再次总结一下这两个节点的用途和关键技巧:
代码 (Code) 节点总结:
- 核心用途:
- 进行标准节点无法完成的 自定义数据转换和格式化。
- 实现 复杂的条件判断和业务逻辑。
- 执行 数据聚合、计算和统计。
- 在自托管环境中,进行 简单的 API 调用 或与其他 JS/Python 库集成。
- 合并多个简单的处理步骤,使工作流更简洁。
- 掌握关键:
- 深刻理解并严格遵守 输入输出数据结构,特别是
[{ json: {...} }]
的输出格式 。 - 区分并根据场景选择 两种执行模式 (
Run Once for All Items
vsRun Once for Each Item
) 。 - 熟练使用 表达式 访问当前节点输入 (
$json
,items
) 和其他节点输出 ($("Node Name").item
) 。 - 理解
pairedItem
在何时以及如何使用,以维护数据链接 。
- 深刻理解并严格遵守 输入输出数据结构,特别是
- 实用技巧:
- 善用
console.log()
进行调试 。 - 养成 写注释 的好习惯 。
- 新手优先从
Run Once for Each Item
模式入手。 - 时刻 检查返回数据格式 是否符合规范。
- 善用
- 节点定位: 它是 n8n 灵活性 的核心体现,是连接无代码与低代码世界的桥梁,是让你突破标准功能限制的利器。
执行命令 (Execute Command) 节点总结:
- 核心用途 (仅限自托管):
- 调用宿主机上的 命令行工具 (如
ffmpeg
,git
,imagemagick
)。 - 执行 外部脚本 (Shell, Python, PHP, Perl 等)。
- 与只提供 CLI 接口 的系统或服务进行交互。
- 执行标准节点无法完成的 特定文件系统操作。
- 调用宿主机上的 命令行工具 (如
- 掌握关键:
- 清醒认识其 对运行环境的强依赖性 。
- 时刻警惕并防范 极高的安全风险,尤其是命令注入 [Insight 8]。
- 学会在命令中通过 表达式
${{...}}
安全地传递参数 。 - 必须检查命令执行结果:
exitCode
判断成功与否,stdout
获取输出,stderr
查看错误信息。
- 实用技巧:
- 优先使用 绝对路径 调用命令和访问文件。
- 极其谨慎 地处理来自外部的、可能包含特殊字符的输入数据。
- 对于复杂逻辑,将命令写入独立脚本,节点只负责调用脚本。
- 务必在使用前 确认命令是否存在、权限是否足够。
- 处理大量输出时,考虑 重定向到文件 。
- 节点定位: 它是 n8n 能力边界的 终极延伸,但如同出鞘的利剑,必须 极其谨慎 地使用。它只适用于自托管环境,并且在使用它之前,务必 优先寻找所有可能的替代方案 [Insight 9]。
给零基础朋友的最终建议:
- 从 Code 节点开始: 如果你是零基础,翔宇建议你先将精力集中在掌握 Code 节点的基础用法上,特别是
Run Once for Each Item
模式下的数据修改、创建和简单计算。它相对更安全、应用场景更广泛,也更能帮助你理解 n8n 的数据流。 - 循序渐进,不怕犯错: 不要期望一下子就掌握所有高级技巧。从解决你遇到的具体小问题开始,模仿翔宇给出的场景和示例,动手去实践。遇到错误是正常的,把它看作学习的机会,回顾教程中的报错分析和调试方法,尝试自己解决它。
- 多看文档,多做实验: n8n 的官方文档 是非常好的学习资源,虽然是英文,但内容详尽。结合文档,在你的 n8n 环境中多多尝试不同的写法和配置,实践是最好的老师。
- 安全意识,时刻紧绷: 如果你决定要使用 Execute Command 节点,请务必将“安全”二字刻在心里。任何不确定的命令、任何未经验证的输入,都不要在生产环境中执行。
- 拥抱灵活性,创造价值: 不要害怕这两个看似复杂的节点。它们是 n8n 赋予你的、突破平台限制的“超能力”。一旦你掌握了它们,你会发现你的自动化工作流将拥有无限可能,能够为你创造巨大的价值!
关注 “翔宇工作流” YouTube 频道学习实战案例
理论学习是打好基础的关键,但将知识应用到实际场景中解决问题,才能真正地融会贯通。
想要看到更多翔宇是如何在真实的业务场景中,巧妙地结合 Code 节点、Execute Command 节点以及 n8n 其他强大的节点,搭建出各种高效、实用的自动化工作流吗?想学习更多 n8