n8n 人工审批流如何防重与防过期
文章用确定性草稿键、部分唯一索引和事件去重控制重复草稿,通过版本比较处理乱序事件。再以过期机制和原子审批状态转换,避免旧草稿被批准或重复发送。
AI 深度解读
给 AI 自动化加一道人工审批,并不会自动解决重复发送。工作流重跑会堆出多份草稿,Webhook 重试会重复投递,旧事件晚到还可能覆盖新内容。原文给出的办法是:先用数据库约束草稿的身份、版本和状态,再让人审批。
这套方案围绕 n8n 和 Postgres 展开:同一客户的同一种操作最多保留一份待审草稿,48 小时未处理就过期,只有成功改变状态的审批请求才能继续发送。思路实用,但原文没有完整解决发送失败与并发更新的问题。
先管住草稿数量,再让 LLM 写内容
原文把几个容易混淆的概念拆开了:
draft_key标识一项待处理操作,由客户或记录标识加操作类型计算哈希。同一客户的不同操作可以分别排队。event_key标识一次来源事件,用来拦截同一事件的重复投递。source_version判断事件新旧,防止迟到的旧事件覆盖已有内容。revision记录草稿修订次数,方便更新审核通知。
其中,事件版本必须能够单调比较。作者认为可以使用序列号、CRM 修改时间,或提供方保证有序的事件 ID;任意一个事件 ID 并不天然具备顺序。
下面这段建表代码解决事件去重记录和待审草稿的唯一性问题:
CREATE TABLE seen_events (
event_key CHAR(64) PRIMARY KEY,
seen_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE draft_queue (
id BIGSERIAL PRIMARY KEY,
draft_key CHAR(64) NOT NULL,
entity TEXT, kind TEXT, body TEXT,
source_version BIGINT,
revision INT DEFAULT 1,
status TEXT NOT NULL, -- pending | approved | rejected | expired | sent
expires_at TIMESTAMPTZ,
decided_by TEXT, decided_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX one_pending ON draft_queue(draft_key) WHERE status = 'pending';
重点是最后一行的部分唯一索引(partial unique index):唯一约束只覆盖 pending 状态。历史记录可以保留,但同一个 draft_key 不能同时出现两份待审草稿。
草稿唯一性应该由数据库保证,不能只靠工作流先查一次。
因为两个并发任务可能同时查到「没有草稿」,随后各自插入。数据库约束才能挡住这种竞争。
审批按钮能不能防重复,取决于状态怎么改
作者设计的 n8n 流程是:Webhook 接收事件,Code 节点构造键和版本,Postgres 拦截重复投递,再由 IF 节点跳过不比现有草稿更新的事件。只有新事件才调用 LLM,随后执行 upsert,也就是存在则更新、不存在则插入,并递增 revision。
这个顺序还有一个直接收益:重复事件和旧事件不用消耗生成 token。
审批端则用一条 SQL 同时检查状态、有效期并完成状态转换:
UPDATE draft_queue
SET status = 'approved', decided_by = $1, decided_at = NOW()
WHERE draft_key = $2 AND status = 'pending' AND expires_at > NOW()
RETURNING id, body;
要看的是 WHERE 条件和 RETURNING:只有仍在有效期内的待审草稿才能变成 approved,后续发送应使用返回的 body。没有返回行就停止,避免重复点击再次放行同一条待审记录。
过期处理有两层:草稿设置为 48 小时后失效,调度触发器每小时清理一次;即使清理任务尚未运行,审批 SQL 里的 expires_at > NOW() 也能阻止过期审批。
通知同样需要控制。作者建议只在插入新键或提高 revision 时通知审核人,并编辑原来的 Slack 消息,避免每次重跑都新增一条。
我的判断
我认同作者把确定性规则放在 LLM 前面的顺序。去重、版本比较、过期判断都适合交给程序和数据库,人负责判断内容是否合适。
但「审批步骤不能重复发送」这个表述需要收窄。SQL 能保证同一条待审记录不会被两次审批成功,却没有把数据库更新和外部发送合成一个事务。状态改成 approved 后进程崩溃,可能漏发;外部发送成功但本地未记录,重试仍可能重复。原文提到发送幂等键、预算上限和紧急停止开关,但没有给出完整实现。
审核版本也有缺口:SQL 只按 draft_key 审批,没有绑定审核人看到的 revision。如果审核页面展示旧内容,而数据库草稿刚被更新,按钮可能批准另一版内容。部分唯一索引也只约束 pending,并不限制同一键下同时存在已批准记录和新待审记录。
此外,「先查版本,再 upsert」存在并发窗口。原文没有展示带版本条件的更新,也没说明事件去重记录写入后、草稿生成失败时怎样恢复。
相比让 Agent 在高置信度时直接发送,人工审批提供了内容复核机会,但两种做法都需要发送幂等。原文没有对照实验、压力测试或故障测试,应把它当作架构经验。
作者还给出 $0 到 $5 的运行预算,建议在 Railway 自托管 n8n 加 Postgres,或从 n8n Cloud 起步,但未交代套餐、用量和模型费用。Make.com 的 Data Store 可以按 draft_key 存储数据,不过原文也没有证明它具备同等的并发约束能力。
能拿来做什么
- 给客户消息或邮件草稿增加稳定的
draft_key,把事件去重和草稿唯一性分开处理,并在调用 LLM 前过滤重复、过时事件。 - 做审核页面时,把审核人看到的
revision随审批请求提交,让后端同时校验版本、状态和有效期,避免批准未看过的更新。 - 在 Node 发送服务中单独设计发送幂等与失败恢复,覆盖审批成功后崩溃、发送超时和回执丢失的情况。
- 配置 48 小时有效期与每小时清理任务,并复用同一条审核通知,让过期草稿和重复提醒都能被控制。