{ "name": "告警去重與抑制機制", "nodes": [ { "parameters": { "httpMethod": "POST", "path": "alert-receiver", "options": {} }, "id": "webhook", "name": "告警接收器", "type": "n8n-nodes-base.webhook", "typeVersion": 2, "position": [240, 300] }, { "parameters": { "jsCode": "const alert = $input.first().json;\nconst now = Date.now();\nconst alertKey = `${alert.environment}_${alert.alertType}_${alert.service}`;\n\n// 從 static data 讀取告警歷史\nconst alertHistory = $getWorkflowStaticData('global');\nif (!alertHistory.alerts) {\n alertHistory.alerts = {};\n}\n\nconst lastAlert = alertHistory.alerts[alertKey];\nconst cooldownMinutes = {\n 'critical': 5, // 嚴重告警:5 分鐘內不重複\n 'warning': 15, // 警告:15 分鐘內不重複\n 'info': 60 // 資訊:1 小時內不重複\n};\n\nconst cooldown = (cooldownMinutes[alert.severity] || 15) * 60 * 1000;\nconst shouldSend = !lastAlert || (now - lastAlert.timestamp > cooldown);\n\nif (shouldSend) {\n alertHistory.alerts[alertKey] = {\n timestamp: now,\n count: (lastAlert?.count || 0) + 1\n };\n}\n\n// 清理超過 24 小時的歷史\nObject.keys(alertHistory.alerts).forEach(key => {\n if (now - alertHistory.alerts[key].timestamp > 24 * 60 * 60 * 1000) {\n delete alertHistory.alerts[key];\n }\n});\n\nreturn [{\n json: {\n ...alert,\n shouldSend,\n suppressReason: shouldSend ? null : `冷卻中 (${cooldownMinutes[alert.severity]}分鐘)`,\n repeatCount: alertHistory.alerts[alertKey]?.count || 1\n }\n}];" }, "id": "dedup", "name": "告警去重邏輯", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [460, 300] }, { "parameters": { "conditions": { "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }, "conditions": [ { "id": "should-send", "leftValue": "={{ $json.shouldSend }}", "rightValue": true, "operator": { "type": "boolean", "operation": "equals" } } ], "combinator": "and" }, "options": {} }, "id": "if-send", "name": "是否發送?", "type": "n8n-nodes-base.if", "typeVersion": 2, "position": [680, 300] }, { "parameters": { "jsCode": "const alert = $input.first().json;\nconst now = new Date().toLocaleString('zh-TW', { timeZone: 'Asia/Taipei' });\n\nconst severityEmoji = {\n 'critical': '🔴',\n 'warning': '🟠',\n 'info': '🔵'\n};\n\nconst envEmoji = {\n 'UAT': '🟦',\n 'GCP': '🟥'\n};\n\nlet message = `${severityEmoji[alert.severity] || '⚪'} ${envEmoji[alert.environment] || ''} ${alert.title}\\n\\n`;\nmessage += `📍 環境: ${alert.environment}\\n`;\nmessage += `🔧 服務: ${alert.service}\\n`;\nmessage += `📝 詳情: ${alert.description}\\n`;\n\nif (alert.repeatCount > 1) {\n message += `🔄 重複次數: ${alert.repeatCount}\\n`;\n}\n\nmessage += `⏰ 時間: ${now}`;\n\nreturn [{ json: { message } }];" }, "id": "format", "name": "格式化訊息", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [900, 200] }, { "parameters": { "url": "https://api.telegram.org/bot/sendMessage", "sendBody": true, "specifyBody": "json", "jsonBody": "={\n \"chat_id\": \"5619078117\",\n \"parse_mode\": \"HTML\",\n \"text\": \"{{ $json.message }}\"\n}", "options": {} }, "id": "telegram", "name": "發送 Telegram", "type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2, "position": [1120, 200] }, { "parameters": { "jsCode": "// 記錄被抑制的告警(供分析用)\nconst alert = $input.first().json;\nconsole.log(`告警被抑制: ${alert.alertType} - ${alert.suppressReason}`);\nreturn [{ json: { suppressed: true, reason: alert.suppressReason } }];" }, "id": "log-suppressed", "name": "記錄抑制", "type": "n8n-nodes-base.code", "typeVersion": 2, "position": [900, 400] } ], "connections": { "告警接收器": { "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": "發送 Telegram", "type": "main", "index": 0 }] ] } }, "settings": { "executionOrder": "v1" }, "staticData": null, "tags": [], "triggerCount": 0, "updatedAt": "2026-02-07T17:00:00.000Z", "versionId": "1" }