{
"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"
}