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 是有代价的,四条都得认。
- 要钱。每条线索一次模型调用。日均一千条线索,这笔账得算。
- 不确定。同样的输入,两次可能给出不同分数。规则打分是确定性的,AI 不是。
- 要处理格式错乱。你让它返回 JSON,它有时候会给你裹一层 markdown 代码块。得做解析容错。
- 要处理超时和失败。模型服务会抖。得配重试和降级路径,比如失败了就退回规则打分。
我的建议是两条腿走路。规则打分做兜底和快速通道(明显是垃圾的直接扔,明显是好的直接放行),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" }
}
