n8n 工作流程工程師
何時觸發
- 編輯或建置 n8n 工作流程 JSON
- 對 n8n 進行除錯:"節點未執行"、"配對項目不可用"、"靜態資料未持久化"
- 透過 n8n REST API 進行批次操作(重新命名、重試、刪除)
- Telegram、Gmail、HTTP 請求、程式碼、切換、合併節點模式
- n8n 中的排程觸發器 / 時區 / cron 行為
- n8n v2.x UI 行為(發布/取消發布、歸檔)
嚴格規則
- 先審計後編輯。 讀取完整的工作流程 JSON → 檢查發現 → 確認 → 一次性套用。永遠不要盲目修補。
- 透過排程觸發器進行測試。 手動"執行"會重置
$workflow.staticData。去重驗證需要一次真正的 cron 執行。 - 驗證真實結果。 檢查執行日誌和最終狀態(訊息已送達、資料列已存在)。節點返回
ok: true是好跡象,但並非證明。UI 同步延遲會誤導觀察者。 - 使程式碼節點模式與返回簽章相符。 錯誤的模式 + 錯誤的返回 = 靜默部分處理。
快速參考 — 最常見的錯誤
| 症狀 | 根本原因 | 修復方法 |
|---|---|---|
HTTP 請求到 localhost:4000 返回拒絕/超時 | n8n 處於 Docker 橋接模式 | 使用主機閘道:在預設橋接上通常是 172.17.0.1(請用 docker network inspect bridge 檢查),或在 extra_hosts 中映射 host.docker.internal:host-gateway |
| Telegram 訊息從未送達,無錯誤 | IPv6 DNS 解析超時 | 在 docker-compose 中設定 NODE_OPTIONS=--dns-result-order=ipv4first |
new Date() 返回 UTC,儘管 cron 設定為 EST | 容器缺少 TZ 環境變數 | 將 TZ 和 GENERIC_TIMEZONE 都設定為您的時區 |
parse_mode: Markdown 靜默失敗或亂碼 | AI 輸出中存在未匹配的 * 或 _ | 使用 parse_mode: HTML + 發送前清理 |
| 對 LiteLLM HTTP 請求節點返回 401 | 缺少認證標頭 | 添加 Authorization: Bearer {{ $env.LITELLM_API_KEY }} |
對 Claude 呼叫返回 ECONNABORTED: timeout of 15000ms | 預設 15 秒超時 | 設定 options.timeout: 90000 |
PUT 到 /api/v1/workflows/:id 返回 400 | 請求體中包含額外欄位 | 僅保留:name, nodes, connections, settings, staticData |
| 重新命名節點會靜默破壞工作流程 | 連線參考引用舊名稱 | 在 nodes[].name 和 connections{} 鍵中全部取代舊名稱 |
| 程式碼聚合後配對項目不可用 | N→1→M 丟失了項目譜系 | 設定 executeOnce: true,明確迭代 $('UpstreamNode').all() |
| 手動執行時靜態資料去重"無法運作" | 手動執行會重置靜態資料 | 僅透過排程 cron 測試 |
程式碼節點 — 模式/返回規則
每個程式碼節點都有一個 mode 參數。不匹配 = 靜默錯誤。
| 模式 | 存取模式 | 返回 |
|---|---|---|
runOnceForAllItems(預設) | const items = $input.all(); | return items.map(...) — 必須是陣列 |
runOnceForEachItem | $json 是當前項目 | return { json: ... } — 單一物件,非陣列 |
不匹配的症狀:
runOnceForAllItems+ 存取$json→ 僅靜默處理第一個項目runOnceForEachItem+ 返回陣列 →"A 'json' property isn't an object"驗證錯誤
去重模式(跨執行跑狀態)
const store = $getWorkflowStaticData('global');
store.jobIds = store.jobIds || [];
const newItems = items.filter(item => !store.jobIds.includes(item.json.id));
// 持久化新 ID,限制最後 500 個以避免無限增長
store.jobIds = [...store.jobIds, ...newItems.map(i => i.json.id)].slice(-500);
return newItems;
關鍵: staticData 僅在排程執行之間持久化。手動執行會重置它。透過觀察兩個連續的排程執行來驗證去重,而不是點擊兩次執行。
每次執行快取清理
// 在第一個程式碼節點頂部 — 清理臨時快取但保留跨執行跑狀態
const store = $getWorkflowStaticData('global');
for (const k of Object.keys(store)) {
if (k.startsWith('cjob_') || k.startsWith('cprep_') || k === 'pendingJobs') {
delete store[k];
}
}
// 永遠不要刪除 jobIds — 跨執行跑去重依賴於它
管線可觀測性模式
在每個主要程式碼節點設定計數器。在格式化步驟讀取。附加到訊息中。
// 階段 1 — 建立 ticker
const store = $getWorkflowStaticData('global');
store.pipeline = { ticker_count: tickers.length, fixed_count: fixed.length };
// 階段 2 — 計算訊號
store.pipeline = store.pipeline || {};
store.pipeline.signal_count = results.length;
// 最終 — 格式化訊息
const pl = store.pipeline || {};
const pf = (n) => (n === undefined || n === null) ? '?' : String(n);
const pipelineBlock =
'\n\n📊 管線\n' +
'建立 Ticker: ' + pf(pl.ticker_count) + '\n' +
'訊號: ' + pf(pl.signal_count);
配對項目問題 + 解決方法
症狀: 在聚合節點(N 個項目 → 1 個提示 → 1 個 LLM 回應 → 拆分為 M 個項目)之後,下游的 $('UpstreamNode').item.json 拋出 Paired item data for item from node 'X' is unavailable。
解決方法(按偏好順序):
-
在下游程式碼節點設定
executeOnce: true,明確迭代:const upstream = $('UpstreamNode').all(); const outputs = []; for (let i = 0; i < parsed.length; i++) { outputs.push({ json: { ...parsed[i], original: upstream[i].json } }); } return outputs; -
透過靜態資料傳遞 ID;在下游明確查詢它們。
-
pairedItem.sourceOverwrite— 脆弱,最後手段。
Telegram 模式
HTML 模式 — 只允許 7 個標籤
<b>, <i>, <u>, <s>, <a>, <code>, <pre> — 其他任何標籤都會拋出 400 Bad Request: Unsupported start tag。
AI 生成內容的三層防禦
- 提示詞:明確告訴 AI 哪些標籤是允許的
- 清理:在發送前於程式碼中移除不支援的標籤
- 跳脫:在嵌入前對使用者/AI 內容進行 HTML 跳脫(
&→&,<→<,>→>)
抑制 n8n 署名頁尾
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false,
"disable_web_page_preview": true
}
.env 中的 Token 與憑證
偏好使用環境變數 + HTTP 請求模式,而非內建的 Telegram 節點 + 憑證:
- 在遷移時可移植
- 更容易輪換(
nano .env+ 容器重啟) - 無資料庫加密金鑰鎖定風險
- 模式:
POST https://api.telegram.org/bot{{ $env.BOT_TOKEN }}/sendMessage - 權衡:token 位於請求 URL 中,因此可能會出現在執行資料、代理日誌和錯誤訊息中。為此工作流程關閉執行資料的儲存,或者如果其他人可以讀取您的日誌,則使用內建的 Telegram 憑證。
Gmail OAuth 模式
| 問題 | 解決方案 |
|---|---|
invalid_grant ... refresh token revoked | "測試中"模式的 OAuth 大約 7 天後過期。透過 UI 重新授權,或將應用程式發布到生產環境。 |
空的 payload.headers(寄件者/主旨空白) | 預設 simple: true 會剝奪標頭。設定 options.simple: false。 |
| 遺失安全警報 / 收據 | 不要排除 -category:promotions -category:social -category:updates — Gmail 將 90% 的相關郵件路由到那裡。僅使用 is:unread newer_than:Nd。 |
| 需要跨多個帳戶標記為已讀 | 需要每個帳戶的憑證。一個 Gmail 憑證無法對另一個帳戶的郵件 ID 進行操作。 |
REST API 模式(批次操作)
認證:X-N8N-API-KEY: <key> 標頭。
| 端點 | 備註 |
|---|---|
GET /api/v1/workflows | 返回所有(分頁)。客戶端過濾。 |
GET /api/v1/workflows/:id | 完整工作流程 JSON。 |
PUT /api/v1/workflows/:id | 嚴格:僅允許 name, nodes, connections, settings, staticData。settings 本身有一個白名單(見下文)。在任何 GET 後 PUT 的往返中,剝奪其他所有欄位。 |
DELETE /api/v1/workflows/:id | 實際刪除(UI 僅提供歸檔)。用於批次清理。 |
POST /api/v1/workflows/:id/activate | 等同於 v2.x UI 中的發布。 |
settings 欄位白名單(PUT 請求體)
executionOrder, timezone, saveExecutionProgress, saveDataErrorExecution, saveDataSuccessExecution, saveManualExecutions, callerPolicy, callerIds, errorWorkflow, executionTimeout
節點 type 在建立後不可變
更改 node.type 的 PUT 請求返回 200,但類型保持不變。要轉換(例如 telegram → httpRequest):取代整個節點物件(為 id 建立新的 UUID,保留相同的 name)。連線透過名稱路由,因此保留名稱的取代是安全的。
n8n v2.x UI 具體說明
| 舊版 (v1.x) | 新版本 (v2.x) |
|---|---|
| 啟用切換 | 發布(綠色按鈕,右上角) / 取消發布 |
| 刪除工作流程 | 僅歸檔(使用 REST API 進行實際刪除) |
HTTP 請求節點 — 用於 LLM 呼叫的必備項目
{
"method": "POST",
"url": "http://172.17.0.1:4000/v1/chat/completions",
"sendHeaders": true,
"headerParameters": {
"parameters": [
{"name": "Content-Type", "value": "application/json"},
{"name": "Authorization", "value": "=Bearer {{ $env.LITELLM_API_KEY }}"}
]
},
"sendBody": true,
"contentType": "raw",
"rawContentType": "application/json",
"body": "={{ JSON.stringify({model:'<your-model-alias>',messages:[{role:'user',content:$json.prompt}],temperature:0.3}) }}",
"options": {
"timeout": 90000,
"retry": { "tries": 2, "waitBetweenTries": 5000 }
}
}
n8n 容器沒有 curl
對於容器內的 HTTP 測試,請透過 docker exec 使用 Node 內建的 https 模組:
docker exec n8n node -e "const https=require('https'); /* request */"
SSH 事前檢查(在 VPS 上進行任何 heredoc 之前)
ssh -o ServerAliveInterval=30 -o ServerAliveCountMax=10 <user>@<host>
# 驗證提示符號:[<user>@<host> ...]$
# 只有這樣才能貼上 heredoc
在 SSH 連線後將 bash heredoc 貼上到 PowerShell 中 = 數百個解析錯誤。
評估套件模式(僅邏輯)
透過 n8n 評估功能進行的模擬輸入測試可以捕獲邏輯回歸,而無需消耗 API 額度。在對過濾邏輯、關鍵字列表或驗證規則進行任何更改之前和之後重新執行。
反模式
| 反模式 | 應該怎麼做 |
|---|---|
| "再執行一次,看看是否可行" | 先端到端閱讀執行日誌 |
| 在多次往返中一個接一個地修補節點 | 審計完整檔案 → 捆綁修補腳本 → 一次性驗證 |
對 AI 輸出使用 parse_mode: Markdown | parse_mode: HTML + 清理器 |
| 在工作流程 JSON 中硬編碼 API 金鑰 / 聊天 ID | 參考 {{ $env.KEY }},儲存在 <secret-file> 中(chmod 600) |
| 重新命名節點而不更新連線 | 在 nodes[].name 和 connections{} 鍵中全部取代節點名稱 |
| 手動執行以測試去重 | 等待排程觸發器 |
| 對 LLM 呼叫使用預設的 httpRequest 超時 | 設定最少 90000ms |
混合使用 runOnceForAllItems 和 $json | 使模式與存取模式相符 |