Lionel · 李豪个人兴趣、文章、摄影、视频和音乐
Article · 文章

看得见的自动化,n8n 到底解决了什么问题

Lionel
2026/08/03 · 24 min read

n8n 能把没人敢删的胶水脚本变成可见、可交接、可治理的工作流。文章用一套线索分诊实例讲清 item 数据流、AI 接入和生产所需的权限、版本与可靠性。

一、那个没人敢删的脚本

几乎每家公司都有这么一个文件。

它躺在某台不知道谁在维护的服务器上,crontab 里一行,每天凌晨三点跑一次。它把 A 系统的数据拉出来,洗一洗,塞进 B 系统,然后往某个群里发条消息。写它的人是三年前某个周五下班前顺手写的,代码里有一行注释是 # TODO: 这里以后要改,那个"以后"再也没有来过。那个人早就离职了。

现在没人知道它到底干了什么。删掉?不敢。改?更不敢。有天它挂了,全公司花了两天才搞清楚"原来那张报表是它生成的"。

这不是段子,这是绝大多数公司里"自动化"的真实形态。能跑,但没人接得住。

过去两年,一个叫 n8n 的开源项目在 GitHub 上冲到了 19.8 万 star、5.97 万 fork。介绍它的文章满大街都是,翻来覆去说的是"1500+ 集成"“可视化拖拽”“AI 时代的自动化神器”。这些都对,也都没说到点上,参数解释不了它为什么会火,也解释不了它到底替你解决了什么。

这篇文章想说清三件事。它解决的到底是什么问题;它怎么运转(会带你完整跑一个能导入就用的真实工作流);以及从玩具到生产,中间那条大多数人没走完的路。

二、它解决的到底是什么

先说三个真实数字,都来自 n8n 官方发布的客户案例,注意,这是厂商的营销材料,数字由客户自报、无第三方审计,但公司、人名、职务、时间线都是可核验的。

Delivery Hero(全球外卖平台,70 多个国家、5.3 万员工)碰到的问题具体到有点滑稽。员工账号被锁。每月约 800 次,流程是员工找 IT,IT 验证身份,然后手动恢复 Okta 和 Google Workspace 的权限。平均耗时 35 分钟。这 35 分钟里,一个拿着工资的人干瞪眼。

Vodafone UK 的问题在另一个极端。英国《电信(安全)法》的配套合规要求在 2024 年落地,运营商必须扩大日志监控覆盖面、把留存周期从 90 天延长到 13 个月。而 Vodafone 每月已经在处理 30 到 50 亿条安全事件。工程经理 Claire Van Hinsbergh 的原话是。“更多资产、更多数据、更多数据类型,就意味着更多监控、更多告警。我们现有的人工流程本来就很耗时间。”

Trendyol(土耳其最大电商,30 万卖家,技术组织 2000 人)的问题最微妙。他们不缺开发能力。2000 个工程师,200 个团队,还有自研的 agent 平台。缺的是吞吐,任何新东西都得排进产品路线图、抢工程资源。“系统里某个事件触发时发条 Slack 消息”"每天汇总一下昨天的数字"这种小事,永远排不上。

这三个场景表面上八竿子打不着,底下是同一个东西。

第一层。胶水活里,九成是样板

你想做的事情往往一句话能说清。“新客户填了表单,查一下公司背景,写进 CRM,通知销售群。”

真写起来呢?OAuth 认证怎么存、token 过期怎么刷新、接口分页怎么翻、对方限流了怎么退避、超时重试几次、失败了往哪报警、这玩意儿部署到哪、日志去哪看、明天想改个字段找谁。真正的业务逻辑就那四句话,剩下九成全是样板。

n8n 干的第一件事,是把这九成变成了配置项。

第二层。这类活写出来,没人维护

这才是关键的一层,也是最容易被忽略的一层。

第一层的问题,Zapier 十年前就在解决,说辞几乎一模一样。但如果只是"省下写样板代码的时间",那写个更好的脚本框架就够了,为什么非得是画布?

因为画布是自带文档的。

回到开头那个 crontab 脚本。它的真正问题不是难写,是难交接。它没有文档,没有测试,没有可视化,唯一的"文档"是作者的记忆,而作者已经走了。这类跨系统的胶水逻辑天然是孤儿代码,重要到不能停,边缘到没人愿意认领。

一张 n8n 画布打开,从哪触发、经过哪几步、哪一步在调什么、上次在哪儿失败的,一眼看完。可视化在这里不是"给不会写代码的人用"的妥协方案,它是让自动化能被别人接手的解法。

Trendyol 的产品经理 Barış Hasan Aras 描述过一个现象。非技术同事第一次看到拖拽界面的反应是惊讶,“他们开始把自己每天的工作想成流程图。他们能把笔记拿出来,拖几个节点连起来。”

这句话的重点不是"非技术人员也能用",是流程第一次变成了一个可以被讨论的对象。它从某个人脑子里,挪到了桌面上。

第三层。大模型会想,但不会干

2024 年之后又多了一层。

大模型能读懂一封邮件的意思,能判断这单该不该退款,能从一段乱糟糟的留言里提取出"这人想买"。但它碰不到你的数据库,发不出那条 Slack,改不了 CRM 里的一个字段。

要让它真的干活,中间得有一层执行基座。有连接器、有权限控制、有重试、有执行记录能复盘。n8n 正好卡在这个位置。Huel 的 Ollie Scheers 说得挺准。“ChatGPT 和 Claude 都很好,但 n8n 才是那个让你能把 AI 安全可控地接进工作流程的东西。”

大模型负责想,n8n 那一千多个节点负责干。 这是它这两年爆火最直接的原因。

三、五个概念,看懂任何一张画布

先说名字。n8n 念作 n-eight-n,是 “nodemation”(node + automation)的缩写。创始人 Jan Oberhauser 解释过。好域名都被注册光了,最后选了 nodemation,“node” 是因为它用节点视图、跑在 Node.js 上,“-mation” 是 automation。然后他嫌全名在命令行里打起来太长,缩成了 n8n。

跑起来只要一行。

npx n8n

或者用 Docker。

docker volume create n8n_data
docker run -it --rm --name n8n -p 5678:5678 \
  -v n8n_data:/home/node/.n8n docker.n8n.io/n8nio/n8n

浏览器打开 localhost:5678,编辑器就在那儿了。

五个概念够你看懂任何一张 n8n 画布。

Workflow(工作流),一张画布上的一整套自动化,是最小的部署和管理单位。

Node(节点),画布上的一个方块,等于一个动作。官方号称 1500+ 集成。

Trigger node(触发节点),特殊的第一个节点,决定"什么时候跑"。定时、收到 HTTP 请求、来了新邮件、表格新增一行。每个要上线的工作流都必须有一个。

Credential(凭证),API key、OAuth 密钥单独存放,和节点解耦。配一次,所有用到这个服务的节点共用。

Expression(表达式),在节点参数里写 {{ }} 求值,比如 {{ $json.email }} 取当前数据的邮箱,{{ $today.minus(7, 'days') }} 算七天前。这是让节点"动"起来的关键,静态配置和动态流程的分界线就在这儿。

概念说完了。下面直接上手。

四、实操。一个能导入就用的线索分诊工作流

我们来做一件真实的事。官网表单提交进来一条线索,自动判断值不值得销售马上跟,值得的合并成一条消息推到销售群,不值得的丢进培育池。

这个例子挑得有讲究,它不依赖任何需要申请的 API key,你导入就能跑通;同时它能自然带出 n8n 最大的那个坑。

整条流程九个节点。

接收表单(Webhook) → 立即回执(202) → 拆分线索 → 线索打分 → 是否高意向?
                                                            ├─ true  → 合并成一条通知 → 发给销售群
                                                            └─ false → 写入培育池

节点 1。接收表单(Webhook)

n8n 有个被严重低估的用法。它自己就能当 API 后端。

Webhook 节点会给你一个公网可访问的 URL,任何人 POST 数据过来就触发这条工作流。你不需要写路由、不需要部署服务、不需要买域名。搭个原型后端,二十秒的事。

关键配置。

HTTP Method:  POST
Path:         lead
Respond:      Using 'Respond to Webhook' Node

第三项很重要,下面说。

节点 2。立即回执(Respond to Webhook)

这一步是大多数教程不讲、但生产环境里必须做的。

webhook 必须秒回。 对方(表单服务、支付网关、GitHub)发请求给你,通常只等几秒。如果你的流程里要调大模型、要查三个外部接口,跑完可能要十几秒,对方早就超时重试了,于是同一条线索你收到了三遍。

正确做法是。先回一个"我收到了",然后再慢慢处理。 在 n8n 里,把 Webhook 节点的 Respond 设成 Using 'Respond to Webhook' Node,然后在流程中段放一个 Respond to Webhook 节点。

Respond With:   JSON
Response Body:  { "status": "accepted" }
Response Code:  202

Respond 节点会把收到的数据原样往下传,所以回执之后流程继续跑,互不影响。

节点 3。拆分线索(Code,Run Once for All Items)

表单可能一次提交一条,也可能批量导入一批。这个节点负责把两种情况归一化成统一格式。

// mode = runOnceForAllItems ── 1 个 item 进来,N 个 item 出去
const raw = $input.first().json.body ?? $input.first().json;
const leads = Array.isArray(raw.leads) ? raw.leads : [raw];

return leads.map((lead) => ({
  json: {
    name:    (lead.name    ?? '').trim(),
    email:   (lead.email   ?? '').trim().toLowerCase(),
    company: (lead.company ?? '').trim(),
    message: (lead.message ?? '').trim(),
    source:  raw.source ?? 'unknown',
    receivedAt: new Date().toISOString(),
  },
}));

Webhook 收到的 HTTP 请求体在 json.body 里。?? $input.first().json 是个兜底,方便你在编辑器里手动喂测试数据。

这里有个新手必踩的坑先提一句。Code 节点的沙箱没有网络。 fetch、axios、XMLHttpRequest、require('http') 全都会在运行时失败,官方文档和源码注释里都写死了这一条。想调接口,请用 HTTP Request 节点,然后在 Code 节点里处理它的输出。第一次想在 Code 里发请求的人,百分之百撞这堵墙。

节点 4。线索打分(Code,Run Once for Each Item)

注意模式变了。上一个节点是"跑一次处理全部",这个是"有几条跑几次"。

// mode = runOnceForEachItem ── 只管当前这一条,别的不用操心
const lead = $json;
const freeMail = ['gmail.com','qq.com','163.com','126.com','outlook.com','hotmail.com','yahoo.com'];
const domain = (lead.email.split('@')[1] ?? '');
const valid  = /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(lead.email);
const isBiz  = valid && domain && !freeMail.includes(domain);

let score = 0;
const reasons = [];
if (isBiz)                     { score += 35; reasons.push('企业邮箱'); }
if (lead.company)              { score += 15; reasons.push('填了公司'); }
if (lead.message.length >= 30) { score += 25; reasons.push('留言详细'); }
if (/报价|采购|预算|试用|demo|pricing|trial|quote/i.test(lead.message)) {
  score += 25; reasons.push('含采购意图');
}

return { json: { ...lead, valid, domain, score, reasons } };

在 runOnceForEachItem 模式下,$json 就是当前这一条,你不用写循环,循环是 n8n 帮你跑的。这一点非常关键,下一节整节都在讲它。

节点 5。是否高意向(If)

{{ $json.score }} 大于等于 70 走 true,否则走 false。If 节点会把数据分成两股,各走各的路。

节点 6。合并成一条通知(Code,Run Once for All Items)

这个节点是整条流程里最该被记住的一个。

// mode = runOnceForAllItems ── N 个 item 进来,1 个 item 出去
const hot = $input.all().map(i => i.json);
if (hot.length === 0) return [];

const lines = hot.map(l =>
  `• ${l.name || '(无名)'} @ ${l.company || '未知公司'} — ${l.score} 分(${l.reasons.join('、')})`
);

return [{ json: {
  channel: '#sales-inbound',
  text: `本次收到 ${hot.length} 条高意向线索:\n${lines.join('\n')}`,
}}];

为什么需要它?下一节揭晓。

节点 7、8。发给销售群 / 写入培育池

这两个是 No Operation 占位节点,什么都不干,就是让流程图完整。你把它们换成 Slack 节点、HubSpot 节点、Postgres 节点,这条流程就能上线。

跑一遍看看

POST 一个含五条线索的请求进去。

① Webhook 输出:  1 item            ← 一个 HTTP 请求 = 一条数据
② 拆分线索后:    5 items           ← runOnceForAllItems,1 进 5 出
③ 打分后:        5 items           ← runOnceForEachItem,逐条跑了 5 次
④ If → true: 3 items | false: 2 items
⑤ 合并通知:      1 item            ← 又收回成 1 条
姓名 邮箱域名 分数 理由
张伟 acme-industrial.com 100 企业邮箱 / 填了公司 / 留言详细 / 含采购意图
小李 qq.com 0 ,
Wang bytefoo.io 100 企业邮箱 / 填了公司 / 留言详细 / 含采购意图
陈静 northwind-logistics.cn 75 企业邮箱 / 填了公司 / 留言详细
赵敏 gmail.com 65 填了公司 / 留言详细 / 含采购意图

完整的 workflow JSON 我放在文末了,复制粘贴就能导入。

五、唯一真正的门槛

现在回到那串数字。1 → 5 → 5 → 3/2 → 1。

n8n 里所有节点之间传递的,永远是同一种东西。一个数组。 数组里每个元素长这样。

[
  { "json": { "name": "张伟", "score": 100 } },
  { "json": { "name": "小李", "score": 0 } }
]

json 装普通数据,binary 装文件。就这么简单。

由此推出一条会决定你成败的规则。

节点收到 N 条数据,就自动执行 N 次。

官方文档举的例子很直白。Trello 节点设成"创建卡片",卡片名用表达式取上游的 name 字段。上游传来两条数据,它就建两张卡,每次自动取当前那条的 name。

你不写循环。循环是数据结构自带的。

这是 n8n 和"写个脚本"最不一样的地方,也是百分之八十的新手困惑的唯一来源。

“我明明只想发一封邮件,为什么发了 47 封”

现在回头看节点 6 就懂了。

如果我在 If 的 true 分支后面直接挂一个 Slack 节点,会发生什么?三条高意向线索 = 三个 item = Slack 节点跑三次 = 销售群里三条消息。五十条线索就是刷屏五十条。

节点 6 存在的全部意义,就是把 N 个 item 收拢成 1 个 item。用的正是 runOnceForAllItems 模式。$input.all() 一把抓住所有数据,return [{ json: ... }] 只吐一条出去。

n8n 也有专门干这事的节点(Aggregate、Summarize、Merge),但我建议初学者先用 Code 节点手写一次,你得先在脑子里建立"我现在手上有几条数据"这个感觉,工具才用得对。

两个模式,一句话记住

什么时候用 手里有什么
Run Once for All Items 汇总、去重、排序、改变数据条数 $input.all(),全部数据
Run Once for Each Item 逐条加工,不改变条数 $json,当前这一条

判断标准只有一个。这一步会不会改变数据的条数? 会,就用 All Items;不会,就用 Each Item。

想不清楚的时候,双击任何一个节点,左边 INPUT 面板、右边 OUTPUT 面板,条数明明白白写在那儿。这也是画布相对脚本最实在的好处之一,数据流是可见的,不用靠 print 猜。

六、把打分换成 AI

回头看那张表,赵敏那一行是全文最值钱的一行。

她的留言是。“想申请试用,我们团队大概 30 人,主要做跨境电商的订单处理。”

意图明确到不能再明确了。要试用、有规模、说了业务场景。但她用的是 Gmail,规则里"企业邮箱"那 35 分一分没拿到,总分 65,卡在 70 的门槛外面,被扔进了培育池。

这就是规则打分的天花板。它数得清字符,读不懂意思。

也正是该换 AI 的地方。在 n8n 里,把节点 4 那个 Code 节点换成 Basic LLM Chain 或 AI Agent 节点,提示词大意是"读这条线索,判断采购意图强弱,输出 0-100 的分数和理由",赵敏立刻会被捞出来。

Chain 和 Agent 的区别

n8n 的 AI 能力是用**集群节点(cluster node)**这个机制搭的。一个 root 节点,下面挂若干 sub 节点,聊天模型、记忆、工具、向量库。你在画布上看到的是一个大方块底下吊着几个小方块。

两者的分别,官方文档说得很干净。

  • Chain(链)。按你预先定好的顺序调用组件,既没有持久记忆,也挂不了工具。适合"读一段文本,输出一个结构化结果"这种一次性加工。
  • Agent(智能体)。用大模型自己决定该调哪个工具、调几次。可以挂 memory 记住上下文。执行一次工作流,Agent 内部可能跑好几轮,先规划,再调工具,再评估工具返回的结果,最后回答。

官方给的类比很好记。Agent 就是一条"会做决定"的 Chain。

线索打分这种场景,Chain 就够了,别为了用 Agent 而用 Agent。

换 AI 的代价,得说清楚

很多文章讲到这儿就开始吹了。但换 AI 是有代价的,四条都得认。

  1. 要钱。每条线索一次模型调用。日均一千条线索,这笔账得算。
  2. 不确定。同样的输入,两次可能给出不同分数。规则打分是确定性的,AI 不是。
  3. 要处理格式错乱。你让它返回 JSON,它有时候会给你裹一层 markdown 代码块。得做解析容错。
  4. 要处理超时和失败。模型服务会抖。得配重试和降级路径,比如失败了就退回规则打分。

我的建议是两条腿走路。规则打分做兜底和快速通道(明显是垃圾的直接扔,明显是好的直接放行),AI 只处理中间那段模糊地带。既省钱又稳,还保留了可解释性。

顺便一提,n8n 明确支持你在 OpenAI、Anthropic、Google 和开源模型之间随便切,不用改架构。这在模型半年一换代的当下,是个实打实的好处。

七、从玩具到生产,中间隔着一整套治理

到这儿,你已经能做出一个能跑的工作流了。但能跑和能在公司里长期跑,中间隔着一条很多人没走完的路。

三条最值得抄的实践

第一,起点要小而痛,不要做平台。

Delivery Hero 那个案例的关键数字不是"每月省 200 小时",是**“5 小时”**,从开始做到上线,一个工作流,一下午。Trendyol 的起点是一个工程师刷 GitHub trending 看到了 n8n,在自己电脑上跑了个 POC。

我翻遍了所有公开案例,没有一个是从"我们要建设自动化中台"开始的。全都是某个人被某件具体的破事烦透了。

第二,模块化,别造巨型画布。

Vodafone 的做法是把工作流拆成可复用的模块,“一旦你做好了一个邮件模块,它到哪儿都能用”。这是他们能从 33 个工作流一路做到"指数级产出"的原因。

反过来说,社区里最常见的抱怨是。单张画布超过二三十个节点之后,调试开始变成折磨,错误信息偏泛,定位很花时间。可视化在小规模是优势,在大规模会反噬,一张意大利面画布比一份意大利面代码更难读,因为代码至少能全文搜索。

第三,谁提需求谁维护。

Trendyol 有一条明文规则。复杂工作流可以由中央团队来建,但建完必须移交给提需求的那个团队自己养。

这条规则我认为是所有公开案例里最有洞察力的一句。它一石二鸟,既防住了"中央团队变成新瓶颈",也防住了本文开头那个"没人敢删的脚本"。工作流有主人,自动化才不会重新变回黑盒。

配套的治理架构也值得抄。Trendyol 建了约 200 个 project,一个技术团队一个,成员只能看见自己 project 里的工作流和凭证,团队经理当管理员;凭证进 vault;全量日志;维护团队每天和安全团队碰一次。

规模上来之后

单实例撑不住的时候,n8n 提供了 queue mode。Redis 当消息中间件,Postgres 做持久化,多个 worker 并行消费。

几个实打实的注意事项。

  • SQLite 不支持分布式部署。上生产就换 Postgres,别侥幸。
  • worker 并发建议设 5 以上。很多 worker 配低并发,反而会打爆数据库连接池,导致延迟和失败。这个反直觉,但官方专门提醒过。
  • 该切换的信号大致是。并发执行明显堆积、webhook 成批涌入、单点故障不可接受。
  • 顺带一提,queue mode 社区版就有,但多主节点高可用(multi-main)是企业版功能。真要做无单点,这笔账得算进去。

三件必须泼的冷水

第一,n8n 不是标准开源。

它用的是 fair-code 模式下的 Sustainable Use License,加一个企业版许可。源码永远可见、可以自托管、可以扩展,但商业使用有限制。写文章时把这一点含糊过去,是不诚实的。

第二,社区版撑不住多人协作。

社区版当然有执行日志,但缺的是日志外推(推给外部 SIEM)、SSO、项目隔离、Git 版本控制、外部密钥管理这一层。

Trendyol 的技术负责人 Hüseyin Ulaş 说。"社区版缺日志和 SSO,这两个是我们安全要求的硬指标。"他们跑了一个月就买了企业版。

Stepstone 的经历更有参考价值,因为他们用社区版扛了将近四年(2021 到 2025 年初)才迁移。Marketplace 技术负责人 Luka Pilic 的原话是。“社区版在初期是有效的,但重负载下很快就变得束手束脚。越过某个运营门槛之后,我们需要更高级的能力,比如像样的凭证和工作流权限、专门的执行监控,因为继续靠一个共享的主账号,很快就撑不住了。”

"一个共享的主账号"这五个字,比任何功能对比表都说明问题。

所以那句"开源自托管所以免费",是入门文章里最容易撒的谎。免费的是引擎,撑起多人生产的那套东西,权限、单点登录、多环境、合规日志,大部分在企业版里。真要走这条路,把迁移成本提前算进预算,别等到共享主账号出事那天。

第三,版本管理和测试仍然是弱项。

工作流逻辑一复杂,追踪失败、写测试、做版本对比都很吃力。顺便说个历史坑。n8n 1.x 时代,保存一个已激活的工作流会直接推到生产,意思是你在线上调试时的半成品可能正在跑。这个问题到 2.0 才修好,现在有了草稿和发布的分离(编辑器里那个 Publish 按钮)。如果你看到的老教程里没有这个概念,说明它过时了。

八、最后。省下来的时间,到底是谁省的

回到 Delivery Hero。

先澄清一下那个数字。每月 200 小时不是 IT 团队省下的工时,是全公司员工"被锁在账号外面干等"的总时长减少了 200 小时,平均锁定时长从 35 分钟降到 20 分钟,乘以每月约 800 次。

那这 15 分钟是怎么砍掉的?很多人会说。因为 n8n 自动调用了 Okta、Jira 和 Google 的 API。

我的判断是,这只对了一半。真正的改动是另一个。他们把审批人从 IT 换成了员工的直属经理。

以前的流程是"员工找 IT → IT 验证身份 → IT 恢复权限",IT 是瓶颈,你得排队等一个跟你不熟的人有空;新流程是"员工发起 → 经理点同意 → 系统自动执行",审批人就在隔壁,而且本来就知道你是谁。案例里没有把两者的贡献拆开算,但一个跨部门工单变成一次同组点击,这个提速很难只归功于几个 API 调用。

n8n 的作用是把这个决策的实施成本压到了一下午。5 小时,一个工作流。在没有这类工具的年代,"改一下审批流"意味着提需求、排期、开发、测试、上线,三个月起步,多数时候提案还没排上就被人忘了。

所以,如果你现在打算上手 n8n,我的建议不是"去找找有什么能自动化的"。

是反过来问一句。我们这儿哪个流程,明明大家都知道它蠢,但因为改起来太麻烦,就一直这么蠢着?

那个地方,才是你应该放下第一个节点的位置。


附录。完整可导入的 workflow JSON

在 n8n 编辑器里新建工作流,右上角菜单选 Import from File / Clipboard,粘贴下面这段。导入后打开「接收表单」节点复制 Test URL,POST 一个 {"leads":[...]} 的 JSON 过去即可看到效果。「发给销售群」「写入培育池」是占位节点,换成真实节点就能上线。

{
  "name": "线索自动处理(入门示例)",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "lead",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [-820, 0],
      "id": "接收表单",
      "name": "接收表单",
      "webhookId": "3f7c1c9a-0d2e-4a11-9b8e-6c5a2d1e4f70"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\n  \"status\": \"accepted\"\n}",
        "options": { "responseCode": 202 }
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.5,
      "position": [-600, 0],
      "id": "立即回执",
      "name": "立即回执"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Code 节点「拆分线索」 mode = runOnceForAllItems\nconst raw = $input.first().json.body ?? $input.first().json;\nconst leads = Array.isArray(raw.leads) ? raw.leads : [raw];\nreturn leads.map((lead) => ({\n  json: {\n    name:    (lead.name    ?? '').trim(),\n    email:   (lead.email   ?? '').trim().toLowerCase(),\n    company: (lead.company ?? '').trim(),\n    message: (lead.message ?? '').trim(),\n    source:  raw.source ?? 'unknown',\n    receivedAt: new Date().toISOString(),\n  },\n}));"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [-380, 0],
      "id": "拆分线索",
      "name": "拆分线索"
    },
    {
      "parameters": {
        "mode": "runOnceForEachItem",
        "language": "javaScript",
        "jsCode": "// Code 节点「打分」 mode = runOnceForEachItem\nconst lead = $json;\nconst freeMail = ['gmail.com','qq.com','163.com','126.com','outlook.com','hotmail.com','yahoo.com'];\nconst domain = (lead.email.split('@')[1] ?? '');\nconst valid  = /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(lead.email);\nconst isBiz  = valid && domain && !freeMail.includes(domain);\n\nlet score = 0;\nconst reasons = [];\nif (isBiz)                     { score += 35; reasons.push('企业邮箱'); }\nif (lead.company)              { score += 15; reasons.push('填了公司'); }\nif (lead.message.length >= 30) { score += 25; reasons.push('留言详细'); }\nif (/报价|采购|预算|试用|demo|pricing|trial|quote/i.test(lead.message)) { score += 25; reasons.push('含采购意图'); }\n\nreturn { json: { ...lead, valid, domain, score, reasons } };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [-160, 0],
      "id": "线索打分",
      "name": "线索打分"
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.score }}",
              "rightValue": 70,
              "operator": { "type": "number", "operation": "gte" }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [60, 0],
      "id": "是否高意向",
      "name": "是否高意向"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Code 节点「合并成一条通知」 mode = runOnceForAllItems\nconst hot = $input.all().map(i => i.json);\nif (hot.length === 0) return [];\nconst lines = hot.map(l => `• ${l.name || '(无名)'} @ ${l.company || '未知公司'} — ${l.score} 分(${l.reasons.join('、')})`);\nreturn [{ json: {\n  channel: '#sales-inbound',\n  text: `本次收到 ${hot.length} 条高意向线索:\\n${lines.join('\\n')}`,\n}}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [300, -100],
      "id": "合并成一条通知",
      "name": "合并成一条通知"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [520, -100],
      "id": "发给销售群",
      "name": "发给销售群"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [300, 120],
      "id": "写入培育池",
      "name": "写入培育池"
    }
  ],
  "connections": {
    "接收表单":       { "main": [[{ "node": "立即回执", "type": "main", "index": 0 }]] },
    "立即回执":       { "main": [[{ "node": "拆分线索", "type": "main", "index": 0 }]] },
    "拆分线索":       { "main": [[{ "node": "线索打分", "type": "main", "index": 0 }]] },
    "线索打分":       { "main": [[{ "node": "是否高意向", "type": "main", "index": 0 }]] },
    "是否高意向":     { "main": [
                          [{ "node": "合并成一条通知", "type": "main", "index": 0 }],
                          [{ "node": "写入培育池", "type": "main", "index": 0 }]
                      ] },
    "合并成一条通知": { "main": [[{ "node": "发给销售群", "type": "main", "index": 0 }]] }
  },
  "pinData": {},
  "active": false,
  "settings": { "executionOrder": "v1" }
}

资料来源

#文章
Lionel Written by
我叫李豪,来自云南昭通,现居杭州 喜欢摄影、足球 、Vibe Coding 和老婆一起养了两只猫
Scinentistcoldplay